@softspark/ai-toolkit 2.3.1 → 2.4.1

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/CHANGELOG.md CHANGED
@@ -7,6 +7,39 @@ Versioning follows [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ---
9
9
 
10
+ ## v2.4.1 — Codex Global Install, Security Hardening & Editor Tracking (2026-04-15)
11
+
12
+ ### Added
13
+ - **Codex hook propagation** — `inject-hook` auto-propagates Codex-compatible events (`SessionStart`, `PreToolUse`, `PostToolUse`, `UserPromptSubmit`, `Stop`) to `~/.codex/hooks.json`. `remove-hook` cleans both targets.
14
+ - **Global editor tracking** — `ai-toolkit install --editors codex` installs editors globally (opt-in). Tracked in `state.json`, auto-refreshed on `update`. Default: Claude only.
15
+ - **Per-project editor tracking** — `install --local --editors` records editors in `projects.json`. `update` re-installs saved editors per project.
16
+ - **Auto-propagation** — `add-rule`, `remove-rule`, `mcp add` auto-propagate to globally installed editors via `propagate_global.py`.
17
+ - **Doctor Check 9** — URL hook sources health check with `--fix` re-fetch support.
18
+
19
+ ### Fixed
20
+ - **Tarball path traversal** — `_extract_tarball` rejects symlinks, absolute paths, and paths escaping dest directory.
21
+ - **Git clone URL validation** — `_resolve_git` enforces HTTPS-only, rejects `file://`, `ssh://`.
22
+ - **`dr.error()` crash** — `DiagResult` has no `error()` method; fixed to `dr.fail()`.
23
+ - **`refresh_url_hooks` missing timestamp update** — now calls `register_url_source` after re-fetch.
24
+ - **Defense-in-depth name validation** — `register_url_source()` rejects path traversal chars in both `hook_sources` and `rule_sources`.
25
+ - **Unused `execSync` import** — removed from CLI entry point.
26
+ - **URL truncation detection** — `url_fetch.py` raises `ValueError` when response exceeds 10MB.
27
+ - **MCP templates path** — corrected `app/plugins/mcp-templates/` → `app/mcp-templates/` in ARCHITECTURE docs.
28
+ - **MCP templates header count** — corrected 25 → 26 in `app/ARCHITECTURE.md`.
29
+
30
+ ---
31
+
32
+ ## v2.4.0 — URL Hook Injection & Karpathy Coding Rules (2026-04-15)
33
+
34
+ ### Added
35
+ - **URL hook injection** — `ai-toolkit inject-hook https://...` fetches, caches, and injects hooks from HTTPS URLs. Cached in `~/.softspark/ai-toolkit/hooks/external/`, auto-refreshed on every `update`. `remove-hook` also unregisters URL source and cleans cache.
36
+ - **Shared URL fetch module** — extracted `url_fetch.py` from `rule_sources.py` for reuse by both rule and hook URL sources.
37
+ - **Hook URL source registry** — `hook_sources.py` tracks URL-sourced hooks in `sources.json` (analogous to `rule_sources.py`).
38
+ - **Surgical Changes rule** — orphan cleanup protocol, match existing style, don't touch adjacent code (inspired by Karpathy's LLM coding guidelines).
39
+ - **Goal-Driven Execution rule** — `step → verify: check` pattern for multi-step tasks, strong success criteria before looping.
40
+
41
+ ---
42
+
10
43
  ## v2.3.1 — Release Quality Gate (2026-04-14)
11
44
 
12
45
  ### Fixed
