amkernel 0.1.0__py3-none-any.whl

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.
amkernel/cli.py ADDED
@@ -0,0 +1,172 @@
1
+ """AMKernel server and distribution command-line interface."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import asyncio
7
+ import json
8
+ import sys
9
+ from collections.abc import Sequence
10
+ from dataclasses import replace
11
+ from pathlib import Path
12
+
13
+ from amkernel import __version__
14
+ from amkernel.distribution import Client, DistributionError, DistributionManager, ProjectStatus
15
+ from amkernel.server import serve_stdio
16
+
17
+
18
+ def build_parser() -> argparse.ArgumentParser:
19
+ """Build the command-line parser."""
20
+ parser = argparse.ArgumentParser(
21
+ prog="amkernel",
22
+ description="Install, verify, and run AMKernel for Coding Agents.",
23
+ )
24
+ parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
25
+ subparsers = parser.add_subparsers(dest="command")
26
+ for name in ("install", "status", "doctor", "update"):
27
+ command = subparsers.add_parser(name)
28
+ command.add_argument("--client", choices=("auto", "omp", "pi", "codex"), default="auto")
29
+ command.add_argument("--project", type=Path, default=Path.cwd())
30
+ command.add_argument("--json", action="store_true")
31
+ if name in {"install", "update"}:
32
+ command.add_argument("--scope", choices=("project",))
33
+ command.add_argument("--yes", action="store_true")
34
+ uninstall = subparsers.add_parser("uninstall")
35
+ uninstall.add_argument("--client", choices=("auto", "omp", "pi", "codex"), default="auto")
36
+ uninstall.add_argument("--project", type=Path, default=Path.cwd())
37
+ uninstall.add_argument("--scope", choices=("project",))
38
+ uninstall.add_argument("--global", dest="global_scope", action="store_true")
39
+ uninstall.add_argument("--yes", action="store_true")
40
+ uninstall.add_argument("--json", action="store_true")
41
+ return parser
42
+
43
+
44
+ def _confirm(message: str, assumed: bool) -> None:
45
+ if assumed:
46
+ return
47
+ if not sys.stdin.isatty():
48
+ raise DistributionError("confirmation required; rerun with --yes")
49
+ answer = input(f"{message} [y/N] ").strip().lower()
50
+ if answer not in {"y", "yes"}:
51
+ raise DistributionError("cancelled")
52
+
53
+
54
+ def _print_status(status: ProjectStatus, as_json: bool) -> None:
55
+ if as_json:
56
+ print(json.dumps(status.to_dict(), indent=2, sort_keys=True))
57
+ return
58
+ print(f"Runtime installed: {'yes' if status.runtime_installed else 'no'}")
59
+ print(f"Skill installed: {'yes' if status.skill_installed else 'no'}")
60
+ print(f"MCP configured: {'yes' if status.mcp_configured else 'no'}")
61
+ print(f"Project: {status.project}")
62
+ if status.client:
63
+ print(f"Client: {status.client}")
64
+ if status.runtime_version:
65
+ print(f"Runtime version: {status.runtime_version}")
66
+ if status.skill_version:
67
+ print(f"Skill version: {status.skill_version}")
68
+ if status.known_project_references:
69
+ print("Known project references:")
70
+ for project in status.known_project_references:
71
+ print(f"- {project}")
72
+ if status.references_truncated:
73
+ print("- additional references omitted")
74
+ if status.detail:
75
+ print(f"Next action: {status.detail}")
76
+ elif status.restart_required:
77
+ print("Next action: restart the Coding Agent to load AMKernel")
78
+
79
+
80
+ def _client(manager: DistributionManager, requested: str) -> Client:
81
+ return manager.select_client(requested)
82
+
83
+
84
+ def _require_automation_contract(args: argparse.Namespace) -> None:
85
+ if not getattr(args, "yes", False):
86
+ return
87
+ if not args.json:
88
+ raise DistributionError("automated mutations require --json")
89
+ if args.command == "uninstall" and args.global_scope:
90
+ return
91
+ if args.client == "auto":
92
+ raise DistributionError("automated project mutations require an explicit --client")
93
+ if args.scope != "project":
94
+ raise DistributionError("automated project mutations require --scope project")
95
+
96
+
97
+ def _print_plan(manager: DistributionManager, client: Client) -> None:
98
+ print("Planned changes:", file=sys.stderr)
99
+ for change in manager.planned_changes(client):
100
+ print(f"- {change}", file=sys.stderr)
101
+
102
+
103
+ def _run_distribution(args: argparse.Namespace) -> int:
104
+ manager = DistributionManager(args.project)
105
+ _require_automation_contract(args)
106
+ if args.command == "install":
107
+ client = _client(manager, args.client)
108
+ _print_plan(manager, client)
109
+ _confirm(
110
+ f"Install AMKernel globally and enable it for {manager.project}?",
111
+ args.yes,
112
+ )
113
+ status = manager.install(client)
114
+ elif args.command == "status":
115
+ status_client = None if args.client == "auto" else _client(manager, args.client)
116
+ status = manager.status(status_client)
117
+ elif args.command == "doctor":
118
+ status = manager.doctor(_client(manager, args.client))
119
+ elif args.command == "update":
120
+ client = _client(manager, args.client)
121
+ _print_plan(manager, client)
122
+ _confirm("Update the global AMKernel Runtime used by every enabled project?", args.yes)
123
+ status = manager.update(client)
124
+ elif args.command == "uninstall":
125
+ if args.global_scope:
126
+ references = sorted(manager.known_projects())
127
+ retained_references = tuple(references[:100])
128
+ reference_note = (
129
+ " No enabled project references are registered."
130
+ if not references
131
+ else " Registered project references will stop working: "
132
+ + ", ".join(references[:20])
133
+ )
134
+ print(reference_note.strip(), file=sys.stderr)
135
+ _confirm("Remove the global AMKernel Runtime?" + reference_note, args.yes)
136
+ manager.uninstall_global()
137
+ status = replace(
138
+ manager.status(None),
139
+ known_project_references=retained_references,
140
+ references_truncated=len(references) > len(retained_references),
141
+ )
142
+ else:
143
+ _confirm(f"Disable AMKernel in {manager.project}?", args.yes)
144
+ status = manager.uninstall_project(_client(manager, args.client))
145
+ else:
146
+ raise AssertionError(f"unexpected command: {args.command}")
147
+ _print_status(status, args.json)
148
+ return 0 if not status.detail or args.command == "uninstall" else 1
149
+
150
+
151
+ def main(argv: Sequence[str] | None = None) -> int:
152
+ """Run the MCP server by default, or an explicit distribution command."""
153
+ parser = build_parser()
154
+ args = parser.parse_args(argv)
155
+ if args.command:
156
+ try:
157
+ return _run_distribution(args)
158
+ except DistributionError as error:
159
+ if getattr(args, "json", False):
160
+ message = str(error)[:4096]
161
+ print(
162
+ json.dumps(
163
+ {"ok": False, "error": {"type": "DistributionError", "message": message}}
164
+ )
165
+ )
166
+ else:
167
+ print(f"amkernel: {error}", file=sys.stderr)
168
+ return 2
169
+ try:
170
+ return asyncio.run(serve_stdio())
171
+ except KeyboardInterrupt:
172
+ return 130