kitctl 0.0.1__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.
kitctl/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """Kit: inventory and manage agent tooling through deterministic loadouts."""
2
+
3
+ __version__ = "0.0.1"
kitctl/__main__.py ADDED
@@ -0,0 +1,3 @@
1
+ from kitctl.cli import main
2
+
3
+ raise SystemExit(main())
kitctl/cli.py ADDED
@@ -0,0 +1,44 @@
1
+ """CLI for Kit's initial read-only release."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ from kitctl import __version__
11
+ from kitctl.inventory import inventory
12
+
13
+
14
+ def main(argv: list[str] | None = None) -> int:
15
+ parser = argparse.ArgumentParser(
16
+ prog="kitctl", description="Kit — inventory and manage agent tooling."
17
+ )
18
+ parser.add_argument("--version", action="version", version=f"kitctl {__version__}")
19
+ commands = parser.add_subparsers(dest="command", required=True)
20
+ scan = commands.add_parser("inventory", help="Read-only discovery in explicit roots.")
21
+ scan.add_argument(
22
+ "--root",
23
+ action="append",
24
+ type=Path,
25
+ dest="roots",
26
+ metavar="PATH",
27
+ help="Repository or home directory to inspect; repeatable. Defaults to current directory.",
28
+ )
29
+ scan.add_argument("--json", action="store_true", help="Print metadata and hashes as JSON.")
30
+ args = parser.parse_args(argv)
31
+ try:
32
+ report = inventory(args.roots or [Path.cwd()])
33
+ except (OSError, ValueError) as error:
34
+ print(f"kitctl: {error}", file=sys.stderr)
35
+ return 2
36
+ if args.json:
37
+ print(json.dumps(report, indent=2))
38
+ else:
39
+ for entry in report["entries"]:
40
+ print(f"{entry['kind']:6} {entry['name']} {entry['path']}")
41
+ print(f"{len(report['entries'])} items; no files changed.")
42
+ for warning in report["warnings"]:
43
+ print(f"warning: {warning}", file=sys.stderr)
44
+ return 1 if report["warnings"] else 0
kitctl/inventory.py ADDED
@@ -0,0 +1,129 @@
1
+ """Read-only, explicitly scoped discovery. Never serialize configuration values."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import os
7
+ from dataclasses import asdict, dataclass
8
+ from pathlib import Path
9
+ from typing import TypedDict
10
+
11
+ CONFIG_PATHS = (
12
+ ".claude/settings.json",
13
+ ".claude/settings.local.json",
14
+ ".claude.json",
15
+ ".mcp.json",
16
+ ".codex/config.toml",
17
+ ".codex/hooks.json",
18
+ "CLAUDE.md",
19
+ "AGENTS.md",
20
+ "kit.yaml",
21
+ "kit.lock",
22
+ )
23
+ SKILL_PATHS = (".claude/skills", ".agents/skills", ".codex/skills")
24
+
25
+
26
+ @dataclass(frozen=True)
27
+ class Entry:
28
+ kind: str
29
+ name: str
30
+ root: str
31
+ path: str
32
+ content_hash: str
33
+
34
+
35
+ class Report(TypedDict):
36
+ schema_version: int
37
+ entries: list[dict[str, str]]
38
+ warnings: list[str]
39
+
40
+
41
+ def _safe_file(path: Path, root: Path) -> bool:
42
+ """Skip links, including a link in a parent directory, and special files."""
43
+ relative = path.relative_to(root)
44
+ current = root
45
+ for part in relative.parts:
46
+ current /= part
47
+ if current.is_symlink():
48
+ return False
49
+ return path.is_file()
50
+
51
+
52
+ def _file_hash(path: Path) -> str:
53
+ digest = hashlib.sha256()
54
+ with path.open("rb") as stream:
55
+ for chunk in iter(lambda: stream.read(65536), b""):
56
+ digest.update(chunk)
57
+ return digest.hexdigest()
58
+
59
+
60
+ def _skill_hash(directory: Path, root: Path) -> str:
61
+ """Hash relative names and bytes of all regular skill assets deterministically."""
62
+ digest = hashlib.sha256()
63
+
64
+ def fail(error: OSError) -> None:
65
+ raise error
66
+
67
+ for current, dirs, files in os.walk(directory, followlinks=False, onerror=fail):
68
+ dirs[:] = sorted(
69
+ name for name in dirs if name != ".git" and not (Path(current) / name).is_symlink()
70
+ )
71
+ for name in sorted(files):
72
+ path = Path(current) / name
73
+ if _safe_file(path, root):
74
+ digest.update(path.relative_to(directory).as_posix().encode("utf-8"))
75
+ digest.update(b"\0")
76
+ digest.update(_file_hash(path).encode("ascii"))
77
+ digest.update(b"\0")
78
+ return digest.hexdigest()
79
+
80
+
81
+ def inventory(roots: list[Path]) -> Report:
82
+ entries: list[Entry] = []
83
+ warnings: list[str] = []
84
+ for root in sorted({path.expanduser().resolve() for path in roots}):
85
+ if not root.is_dir():
86
+ raise ValueError(f"Inventory root is not a directory: {root}")
87
+ for relative in CONFIG_PATHS:
88
+ path = root / relative
89
+ try:
90
+ if _safe_file(path, root):
91
+ entries.append(
92
+ Entry("config", relative, str(root), str(path), _file_hash(path))
93
+ )
94
+ elif path.exists() or path.is_symlink():
95
+ warnings.append(f"Skipped link or non-regular file: {path}")
96
+ except OSError:
97
+ warnings.append(f"Cannot read: {path}")
98
+ for relative in SKILL_PATHS:
99
+ directory = root / relative
100
+ try:
101
+ if directory.is_symlink() or any(
102
+ parent.is_symlink() for parent in directory.parents if parent != root
103
+ ):
104
+ warnings.append(f"Skipped linked skill directory: {directory}")
105
+ continue
106
+ if not directory.is_dir():
107
+ continue
108
+ for skill in sorted(directory.iterdir()):
109
+ manifest = skill / "SKILL.md"
110
+ if skill.is_symlink():
111
+ warnings.append(f"Skipped linked skill: {skill}")
112
+ elif manifest.is_symlink():
113
+ warnings.append(f"Skipped linked skill manifest: {manifest}")
114
+ elif _safe_file(manifest, root):
115
+ try:
116
+ entries.append(
117
+ Entry(
118
+ "skill",
119
+ skill.name,
120
+ str(root),
121
+ str(skill),
122
+ _skill_hash(skill, root),
123
+ )
124
+ )
125
+ except OSError:
126
+ warnings.append(f"Cannot read complete skill: {skill}")
127
+ except OSError:
128
+ warnings.append(f"Cannot read: {directory}")
129
+ return {"schema_version": 1, "entries": [asdict(e) for e in entries], "warnings": warnings}
@@ -0,0 +1,96 @@
1
+ Metadata-Version: 2.5
2
+ Name: kitctl
3
+ Version: 0.0.1
4
+ Summary: Inventory and manage agent skills, MCP connectors, hooks, and loadouts.
5
+ Project-URL: Repository, https://github.com/ConnorBritain/kitctl
6
+ Project-URL: Issues, https://github.com/ConnorBritain/kitctl/issues
7
+ Author: Connor England
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: agents,claude,codex,mcp,skills
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Requires-Python: >=3.11
17
+ Description-Content-Type: text/markdown
18
+
19
+ # Kit (`kitctl`)
20
+
21
+ Kit inventories the skills, MCP configuration, hooks, and plugins used by coding
22
+ agents. The planned compiler manages named **loadouts** from a separate private
23
+ warehouse and writes each harness's native configuration with an ownership ledger.
24
+ The planned default warehouse location is `~/kits`; setup will ask which parent
25
+ directories contain your repositories.
26
+
27
+ **0.0.1 is an early, read-only release.** It discovers skill folders and known
28
+ configuration files in explicitly selected roots, computes SHA-256 hashes, and
29
+ prints text or JSON. It does not yet parse connectors, count usage, compile
30
+ loadouts, reconcile changes, or modify agent configuration.
31
+
32
+ ## Run
33
+
34
+ Python 3.11+:
35
+
36
+ ```sh
37
+ # From a checkout (works before the PyPI release):
38
+ uv run kitctl --version
39
+ uv run kitctl inventory --root /path/to/repo --json
40
+
41
+ # After publication to PyPI:
42
+ uvx kitctl --version
43
+ uv tool install kitctl
44
+ ```
45
+
46
+ Node 20+ with [uv installed](https://docs.astral.sh/uv/getting-started/installation/):
47
+
48
+ ```sh
49
+ # After publication to npm:
50
+ npx kitctl --version
51
+ npx kitctl inventory --root /path/to/repo --json
52
+ ```
53
+
54
+ The npm package is a thin launcher around the bundled Python wheel from the same
55
+ release. It uses `uvx` with that local wheel; it does not require a PyPI release,
56
+ run install scripts, or ship a `kit` binary. uv manages Python if needed.
57
+
58
+ `inventory` defaults to the current directory. Repeat `--root` to inspect several
59
+ repositories, or explicitly pass your home directory to include user config.
60
+ There is no recursive search through unrelated repositories. Symlinked skills and
61
+ configuration are skipped with warnings. JSON includes file paths and hashes,
62
+ never configuration contents. Exit codes: 0 complete, 1 partial due to skipped
63
+ items, 2 invalid arguments or roots. Reports may reveal private paths; keep them
64
+ out of public source control.
65
+
66
+ ## Develop
67
+
68
+ ```sh
69
+ uv sync --locked
70
+ uv run ruff check .
71
+ uv run ruff format --check .
72
+ uv run mypy
73
+ uv run pytest
74
+ uv build
75
+ uv run python scripts/prepare_npm.py
76
+ npm --prefix npm test
77
+ cd npm
78
+ npm pack --dry-run
79
+ ```
80
+
81
+ Tests use throwaway HOME, CODEX_HOME, CLAUDE_CONFIG_DIR, and repository fixtures.
82
+ CI runs on Linux and Windows with Python 3.11 and 3.14. A local pre-commit
83
+ configuration and CI check reject common token formats and private keys. Install
84
+ the hook with `uvx pre-commit install` if desired; this basic check is not a
85
+ comprehensive secret scanner.
86
+
87
+ See [PLAN.md](PLAN.md), [design](docs/design.md),
88
+ [harness reference](docs/harness-reference.md), and [publishing](docs/publishing.md).
89
+
90
+ ## Naming
91
+
92
+ Kit ships only the `kitctl` command. AWS's Kubernetes Iteration Toolkit and
93
+ Gravwell also use a `kitctl` binary; installing those tools alongside Kit can
94
+ cause PATH collisions. This project is independent of those projects and KitOps.
95
+
96
+ MIT License. The code repository and private warehouse are deliberately separate.
@@ -0,0 +1,9 @@
1
+ kitctl/__init__.py,sha256=rSm9eIUxoPces7GkCow9GLqAJwoHrEdpF3o8zUE5wuA,101
2
+ kitctl/__main__.py,sha256=YyfWKomGWqYwcoPkocKXKYrrtWoDEImggBnVUHFUYuk,54
3
+ kitctl/cli.py,sha256=EzF9-Z1nShrmsAv-Nfbdd7I4xlUeTWiibCqeoxiLpRI,1581
4
+ kitctl/inventory.py,sha256=AIbAXOm81z8ODsALhfRVaP87YjlG82gPN_l3KxYk1Ps,4516
5
+ kitctl-0.0.1.dist-info/METADATA,sha256=8sPUv3CD6BvhOigGSg1juyjheP1iPNsoQ9SkiAu5kL4,3547
6
+ kitctl-0.0.1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
7
+ kitctl-0.0.1.dist-info/entry_points.txt,sha256=ebID8O6jX7VSeb-McMCyAKvmRomQGLLWqPnC1AACB_w,43
8
+ kitctl-0.0.1.dist-info/licenses/LICENSE,sha256=DNjvGPlBeV_eLjSZIVk9TltdnjU6k0nfD0v-iBY_YOM,1071
9
+ kitctl-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ kitctl = kitctl.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Connor England
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.