130 lines
3.9 KiB
Python
130 lines
3.9 KiB
Python
"""GitHub Copilot CLI native user-hook integration."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from typing import List
|
|
|
|
from .base import AgentPlugin, InstallConfig, PluginStatus
|
|
from .copilot_contract import (
|
|
AGENT_KEY,
|
|
CopilotSources,
|
|
RenderedDocument,
|
|
RuntimeContract,
|
|
build_handler,
|
|
remove_managed_hooks,
|
|
render_document,
|
|
runtime_contracts,
|
|
)
|
|
from .copilot_installation import (
|
|
commit_copilot_installation,
|
|
commit_copilot_uninstall,
|
|
preflight_copilot_installation,
|
|
prepare_copilot_installation,
|
|
prepare_copilot_uninstall,
|
|
)
|
|
from .copilot_io import read_sources, runtime_files_exist
|
|
|
|
|
|
def _render_installation(
|
|
sources: CopilotSources,
|
|
guard_path: str,
|
|
audit_path: str,
|
|
) -> List[RenderedDocument]:
|
|
user_hooks = remove_managed_hooks(sources.user.hooks)
|
|
user_hooks["preToolUse"] = [
|
|
*user_hooks.get("preToolUse", []),
|
|
build_handler(guard_path),
|
|
]
|
|
user_hooks["postToolUse"] = [
|
|
*user_hooks.get("postToolUse", []),
|
|
build_handler(audit_path),
|
|
]
|
|
user_hooks["postToolUseFailure"] = [
|
|
*user_hooks.get("postToolUseFailure", []),
|
|
build_handler(audit_path),
|
|
]
|
|
rendered = [render_document(sources.user, user_hooks)]
|
|
if sources.legacy is not None:
|
|
rendered.append(render_document(
|
|
sources.legacy,
|
|
remove_managed_hooks(sources.legacy.hooks),
|
|
))
|
|
return rendered
|
|
|
|
|
|
def _render_uninstall(
|
|
sources: CopilotSources,
|
|
agent_id: str,
|
|
) -> List[RenderedDocument]:
|
|
rendered = [render_document(
|
|
sources.user,
|
|
remove_managed_hooks(sources.user.hooks, agent_id),
|
|
)]
|
|
if sources.legacy is not None:
|
|
rendered.append(render_document(
|
|
sources.legacy,
|
|
remove_managed_hooks(sources.legacy.hooks, agent_id),
|
|
))
|
|
return rendered
|
|
|
|
|
|
def _merged_contracts(sources: CopilotSources) -> List[RuntimeContract]:
|
|
contracts = runtime_contracts(sources.user.hooks)
|
|
if sources.legacy is not None:
|
|
contracts.extend(runtime_contracts(sources.legacy.hooks))
|
|
unique = {
|
|
os.path.normcase(contract.agent_id): contract
|
|
for contract in contracts
|
|
}
|
|
return list(unique.values())
|
|
|
|
|
|
class CopilotPlugin(AgentPlugin):
|
|
"""Install Elydora into GitHub Copilot CLI's global user hooks."""
|
|
|
|
manages_guard_runtime = True
|
|
|
|
def preflight_install(self, config: InstallConfig) -> None:
|
|
sources = read_sources()
|
|
preflight_copilot_installation(config, sources)
|
|
|
|
def install(self, config: InstallConfig) -> None:
|
|
sources = read_sources()
|
|
paths = preflight_copilot_installation(config, sources)
|
|
rendered = _render_installation(
|
|
sources,
|
|
paths.guard_path,
|
|
paths.audit_path,
|
|
)
|
|
prepared = prepare_copilot_installation(config, sources, rendered)
|
|
commit_copilot_installation(prepared)
|
|
print(f"GitHub Copilot CLI hooks: {sources.user.file_path}")
|
|
print(
|
|
"GitHub Copilot CLI: restart active sessions to load updated hooks."
|
|
)
|
|
|
|
def uninstall(self, agent_id: str = "") -> None:
|
|
sources = read_sources()
|
|
rendered = _render_uninstall(sources, agent_id)
|
|
commit_copilot_uninstall(prepare_copilot_uninstall(rendered))
|
|
|
|
def status(self) -> PluginStatus:
|
|
sources = read_sources()
|
|
contracts = _merged_contracts(sources)
|
|
configured = sources.disabled_by is None and bool(contracts)
|
|
installed = configured and runtime_files_exist(contracts)
|
|
details = (
|
|
f"Config: {sources.user.file_path}"
|
|
if installed
|
|
else (
|
|
f"Configured at {sources.user.file_path}; "
|
|
"managed contract incomplete"
|
|
)
|
|
)
|
|
return PluginStatus(
|
|
installed=installed,
|
|
agent=AGENT_KEY,
|
|
details=details if contracts else "Not installed",
|
|
)
|