pytk-ai 0.1.0__tar.gz

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.
Files changed (44) hide show
  1. pytk_ai-0.1.0/PKG-INFO +64 -0
  2. pytk_ai-0.1.0/README.md +45 -0
  3. pytk_ai-0.1.0/pyproject.toml +39 -0
  4. pytk_ai-0.1.0/setup.cfg +4 -0
  5. pytk_ai-0.1.0/src/pytk_ai/__init__.py +13 -0
  6. pytk_ai-0.1.0/src/pytk_ai/__main__.py +4 -0
  7. pytk_ai-0.1.0/src/pytk_ai/cli.py +146 -0
  8. pytk_ai-0.1.0/src/pytk_ai/config.py +13 -0
  9. pytk_ai-0.1.0/src/pytk_ai/data/__init__.py +1 -0
  10. pytk_ai-0.1.0/src/pytk_ai/data/rules.json +669 -0
  11. pytk_ai-0.1.0/src/pytk_ai/filters/__init__.py +77 -0
  12. pytk_ai-0.1.0/src/pytk_ai/filters/base.py +89 -0
  13. pytk_ai-0.1.0/src/pytk_ai/filters/build.py +470 -0
  14. pytk_ai-0.1.0/src/pytk_ai/filters/generic.py +31 -0
  15. pytk_ai-0.1.0/src/pytk_ai/filters/git.py +288 -0
  16. pytk_ai-0.1.0/src/pytk_ai/filters/go.py +216 -0
  17. pytk_ai-0.1.0/src/pytk_ai/filters/python.py +280 -0
  18. pytk_ai-0.1.0/src/pytk_ai/filters/ruby.py +187 -0
  19. pytk_ai-0.1.0/src/pytk_ai/filters/system.py +58 -0
  20. pytk_ai-0.1.0/src/pytk_ai/filters/tests.py +101 -0
  21. pytk_ai-0.1.0/src/pytk_ai/models.py +46 -0
  22. pytk_ai-0.1.0/src/pytk_ai/plan/__init__.py +12 -0
  23. pytk_ai-0.1.0/src/pytk_ai/plan/models.py +39 -0
  24. pytk_ai-0.1.0/src/pytk_ai/plan/normalize.py +153 -0
  25. pytk_ai-0.1.0/src/pytk_ai/plan/planner.py +281 -0
  26. pytk_ai-0.1.0/src/pytk_ai/plan/rules.py +43 -0
  27. pytk_ai-0.1.0/src/pytk_ai/plan/scanner.py +61 -0
  28. pytk_ai-0.1.0/src/pytk_ai/rewrite.py +50 -0
  29. pytk_ai-0.1.0/src/pytk_ai/runner.py +64 -0
  30. pytk_ai-0.1.0/src/pytk_ai/subprocess_utils.py +57 -0
  31. pytk_ai-0.1.0/src/pytk_ai.egg-info/PKG-INFO +64 -0
  32. pytk_ai-0.1.0/src/pytk_ai.egg-info/SOURCES.txt +42 -0
  33. pytk_ai-0.1.0/src/pytk_ai.egg-info/dependency_links.txt +1 -0
  34. pytk_ai-0.1.0/src/pytk_ai.egg-info/entry_points.txt +2 -0
  35. pytk_ai-0.1.0/src/pytk_ai.egg-info/top_level.txt +1 -0
  36. pytk_ai-0.1.0/tests/test_cli_run.py +46 -0
  37. pytk_ai-0.1.0/tests/test_filters_generic.py +49 -0
  38. pytk_ai-0.1.0/tests/test_filters_phase1.py +192 -0
  39. pytk_ai-0.1.0/tests/test_filters_phase2.py +272 -0
  40. pytk_ai-0.1.0/tests/test_plan_normalize.py +46 -0
  41. pytk_ai-0.1.0/tests/test_plan_planner.py +63 -0
  42. pytk_ai-0.1.0/tests/test_plan_rules.py +30 -0
  43. pytk_ai-0.1.0/tests/test_plan_scanner.py +26 -0
  44. pytk_ai-0.1.0/tests/test_runner.py +39 -0
