Merge the manifest's hooks into its target settings file. Existing entries whose command contains the manifest's `command_prefix` are removed first, so re-running syncs the target to the manifest (picking up command/event changes). Non-matching hooks are preserved untouched.
(manifest_path: &Path)
| 93 | /// removed first, so re-running syncs the target to the manifest (picking up |
| 94 | /// command/event changes). Non-matching hooks are preserved untouched. |
| 95 | pub fn install_from_manifest(manifest_path: &Path) -> AgentResult<ManifestOutcome> { |
| 96 | let manifest = read_manifest(manifest_path)?; |
| 97 | let target = expand_target(&manifest.target)?; |
| 98 | |
| 99 | let mut root = read_json_object(&target)?; |
| 100 | |
| 101 | // Merge-only manifests (no hooks) must not create an empty hooks key. |
| 102 | let mut removed = 0; |
| 103 | let mut added = 0; |
| 104 | if !manifest.hooks.is_empty() { |
| 105 | let entry = root |
| 106 | .entry(manifest.hooks_key.clone()) |
| 107 | .or_insert_with(|| Value::Object(Map::new())); |
| 108 | if !entry.is_object() { |
| 109 | *entry = Value::Object(Map::new()); |
| 110 | } |
| 111 | let hooks = entry |
| 112 | .as_object_mut() |
| 113 | .expect("entry was just ensured to be an object"); |
| 114 | |
| 115 | // Remove our previous entries first so command/event changes take effect. |
| 116 | removed = remove_prefixed(hooks, &manifest.command_prefix); |
| 117 | |
| 118 | for (event, entries) in &manifest.hooks { |
| 119 | let Some(src_entries) = entries.as_array() else { |
| 120 | continue; |
| 121 | }; |
| 122 | let dst = hooks |
| 123 | .entry(event.clone()) |
| 124 | .or_insert_with(|| Value::Array(Vec::new())); |
| 125 | let Some(dst_entries) = dst.as_array_mut() else { |
| 126 | continue; |
| 127 | }; |
| 128 | for entry in src_entries { |
| 129 | dst_entries.push(entry.clone()); |
| 130 | added += count_commands(entry); |
| 131 | } |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | // Deep-merge any extra non-hook settings (e.g. Claude's permissions.deny). |
| 136 | deep_merge(&mut root, &manifest.merge); |
| 137 | |
| 138 | write_json_object(&target, &root)?; |
| 139 | |
| 140 | Ok(ManifestOutcome { |
| 141 | target, |
| 142 | added, |
| 143 | removed, |
| 144 | }) |
| 145 | } |
| 146 | |
| 147 | /// Remove the manifest's hooks from its target settings file. |
| 148 | /// |