package/README.md CHANGED
@@ -6,17 +6,17 @@
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
7
  [![Skills](https://img.shields.io/badge/skills-92-brightgreen)](app/skills/)
8
8
  [![Agents](https://img.shields.io/badge/agents-44-blue)](app/agents/)
9
- [![Tests](https://img.shields.io/badge/tests-606%20passing-success)](tests/)
9
+ [![Tests](https://img.shields.io/badge/tests-618%20passing-success)](tests/)
10
10
 
11
11
  ---
12
12
 
13
- ## What's New in v2.3.1
13
+ ## What's New in v2.4.1
14
14
 
15
- - **URL rule registration** — `ai-toolkit add-rule https://...` with auto-refresh on every update
16
- - **Jira MCP template** — `ai-toolkit mcp add jira` with `postInstall` setup hints
17
- - **MCP template tracking** — globally installed templates saved in `state.json`, auto-synced on `update`
18
- - **Race condition fix** — parallel project updates no longer drop registry entries (fcntl.flock + atomic writes)
19
- - **Claude MCP paths fixed** — now uses `~/.claude.json` (global) and `.mcp.json` (project) per official docs
15
+ - **Global editor tracking** — `ai-toolkit install --editors codex` installs Codex globally, tracked in `state.json`, auto-refreshed on `update`
16
+ - **Codex hook propagation** — `inject-hook` auto-propagates to `~/.codex/hooks.json` for Codex-compatible events
17
+ - **Auto-propagation** — `add-rule`, `remove-rule`, `mcp add` auto-propagate to all globally installed editors
18
+ - **Security hardening** — tarball path traversal fix, git URL scheme validation, defense-in-depth name validation
19
+ - **Doctor Check 9** — URL hook sources health check with `--fix` re-fetch support
20
20
 
21
21
  See [CHANGELOG.md](CHANGELOG.md) for full history.
22
22
 
@@ -143,7 +143,7 @@ ai-toolkit/
143
143
  │ └── ARCHITECTURE.md # Full system design
144
144
  ├── kb/ # Reference docs, procedures, plans
145
145
  ├── scripts/ # Validation, install, evaluation scripts
146
- ├── tests/ # Bats test suite (606 tests)
146
+ ├── tests/ # Bats test suite (618 tests)
147
147
  └── CHANGELOG.md
148
148
  ```
149
149
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ai-toolkit",
3
3
  "description": "Professional-grade Claude Code toolkit with persona presets, skill security auditor, expanded lifecycle hooks, experimental opt-in plugin packs, benchmark harvesting, and multi-tool support.",
4
- "version": "2.3.1",
4
+ "version": "2.4.1",
5
5
  "author": {
6
6
  "name": "SoftSpark",
7
7
  "url": "https://github.com/softspark"
@@ -301,16 +301,16 @@ Lead Session (You)
301
301
 
302
302
  ## Extension Points
303
303
 
304
- ### MCP Templates (25)
305
- `app/plugins/mcp-templates/` ships 26 ready-to-use MCP server config templates (filesystems, databases, GitHub, Slack, etc.). Opt-in via `ai-toolkit install --modules mcp-templates` or activated automatically with `--profile strict|full`.
304
+ ### MCP Templates (26)
305
+ `app/mcp-templates/` ships 26 ready-to-use MCP server config templates (filesystems, databases, GitHub, Slack, etc.). Opt-in via `ai-toolkit install --modules mcp-templates` or activated automatically with `--profile strict|full`.
306
306
 
307
307
  ### Language Rules (68 files, 13 languages)
308
308
  `app/rules/` contains per-language coding rules. Supported languages: TypeScript, Python, Go, Rust, Java, Kotlin, Swift, Dart, C#, PHP, C++, Ruby, and common (shared). Auto-detected from project files via `--auto-detect` or selected with `--modules rules-<lang>`.
309
309
 
310
310
  Language rules are propagated to **all configured editors** — not just Claude. `dir_rules_shared.build_language_rules()` reads `app/rules/<lang>/*.md`, strips frontmatter, and returns combined content per language. Each directory-based generator (Cursor, Windsurf, Cline, Roo, Augment, Antigravity, Codex) emits `ai-toolkit-lang-<lang>` files in its native format. Registered custom rules (`~/.softspark/ai-toolkit/rules/`) are similarly propagated as `ai-toolkit-custom-<name>` files via `build_registered_rules()`.
311
311
 
312
- ### Extension API (`inject-hook`)
313
- The `inject_section_cli.py` script provides a stable marker-based injection API. Any tool can add sections to `CLAUDE.md`, `constitution.md`, or `ARCHITECTURE.md` without overwriting user content, using `<!-- TOOLKIT:START:<id> -->` / `<!-- TOOLKIT:END:<id> -->` markers.
312
+ ### Extension API (`inject-hook`, `inject-rule`)
313
+ The `inject_section_cli.py` script provides a stable marker-based injection API. Any tool can add sections to `CLAUDE.md`, `constitution.md`, or `ARCHITECTURE.md` without overwriting user content, using `<!-- TOOLKIT:START:<id> -->` / `<!-- TOOLKIT:END:<id> -->` markers. `inject_hook_cli.py` injects hooks into `settings.json` with `_source` tags — supports both local files and HTTPS URLs (cached in `~/.softspark/ai-toolkit/hooks/external/`, auto-refreshed on `update`). Shared URL fetch logic lives in `url_fetch.py`.
314
314
 
315
315
  ### SLM Compilation (`compile-slm`)
316
316
  `scripts/compile_slm.py` compiles the full toolkit (20K+ tokens) into a minimal system prompt for Small Language Models (2K-16K tokens). Pipeline: Parse → Score → Compress → Pack → Emit. Supports 4 compression levels (ultra-light, light, standard, extended), 4 output formats (raw, ollama, json-string, aider), persona-aware scoring, and language-aware rule filtering. Profile `offline-slm` in `manifest.json`. Constitution is always included (non-negotiable).
@@ -7,11 +7,12 @@ version: "1.0.0"
7
7
  # Universal Coding Style
8
8
 
9
9
  ## Principles
10
- - KISS: simplest solution that works. Clever code is a liability.
10
+ - KISS: simplest solution that works. Clever code is a liability. If 200 lines could be 50, rewrite.
11
11
  - DRY: extract when you repeat 3+ times, not before.
12
- - YAGNI: do not build features "just in case."
12
+ - YAGNI: do not build features "just in case." No abstractions for single-use code.
13
13
  - Prefer immutability: use `const`, `final`, `val`, `let` by default.
14
14
  - Fail fast: validate inputs at boundaries, return early on errors.
15
+ - State assumptions before coding. If uncertain or multiple interpretations exist, ask — don't pick silently.
15
16
 
16
17
  ## Naming
17
18
  - Use descriptive names that reveal intent (`remainingRetries`, not `r`).
@@ -45,6 +46,19 @@ version: "1.0.0"
45
46
  - Max line length: 80-120 characters depending on language convention.
46
47
  - Trailing commas in multi-line structures (where language supports).
47
48
 
49
+ ## Surgical Changes
50
+ - Touch only what the task requires. Every changed line should trace to the request.
51
+ - Match existing style, even if you would do it differently.
52
+ - Do not "improve" adjacent code, comments, or formatting unprompted.
53
+ - Orphan cleanup: remove imports/variables/functions that YOUR changes made unused.
54
+ - Do not remove pre-existing dead code unless explicitly asked.
55
+
56
+ ## Goal-Driven Execution
57
+ - Transform vague tasks into verifiable goals before starting.
58
+ - For multi-step work, state a brief plan with verification per step:
59
+ `1. [Step] → verify: [check]`
60
+ - Strong success criteria enable independent looping. Weak criteria ("make it work") require clarification — ask first.
61
+
48
62
  ## Anti-Patterns to Avoid
49
63
  - God classes/modules with 500+ lines and multiple responsibilities.
50
64
  - Deep nesting (>3 levels): use early returns and extract functions.
package/bin/ai-toolkit.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- const { execFileSync, spawnSync, execSync } = require('child_process');
4
+ const { execFileSync, spawnSync } = require('child_process');
5
5
  const path = require('path');
6
6
  const fs = require('fs');
7
7
 
@@ -65,8 +65,8 @@ const COMMANDS = {
65
65
  uninstall: 'Remove ai-toolkit from ~/.claude/',
66
66
  'add-rule': 'Register a rule file or URL in ~/.softspark/ai-toolkit/rules/ (URL rules auto-refresh on update)',
67
67
  'remove-rule': 'Unregister a rule from ~/.softspark/ai-toolkit/rules/ and remove its block from CLAUDE.md',
68
- 'inject-hook': 'Inject external hooks into ~/.claude/settings.json (tagged with _source for idempotent updates)',
69
- 'remove-hook': 'Remove injected hooks by source name from ~/.claude/settings.json',
68
+ 'inject-hook': 'Inject external hooks (file or URL) into ~/.claude/settings.json (URL hooks auto-refresh on update)',
69
+ 'remove-hook': 'Remove injected hooks by source name from ~/.claude/settings.json (also unregisters URL source)',
70
70
  validate: 'Verify toolkit integrity',
71
71
  doctor: 'Check install health, hooks, and artifact drift',
72
72
  eject: 'Export standalone config (no symlinks, no toolkit dependency)',
@@ -151,6 +151,23 @@ function run(script, args = [], opts = {}) {
151
151
  }
152
152
  }
153
153
 
154
+ /**
155
+ * Propagate changes to globally installed editors (from state.json).
156
+ * Silently skips if no global editors are configured.
157
+ * @param {...string} flags - Flags to pass: --rules, --hooks, --mcp
158
+ */
159
+ function propagateGlobal(...flags) {
160
+ const result = spawnSync('python3', [scriptPath('propagate_global.py'), ...flags], {
161
+ stdio: 'inherit',
162
+ cwd: CWD,
163
+ env: { ...process.env },
164
+ });
165
+ // Non-fatal — propagation failure shouldn't block the primary operation
166
+ if (result.status !== 0) {
167
+ console.error('Warning: global editor propagation had issues (non-fatal)');
168
+ }
169
+ }
170
+
154
171
  /**
155
172
  * Generic dispatcher for SCRIPT_COMMANDS entries.
156
173
  * Resolves the script path and selects the correct cwd.
@@ -226,10 +243,11 @@ function showHelp() {
226
243
  console.log(' <rule-name> Name of rule to unregister (filename without .md)');
227
244
  console.log(' [target-dir] Target dir containing .claude/CLAUDE.md (default: $HOME)');
228
245
  console.log('\nOptions for inject-hook:');
229
- console.log(' <hooks-file> Path to JSON file with {"hooks": {"EventName": [...]}} format');
230
- console.log(' [target-dir] Target dir containing .claude/settings.json (default: $HOME)');
246
+ console.log(' <hooks-file-or-url> Path to JSON file or HTTPS URL with {"hooks": {"EventName": [...]}}');
247
+ console.log(' [hook-name] Override source name (default: filename/URL stem)');
248
+ console.log(' [target-dir] Target dir containing .claude/settings.json (default: $HOME)');
231
249
  console.log('\nOptions for remove-hook:');
232
- console.log(' <source-name> Source tag to remove (derived from hooks filename stem)');
250
+ console.log(' <source-name> Source tag to remove (also unregisters URL source if present)');
233
251
  console.log(' [target-dir] Target dir containing .claude/settings.json (default: $HOME)');
234
252
  console.log('\nOptions for add-rule:');
235
253
  console.log(' <rule-file> Path to .md rule file or HTTPS URL to register globally');
@@ -330,6 +348,7 @@ function handleRemoveRule(args) {
330
348
  }
331
349
  const targetDir = args[1] || process.env.HOME;
332
350
  run(scriptPath('remove_rule.py'), [ruleName, targetDir]);
351
+ propagateGlobal('--rules');
333
352
  }
334
353
 
335
354
  /**
@@ -344,24 +363,32 @@ function handleAddRule(args) {
344
363
  }
345
364
  // Pass URLs through directly (don't resolve as filesystem path)
346
365
  const isUrl = ruleFile.startsWith('https://') || ruleFile.startsWith('http://');
366
+ if (ruleFile.startsWith('http://')) {
367
+ console.error('Error: only HTTPS URLs are supported. Use https:// for security.');
368
+ process.exit(1);
369
+ }
347
370
  const absRuleFile = isUrl ? ruleFile : path.resolve(CWD, ruleFile);
348
371
  const ruleName = args[1];
349
372
  run(scriptPath('add_rule.py'), ruleName ? [absRuleFile, ruleName] : [absRuleFile]);
373
+ propagateGlobal('--rules');
350
374
  }
351
375
 
352
376
  /**
353
- * Handle `ai-toolkit inject-hook` -- injects external hooks into settings.json.
377
+ * Handle `ai-toolkit inject-hook` -- injects external hooks (file or URL) into settings.json.
354
378
  * @param {string[]} args
355
379
  */
356
380
  function handleInjectHook(args) {
357
- const hooksFile = args[0];
358
- if (!hooksFile) {
359
- console.error('Usage: ai-toolkit inject-hook <hooks-file.json> [target-dir]');
381
+ const source = args[0];
382
+ if (!source) {
383
+ console.error('Usage: ai-toolkit inject-hook <hooks-file-or-url> [hook-name] [target-dir]');
360
384
  process.exit(1);
361
385
  }
362
- const absHooksFile = path.resolve(CWD, hooksFile);
363
- const targetDir = args[1] || process.env.HOME;
364
- run(scriptPath('inject_hook_cli.py'), [absHooksFile, targetDir]);
386
+ const isUrl = source.startsWith('https://') || source.startsWith('http://');
387
+ // For URLs pass as-is; for files resolve to absolute path
388
+ const resolvedSource = isUrl ? source : path.resolve(CWD, source);
389
+ // Pass remaining args through — Python CLI handles positional parsing
390
+ const remaining = args.slice(1);
391
+ run(scriptPath('inject_hook_cli.py'), [resolvedSource, ...remaining]);
365
392
  }
366
393
 
367
394
  /**
@@ -388,6 +415,10 @@ function handleMcp(args) {
388
415
  process.exit(1);
389
416
  }
390
417
  run(scriptPath('mcp_manager.py'), args);
418
+ // After `mcp add`, propagate to global editors
419
+ if (args[0] === 'add') {
420
+ propagateGlobal('--mcp');
421
+ }
391
422
  }
392
423
 
393
424
  /**
@@ -5,7 +5,7 @@ service: ai-toolkit
5
5
  tags: [architecture, overview, design, structure]
6
6
  version: "1.4.4"
7
7
  created: "2026-03-23"
8
- last_updated: "2026-04-13"
8
+ last_updated: "2026-04-15"
9
9
  description: "Architecture of ai-toolkit: directory layout, global install model, editor-aware MCP install, Codex translation layer, skill tiers, and integration with projects."
10
10
  ---
11
11
 
@@ -335,13 +335,13 @@ Severity levels: HIGH (blocks deployment), WARN (should fix), INFO (best practic
335
335
  ## Extension Points
336
336
 
337
337
  ### MCP Templates
338
- `app/plugins/mcp-templates/` contains 26 ready-to-use MCP server config templates. Opt-in via `ai-toolkit install --modules mcp-templates` or activated automatically with `--profile strict|full`.
338
+ `app/mcp-templates/` contains 26 ready-to-use MCP server config templates. Opt-in via `ai-toolkit install --modules mcp-templates` or activated automatically with `--profile strict|full`.
339
339
 
340
340
  ### Language Rules
341
341
  `app/rules/` provides language-specific rule files covering 13 languages (TypeScript, Python, Go, Rust, Java, Kotlin, Swift, Dart, C#, PHP, C++, Ruby, common). Auto-detected from project files via `--auto-detect` or selectable with `--modules rules-<lang>`. See README.md for current count.
342
342
 
343
- ### Extension API (`inject-hook`)
344
- `inject_section_cli.py` provides a stable marker-based API for injecting content into `CLAUDE.md`, `constitution.md`, or `ARCHITECTURE.md` without overwriting user content.
343
+ ### Extension API (`inject-hook`, `inject-rule`)
344
+ `inject_section_cli.py` provides a stable marker-based API for injecting content into `CLAUDE.md`, `constitution.md`, or `ARCHITECTURE.md` without overwriting user content. `inject_hook_cli.py` injects hooks into `settings.json` with `_source` tags — supports both local files and HTTPS URLs (cached in `~/.softspark/ai-toolkit/hooks/external/`, auto-refreshed on `update`).
345
345
 
346
346
  ### Manifest Install (`--modules`, `--auto-detect`)
347
347
  `manifest.json` defines all installable components as named modules. Install individual modules with `ai-toolkit install --modules <name>` or enable auto-detection to select language rules based on files found in the project.
@@ -4,7 +4,7 @@ category: reference
4
4
  service: ai-toolkit
5
5
  tags: [cli, commands, reference, install, update, plugin, mcp]
6
6
  created: "2026-04-13"
7
- last_updated: "2026-04-13"
7
+ last_updated: "2026-04-15"
8
8
  description: "Complete CLI reference for all ai-toolkit commands, options, and flags."
9
9
  ---
10
10
 
@@ -34,10 +34,10 @@ Usage: ai-toolkit <command> [options]
34
34
 
35
35
  | Command | Description |
36
36
  |---------|-------------|
37
- | `add-rule <rule.md> [name]` | Register rule in `~/.softspark/ai-toolkit/rules/` — auto-applied on every `update` |
37
+ | `add-rule <rule.md\|url> [name]` | Register rule in `~/.softspark/ai-toolkit/rules/` — auto-applied on every `update` |
38
38
  | `remove-rule <name> [dir]` | Unregister rule and remove its block from `CLAUDE.md` |
39
- | `inject-hook <file.json>` | Inject external hooks into settings.json (idempotent, `_source` tagged) |
40
- | `remove-hook <name>` | Remove injected hooks by source name |
39
+ | `inject-hook <file.json\|url> [name]` | Inject external hooks (file or URL) into settings.json (idempotent, `_source` tagged, URL hooks auto-refresh on update) |
40
+ | `remove-hook <name>` | Remove injected hooks by source name (also unregisters URL source if present) |
41
41
 
42
42
  ## MCP Management
43
43
 
@@ -107,6 +107,10 @@ This means Claude-only events such as `TaskCompleted`, `TeammateIdle`,
107
107
  `SubagentStart`, `SubagentStop`, `PreCompact`, `SessionEnd`, and
108
108
  `Notification` are not available in `.codex/hooks.json`.
109
109
 
110
+ `inject-hook` automatically propagates Codex-compatible events to
111
+ `~/.codex/hooks.json` (global layer). Non-Codex events are silently skipped.
112
+ `remove-hook` cleans both Claude and Codex targets.
113
+
110
114
  ## Behavioral Limits
111
115
 
112
116
  Codex wrappers preserve workflow intent, but not every Claude runtime behavior
@@ -3,9 +3,9 @@ title: "Extension API Reference"
3
3
  category: reference
4
4
  service: ai-toolkit
5
5
  tags: [extension-api, inject-rule, inject-hook, mcp-templates, integration, editors]
6
- version: "1.3.9"
6
+ version: "1.4.0"
7
7
  created: "2026-04-07"
8
- last_updated: "2026-04-12"
8
+ last_updated: "2026-04-15"
9
9
  description: "Reference for ai-toolkit's extension API: inject-rule, inject-hook, remove-rule, remove-hook, and editor-aware MCP template management."
10
10
  ---
11
11
 
@@ -23,9 +23,9 @@ This design is intentional: ai-toolkit is a generic toolkit. Consumers (MCP serv
23
23
  |---------|-------------|-----------|------------|
24
24
  | `inject-rule <file.md>` | `~/.claude/CLAUDE.md` | HTML comment markers (`<!-- TOOLKIT:name -->`) | Yes |
25
25
  | `remove-rule <name>` | `~/.claude/CLAUDE.md` | Strip markers by block name | Yes |
26
- | `inject-hook <file.json>` | `~/.claude/settings.json` | JSON `_source` tag per entry | Yes |
27
- | `remove-hook <name>` | `~/.claude/settings.json` | Strip all entries with matching `_source` | Yes |
28
- | `add-rule <file.md>` | `~/.softspark/ai-toolkit/rules/` | File copy + re-inject all rules on next `update` | Yes |
26
+ | `inject-hook <file.json\|url> [name]` | `~/.claude/settings.json` | JSON `_source` tag per entry, URL cached + registered | Yes |
27
+ | `remove-hook <name>` | `~/.claude/settings.json` | Strip all entries with matching `_source`, unregister URL source | Yes |
28
+ | `add-rule <file.md\|url>` | `~/.softspark/ai-toolkit/rules/` | File copy + re-inject all rules on next `update` | Yes |
29
29
  | `mcp add <name...>` | `.mcp.json` | Merge `mcpServers` block from template | Yes |
30
30
  | `mcp install --editor <name...>` | Native editor MCP config | Render canonical template into editor format | Yes |
31
31
 
@@ -60,13 +60,20 @@ The argument is the block name (file stem used during `inject-rule`). If the blo
60
60
 
61
61
  ## inject-hook
62
62
 
63
- Injects hook entries from a JSON file into `~/.claude/settings.json`. Every injected entry is tagged with `"_source": "<source-name>"` where the source name is derived from the filename stem.
63
+ Injects hook entries from a JSON file or HTTPS URL into `~/.claude/settings.json`. Every injected entry is tagged with `"_source": "<source-name>"` where the source name is derived from the filename stem or URL last segment.
64
64
 
65
65
  ```bash
66
+ # From local file
66
67
  npx @softspark/ai-toolkit inject-hook ./my-tool-hooks.json
68
+
69
+ # From URL (HTTPS only) — cached locally, auto-refreshed on update
70
+ npx @softspark/ai-toolkit inject-hook https://example.com/my-tool-hooks.json
71
+
72
+ # With explicit source name
73
+ npx @softspark/ai-toolkit inject-hook https://example.com/hooks.json my-tool-hooks
67
74
  ```
68
75
 
69
- **Implementation:** `scripts/inject_hook_cli.py`.
76
+ **Implementation:** `scripts/inject_hook_cli.py`, `scripts/hook_sources.py`, `scripts/url_fetch.py`.
70
77
 
71
78
  **Input format:**
72
79
  ```json
@@ -88,15 +95,19 @@ npx @softspark/ai-toolkit inject-hook ./my-tool-hooks.json
88
95
  }
89
96
  ```
90
97
 
91
- **Source name derivation:** `my-tool-hooks.json` → source name `"my-tool-hooks"`. All entries are tagged `"_source": "my-tool-hooks"` in settings.json.
98
+ **Source name derivation:** `my-tool-hooks.json` → source name `"my-tool-hooks"`. For URLs: `https://example.com/path/my-tool-hooks.json` → `"my-tool-hooks"`. All entries are tagged `"_source": "my-tool-hooks"` in settings.json.
99
+
100
+ **URL support:** When an HTTPS URL is provided, the JSON is fetched, validated, cached in `~/.softspark/ai-toolkit/hooks/external/<name>.json`, and registered in `sources.json`. On every `ai-toolkit update`, URL-sourced hooks are re-fetched and re-injected automatically. If the fetch fails during update, the cached version is used.
92
101
 
93
102
  **Idempotency:** Re-running strips all existing entries with the same source name, then appends the new ones. No duplicates accumulate.
94
103
 
95
- **Safety:** Entries tagged `"_source": "ai-toolkit"` are never modified or removed by this command. External tools cannot affect the toolkit's own hooks.
104
+ **Safety:** Entries tagged `"_source": "ai-toolkit"` are never modified or removed by this command. External tools cannot affect the toolkit's own hooks. Only HTTPS URLs are accepted.
105
+
106
+ **Codex propagation:** Codex-compatible events (`SessionStart`, `PreToolUse`, `PostToolUse`, `UserPromptSubmit`, `Stop`) are automatically propagated to `~/.codex/hooks.json`. Non-Codex events are silently skipped. No extra flags needed.
96
107
 
97
108
  ## remove-hook
98
109
 
99
- Strips all hook entries from `~/.claude/settings.json` that carry a given `_source` tag.
110
+ Strips all hook entries from `~/.claude/settings.json` that carry a given `_source` tag. If the hook was URL-sourced, also unregisters the URL from `sources.json` and removes the cached file.
100
111
 
101
112
  ```bash
102
113
  npx @softspark/ai-toolkit remove-hook my-tool-hooks
@@ -140,14 +151,14 @@ When `install` runs with `--scope project`, ai-toolkit also updates `.mcp.json`
140
151
  │ Public Extension API: │
141
152
  │ inject-rule <file.md> → CLAUDE.md │
142
153
  │ remove-rule <name> → CLAUDE.md │
143
- │ inject-hook <file.json> → settings.json │
154
+ │ inject-hook <file|url> → settings.json │
144
155
  │ remove-hook <name> → settings.json │
145
- │ add-rule <file.md> → rules/ registry │
156
+ │ add-rule <file|url> → rules/ registry │
146
157
  │ mcp add <template> → .mcp.json │
147
158
  │ mcp install <template> → editor-native MCP │
148
159
  │ │
149
160
  │ Idempotent: markers (rules) / _source tags (hooks) │
150
- ai-toolkit NEVER calls external services
161
+ URL sources: cached + auto-refreshed on update
151
162
  └──────────────────────────────────────────────────────┘
152
163
 
153
164
  │ uses API
package/llms-full.txt CHANGED
@@ -4791,8 +4791,8 @@ Severity levels: HIGH (blocks deployment), WARN (should fix), INFO (best practic
4791
4791
  ### Language Rules
4792
4792
  `app/rules/` provides language-specific rule files covering 13 languages (TypeScript, Python, Go, Rust, Java, Kotlin, Swift, Dart, C#, PHP, C++, Ruby, common). Auto-detected from project files via `--auto-detect` or selectable with `--modules rules-<lang>`. See README.md for current count.
4793
4793
 
4794
- ### Extension API (`inject-hook`)
4795
- `inject_section_cli.py` provides a stable marker-based API for injecting content into `CLAUDE.md`, `constitution.md`, or `ARCHITECTURE.md` without overwriting user content.
4794
+ ### Extension API (`inject-hook`, `inject-rule`)
4795
+ `inject_section_cli.py` provides a stable marker-based API for injecting content into `CLAUDE.md`, `constitution.md`, or `ARCHITECTURE.md` without overwriting user content. `inject_hook_cli.py` injects hooks into `settings.json` with `_source` tags — supports both local files and HTTPS URLs (cached in `~/.softspark/ai-toolkit/hooks/external/`, auto-refreshed on `update`).
4796
4796
 
4797
4797
  ### Manifest Install (`--modules`, `--auto-detect`)
4798
4798
  `manifest.json` defines all installable components as named modules. Install individual modules with `ai-toolkit install --modules <name>` or enable auto-detection to select language rules based on files found in the project.
@@ -5167,10 +5167,10 @@ Usage: ai-toolkit <command> [options]
5167
5167
 
5168
5168
  | Command | Description |
5169
5169
  |---------|-------------|
5170
- | `add-rule <rule.md> [name]` | Register rule in `~/.softspark/ai-toolkit/rules/` — auto-applied on every `update` |
5170
+ | `add-rule <rule.md\|url> [name]` | Register rule in `~/.softspark/ai-toolkit/rules/` — auto-applied on every `update` |
5171
5171
  | `remove-rule <name> [dir]` | Unregister rule and remove its block from `CLAUDE.md` |
5172
- | `inject-hook <file.json>` | Inject external hooks into settings.json (idempotent, `_source` tagged) |
5173
- | `remove-hook <name>` | Remove injected hooks by source name |
5172
+ | `inject-hook <file.json\|url> [name]` | Inject external hooks (file or URL) into settings.json (idempotent, `_source` tagged, URL hooks auto-refresh on update) |
5173
+ | `remove-hook <name>` | Remove injected hooks by source name (also unregisters URL source if present) |
5174
5174
 
5175
5175
  ## MCP Management
5176
5176
 
@@ -5380,6 +5380,10 @@ This means Claude-only events such as `TaskCompleted`, `TeammateIdle`,
5380
5380
  `SubagentStart`, `SubagentStop`, `PreCompact`, `SessionEnd`, and
5381
5381
  `Notification` are not available in `.codex/hooks.json`.
5382
5382
 
5383
+ `inject-hook` automatically propagates Codex-compatible events to
5384
+ `~/.codex/hooks.json` (global layer). Non-Codex events are silently skipped.
5385
+ `remove-hook` cleans both Claude and Codex targets.
5386
+
5383
5387
  ## Behavioral Limits
5384
5388
 
5385
5389
  Codex wrappers preserve workflow intent, but not every Claude runtime behavior
@@ -6534,9 +6538,9 @@ title: "Extension API Reference"
6534
6538
  category: reference
6535
6539
  service: ai-toolkit
6536
6540
  tags: [extension-api, inject-rule, inject-hook, mcp-templates, integration, editors]
6537
- version: "1.3.9"
6541
+ version: "1.4.0"
6538
6542
  created: "2026-04-07"
6539
- last_updated: "2026-04-12"
6543
+ last_updated: "2026-04-15"
6540
6544
  description: "Reference for ai-toolkit's extension API: inject-rule, inject-hook, remove-rule, remove-hook, and editor-aware MCP template management."
6541
6545
  ---
6542
6546
 
@@ -6554,9 +6558,9 @@ This design is intentional: ai-toolkit is a generic toolkit. Consumers (MCP serv
6554
6558
  |---------|-------------|-----------|------------|
6555
6559
  | `inject-rule <file.md>` | `~/.claude/CLAUDE.md` | HTML comment markers (`<!-- TOOLKIT:name -->`) | Yes |
6556
6560
  | `remove-rule <name>` | `~/.claude/CLAUDE.md` | Strip markers by block name | Yes |
6557
- | `inject-hook <file.json>` | `~/.claude/settings.json` | JSON `_source` tag per entry | Yes |
6558
- | `remove-hook <name>` | `~/.claude/settings.json` | Strip all entries with matching `_source` | Yes |
6559
- | `add-rule <file.md>` | `~/.softspark/ai-toolkit/rules/` | File copy + re-inject all rules on next `update` | Yes |
6561
+ | `inject-hook <file.json\|url> [name]` | `~/.claude/settings.json` | JSON `_source` tag per entry, URL cached + registered | Yes |
6562
+ | `remove-hook <name>` | `~/.claude/settings.json` | Strip all entries with matching `_source`, unregister URL source | Yes |
6563
+ | `add-rule <file.md\|url>` | `~/.softspark/ai-toolkit/rules/` | File copy + re-inject all rules on next `update` | Yes |
6560
6564
  | `mcp add <name...>` | `.mcp.json` | Merge `mcpServers` block from template | Yes |
6561
6565
  | `mcp install --editor <name...>` | Native editor MCP config | Render canonical template into editor format | Yes |
6562
6566
 
@@ -6591,13 +6595,20 @@ The argument is the block name (file stem used during `inject-rule`). If the blo
6591
6595
 
6592
6596
  ## inject-hook
6593
6597
 
6594
- Injects hook entries from a JSON file into `~/.claude/settings.json`. Every injected entry is tagged with `"_source": "<source-name>"` where the source name is derived from the filename stem.
6598
+ Injects hook entries from a JSON file or HTTPS URL into `~/.claude/settings.json`. Every injected entry is tagged with `"_source": "<source-name>"` where the source name is derived from the filename stem or URL last segment.
6595
6599
 
6596
6600
  ```bash
6601
+ # From local file
6597
6602
  npx @softspark/ai-toolkit inject-hook ./my-tool-hooks.json
6603
+
6604
+ # From URL (HTTPS only) — cached locally, auto-refreshed on update
6605
+ npx @softspark/ai-toolkit inject-hook https://example.com/my-tool-hooks.json
6606
+
6607
+ # With explicit source name
6608
+ npx @softspark/ai-toolkit inject-hook https://example.com/hooks.json my-tool-hooks
6598
6609
  ```
6599
6610
 
6600
- **Implementation:** `scripts/inject_hook_cli.py`.
6611
+ **Implementation:** `scripts/inject_hook_cli.py`, `scripts/hook_sources.py`, `scripts/url_fetch.py`.
6601
6612
 
6602
6613
  **Input format:**
6603
6614
  ```json
@@ -6619,15 +6630,19 @@ npx @softspark/ai-toolkit inject-hook ./my-tool-hooks.json
6619
6630
  }
6620
6631
  ```
6621
6632
 
6622
- **Source name derivation:** `my-tool-hooks.json` → source name `"my-tool-hooks"`. All entries are tagged `"_source": "my-tool-hooks"` in settings.json.
6633
+ **Source name derivation:** `my-tool-hooks.json` → source name `"my-tool-hooks"`. For URLs: `https://example.com/path/my-tool-hooks.json` → `"my-tool-hooks"`. All entries are tagged `"_source": "my-tool-hooks"` in settings.json.
6634
+
6635
+ **URL support:** When an HTTPS URL is provided, the JSON is fetched, validated, cached in `~/.softspark/ai-toolkit/hooks/external/<name>.json`, and registered in `sources.json`. On every `ai-toolkit update`, URL-sourced hooks are re-fetched and re-injected automatically. If the fetch fails during update, the cached version is used.
6623
6636
 
6624
6637
  **Idempotency:** Re-running strips all existing entries with the same source name, then appends the new ones. No duplicates accumulate.
6625
6638
 
6626
- **Safety:** Entries tagged `"_source": "ai-toolkit"` are never modified or removed by this command. External tools cannot affect the toolkit's own hooks.
6639
+ **Safety:** Entries tagged `"_source": "ai-toolkit"` are never modified or removed by this command. External tools cannot affect the toolkit's own hooks. Only HTTPS URLs are accepted.
6640
+
6641
+ **Codex propagation:** Codex-compatible events (`SessionStart`, `PreToolUse`, `PostToolUse`, `UserPromptSubmit`, `Stop`) are automatically propagated to `~/.codex/hooks.json`. Non-Codex events are silently skipped. No extra flags needed.
6627
6642
 
6628
6643
  ## remove-hook
6629
6644
 
6630
- Strips all hook entries from `~/.claude/settings.json` that carry a given `_source` tag.
6645
+ Strips all hook entries from `~/.claude/settings.json` that carry a given `_source` tag. If the hook was URL-sourced, also unregisters the URL from `sources.json` and removes the cached file.
6631
6646
 
6632
6647
  ```bash
6633
6648
  npx @softspark/ai-toolkit remove-hook my-tool-hooks
@@ -6671,14 +6686,14 @@ When `install` runs with `--scope project`, ai-toolkit also updates `.mcp.json`
6671
6686
  │ Public Extension API: │
6672
6687
  │ inject-rule <file.md> → CLAUDE.md │
6673
6688
  │ remove-rule <name> → CLAUDE.md │
6674
- │ inject-hook <file.json> → settings.json │
6689
+ │ inject-hook <file|url> → settings.json │
6675
6690
  │ remove-hook <name> → settings.json │
6676
- │ add-rule <file.md> → rules/ registry │
6691
+ │ add-rule <file|url> → rules/ registry │
6677
6692
  │ mcp add <template> → .mcp.json │
6678
6693
  │ mcp install <template> → editor-native MCP │
6679
6694
  │ │
6680
6695
  │ Idempotent: markers (rules) / _source tags (hooks) │
6681
- ai-toolkit NEVER calls external services
6696
+ URL sources: cached + auto-refreshed on update
6682
6697
  └──────────────────────────────────────────────────────┘
6683
6698
 
6684
6699
  │ uses API
package/manifest.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.3.1",
2
+ "version": "2.4.1",
3
3
  "components": {
4
4
  "agents": {
5
5
  "description": "44 specialized agents (orchestrator, backend, frontend, security, devops, etc.)",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softspark/ai-toolkit",
3
- "version": "2.3.1",
3
+ "version": "2.4.1",
4
4
  "description": "Professional-grade AI coding toolkit: 92 skills, 44 agents, multi-platform support (Claude, Cursor, Windsurf, Copilot, Gemini, Cline, Roo Code, Aider, Augment, Google Antigravity, Codex CLI), machine-enforced safety constitution, persona presets, skill security auditor, expanded lifecycle hooks, 11 plugin packs, and benchmark tooling.",
5
5
  "keywords": [
6
6
  "claude",
@@ -299,14 +299,28 @@ def _find_cached_npm(cache_dir: Path) -> Path | None:
299
299
 
300
300
 
301
301
  def _extract_tarball(tarball: Path, dest: Path) -> None:
302
- """Extract npm tarball (which has a package/ prefix) to dest."""
302
+ """Extract npm tarball (which has a package/ prefix) to dest.
303
+
304
+ Validates that extracted paths stay within dest to prevent path traversal.
305
+ Rejects symlinks and absolute paths.
306
+ """
307
+ dest_resolved = dest.resolve()
303
308
  with tarfile.open(tarball, "r:gz") as tf:
304
309
  for member in tf.getmembers():
305
310
  # npm tarballs have a "package/" prefix
306
- if member.name.startswith("package/"):
307
- member.name = member.name[len("package/"):]
308
- if member.name: # skip empty (the "package/" dir itself)
309
- tf.extract(member, dest)
311
+ if not member.name.startswith("package/"):
312
+ continue
313
+ member.name = member.name[len("package/"):]
314
+ if not member.name: # skip empty (the "package/" dir itself)
315
+ continue
316
+ # Reject symlinks and absolute paths
317
+ if member.issym() or member.islnk() or member.name.startswith("/"):
318
+ continue
319
+ # Path traversal protection
320
+ target = (dest / member.name).resolve()
321
+ if not str(target).startswith(str(dest_resolved)):
322
+ continue
323
+ tf.extract(member, dest)
310
324
 
311
325
 
312
326
  def _extract_version_from_tarball(filename: str, package_name: str) -> str:
@@ -332,6 +346,10 @@ def _resolve_git(
332
346
  ) -> BaseConfig:
333
347
  """Resolve from git URL (git+https://...)."""
334
348
  url = source.removeprefix("git+")
349
+ if not url.startswith("https://"):
350
+ raise ConfigResolverError(
351
+ f"Only HTTPS git URLs are supported (got: {url.split('://')[0]}://)"
352
+ )
335
353
  cache_key = hashlib.sha256(url.encode()).hexdigest()[:16]
336
354
  cache_dir = _cache_root() / "git" / cache_key
337
355