pytk_ai-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,64 @@
1
+ Metadata-Version: 2.4
2
+ Name: pytk-ai
3
+ Version: 0.1.0
4
+ Summary: Python command planning, execution, and output filtering for PYTK-AI
5
+ Author: RTK AI
6
+ License: MIT
7
+ Keywords: llm,cli,token,hooks,productivity
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Topic :: Software Development :: Build Tools
16
+ Classifier: Topic :: Utilities
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+
20
+ # PYTK-AI (Python)
21
+
22
+ This repository is a Python port of the PYTK-AI command rewrite and output-filtering engine.
23
+
24
+ ## Migration direction
25
+
26
+ - target public CLI: `pytk-ai run <command>`
27
+ - target public library API: `from pytk_ai.runner import run_command`
28
+ - command rewrite/planning is internal implementation detail
29
+ - `rtk/` is read-only reference material, not a compatibility contract
30
+ - the old rewrite-first scaffold has been replaced by the execution-oriented `pytk-ai run` flow
31
+
32
+ ## What it does
33
+
34
+ - plans shell commands internally for PYTK-AI-managed handling when useful
35
+ - executes raw shell commands through a small Python runner
36
+ - filters stdout/stderr into a compact token-efficient result
37
+ - preserves compound commands such as `&&`, `||`, `;`, `&`, and simple pipes
38
+ - exposes a small CLI plus hook-oriented JSON adapters
39
+
40
+ ## Install locally
41
+
42
+ ```bash
43
+ pip install -e .
44
+ ```
45
+
46
+ ## CLI usage
47
+
48
+ ```bash
49
+ pytk-ai run git status
50
+ pytk-ai run "cargo test && git push"
51
+ ```
52
+
53
+ ## Library usage
54
+
55
+ ```python
56
+ from pytk_ai.runner import run_command
57
+
58
+ result = run_command("git status")
59
+ print(result.filtered_output)
60
+ print(result.exit_code)
61
+ ```
62
+
63
+ The package is intentionally small and dependency-free so it can be embedded in
64
+ agent toolchains and hook scripts.
@@ -0,0 +1,45 @@
1
+ # PYTK-AI (Python)
2
+
3
+ This repository is a Python port of the PYTK-AI command rewrite and output-filtering engine.
4
+
5
+ ## Migration direction
6
+
7
+ - target public CLI: `pytk-ai run <command>`
8
+ - target public library API: `from pytk_ai.runner import run_command`
9
+ - command rewrite/planning is internal implementation detail
10
+ - `rtk/` is read-only reference material, not a compatibility contract
11
+ - the old rewrite-first scaffold has been replaced by the execution-oriented `pytk-ai run` flow
12
+
13
+ ## What it does
14
+
15
+ - plans shell commands internally for PYTK-AI-managed handling when useful
16
+ - executes raw shell commands through a small Python runner
17
+ - filters stdout/stderr into a compact token-efficient result
18
+ - preserves compound commands such as `&&`, `||`, `;`, `&`, and simple pipes
19
+ - exposes a small CLI plus hook-oriented JSON adapters
20
+
21
+ ## Install locally
22
+
23
+ ```bash
24
+ pip install -e .
25
+ ```
26
+
27
+ ## CLI usage
28
+
29
+ ```bash
30
+ pytk-ai run git status
31
+ pytk-ai run "cargo test && git push"
32
+ ```
33
+
34
+ ## Library usage
35
+
36
+ ```python
37
+ from pytk_ai.runner import run_command
38
+
39
+ result = run_command("git status")
40
+ print(result.filtered_output)
41
+ print(result.exit_code)
42
+ ```
43
+
44
+ The package is intentionally small and dependency-free so it can be embedded in
45
+ agent toolchains and hook scripts.
@@ -0,0 +1,39 @@
1
+ [build-system]
2
+ requires = ["setuptools>=69", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pytk-ai"
7
+ version = "0.1.0"
8
+ description = "Python command planning, execution, and output filtering for PYTK-AI"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "RTK AI" }]
13
+ keywords = ["llm", "cli", "token", "hooks", "productivity"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
+ "Topic :: Software Development :: Build Tools",
23
+ "Topic :: Utilities",
24
+ ]
25
+
26
+ [project.scripts]
27
+ pytk-ai = "pytk_ai.cli:main"
28
+
29
+ [tool.setuptools]
30
+ package-dir = {"" = "src"}
31
+
32
+ [tool.setuptools.packages.find]
33
+ where = ["src"]
34
+
35
+ [tool.setuptools.package-data]
36
+ pytk_ai = ["data/*.json"]
37
+
38
+ [tool.pytest.ini_options]
39
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,13 @@
1
+ from .config import RunOptions
2
+ from .models import CommandResult, ExecutionResult, FilterResult
3
+ from .runner import run_command
4
+
5
+ __all__ = [
6
+ "CommandResult",
7
+ "ExecutionResult",
8
+ "FilterResult",
9
+ "RunOptions",
10
+ "run_command",
11
+ ]
12
+
13
+ __version__ = "0.1.0"
@@ -0,0 +1,4 @@
1
+ from .cli import main
2
+
3
+ if __name__ == "__main__":
4
+ raise SystemExit(main())
@@ -0,0 +1,146 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ import shlex
6
+ import sys
7
+ from typing import Any
8
+
9
+ from .rewrite import rewrite_command
10
+ from .runner import run_command
11
+
12
+
13
+ def _join_command(parts: list[str]) -> str:
14
+ return shlex.join(parts).strip()
15
+
16
+
17
+ def _read_stdin_json() -> dict[str, Any] | None:
18
+ raw = sys.stdin.read().strip()
19
+ if not raw:
20
+ return None
21
+ try:
22
+ payload = json.loads(raw)
23
+ except json.JSONDecodeError:
24
+ return None
25
+ if isinstance(payload, dict):
26
+ return payload
27
+ return None
28
+
29
+
30
+ def _hook_command_from_payload(payload: dict[str, Any]) -> str | None:
31
+ tool_input = payload.get("tool_input")
32
+ if isinstance(tool_input, dict):
33
+ command = tool_input.get("command")
34
+ if isinstance(command, str):
35
+ return command
36
+ tool_args = payload.get("toolArgs")
37
+ if isinstance(tool_args, str):
38
+ try:
39
+ parsed = json.loads(tool_args)
40
+ except json.JSONDecodeError:
41
+ return None
42
+ if isinstance(parsed, dict):
43
+ command = parsed.get("command")
44
+ if isinstance(command, str):
45
+ return command
46
+ return None
47
+
48
+
49
+ def _emit_claude_hook(rewritten: str) -> None:
50
+ payload = {
51
+ "hookSpecificOutput": {
52
+ "hookEventName": "PreToolUse",
53
+ "permissionDecision": "allow",
54
+ "permissionDecisionReason": "PYTK-AI auto-rewrite",
55
+ "updatedInput": {"command": rewritten},
56
+ }
57
+ }
58
+ sys.stdout.write(json.dumps(payload))
59
+
60
+
61
+ def _emit_cursor_hook(rewritten: str) -> None:
62
+ payload = {"permission": "allow", "updated_input": {"command": rewritten}}
63
+ sys.stdout.write(json.dumps(payload))
64
+
65
+
66
+ def _run_command(args: argparse.Namespace) -> int:
67
+ command = _join_command(args.args)
68
+ result = run_command(
69
+ command,
70
+ excluded=tuple(args.exclude or ()),
71
+ max_output_lines=args.max_output_lines,
72
+ )
73
+ if result.filtered_output:
74
+ sys.stdout.write(result.filtered_output)
75
+ if not result.filtered_output.endswith("\n"):
76
+ sys.stdout.write("\n")
77
+ return result.exit_code
78
+
79
+
80
+ def _run_hook(args: argparse.Namespace) -> int:
81
+ payload = _read_stdin_json()
82
+ if not payload:
83
+ if args.agent == "cursor":
84
+ sys.stdout.write("{}")
85
+ return 0
86
+
87
+ command = _hook_command_from_payload(payload)
88
+ if not command:
89
+ if args.agent == "cursor":
90
+ sys.stdout.write("{}")
91
+ return 0
92
+
93
+ result = rewrite_command(command)
94
+ if result is None or not result.changed:
95
+ if args.agent == "cursor":
96
+ sys.stdout.write("{}")
97
+ return 0
98
+
99
+ if args.agent == "cursor":
100
+ _emit_cursor_hook(result.output)
101
+ else:
102
+ _emit_claude_hook(result.output)
103
+ return 0
104
+
105
+
106
+ def build_parser() -> argparse.ArgumentParser:
107
+ parser = argparse.ArgumentParser(prog="pytk-ai", description="PYTK-AI command runner")
108
+ sub = parser.add_subparsers(dest="command", required=True)
109
+
110
+ run = sub.add_parser("run", help="execute a raw shell command and filter output")
111
+ run.add_argument("args", nargs=argparse.REMAINDER, help="raw command tokens")
112
+ run.add_argument(
113
+ "--exclude",
114
+ action="append",
115
+ default=[],
116
+ help="base commands to never plan as PYTK-AI-managed",
117
+ )
118
+ run.add_argument(
119
+ "--max-output-lines",
120
+ type=int,
121
+ default=200,
122
+ help="maximum number of filtered output lines",
123
+ )
124
+ run.set_defaults(func=_run_command)
125
+
126
+ hook = sub.add_parser("hook", help="process hook JSON from stdin")
127
+ hook_sub = hook.add_subparsers(dest="agent", required=True)
128
+ for agent in ("claude", "cursor"):
129
+ agent_parser = hook_sub.add_parser(agent)
130
+ agent_parser.set_defaults(func=_run_hook)
131
+
132
+ return parser
133
+
134
+
135
+ def main(argv: list[str] | None = None) -> int:
136
+ parser = build_parser()
137
+ args = parser.parse_args(argv)
138
+ func = getattr(args, "func", None)
139
+ if func is None:
140
+ parser.print_help()
141
+ return 2
142
+ return func(args)
143
+
144
+
145
+ if __name__ == "__main__":
146
+ raise SystemExit(main())
@@ -0,0 +1,13 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+
5
+
6
+ @dataclass(frozen=True)
7
+ class RunOptions:
8
+ cwd: str | None = None
9
+ env: dict[str, str] = field(default_factory=dict)
10
+ timeout: float | None = None
11
+ plan: bool = True
12
+ excluded_commands: tuple[str, ...] = ()
13
+ max_output_lines: int = 200
@@ -0,0 +1 @@
1
+ """Packaged command registry data for PYTK-AI."""