wizard-central-toolkit 0.2.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.
Files changed (77) hide show
  1. wiz_central_toolkit/__init__.py +2 -0
  2. wiz_central_toolkit/bannerwiz/__main__.py +6 -0
  3. wiz_central_toolkit/bannerwiz/cli.py +79 -0
  4. wiz_central_toolkit/bannerwiz/lib/__init__.py +1 -0
  5. wiz_central_toolkit/bannerwiz/lib/banner_wizard.py +999 -0
  6. wiz_central_toolkit/bannerwiz/lib/banner_wrapper.py +199 -0
  7. wiz_central_toolkit/bannerwiz/lib/cli.py +79 -0
  8. wiz_central_toolkit/codeguardwiz/__init__.py +1 -0
  9. wiz_central_toolkit/codeguardwiz/__main__.py +19 -0
  10. wiz_central_toolkit/codeguardwiz/cli.py +971 -0
  11. wiz_central_toolkit/codeguardwiz/codeguard_wizard.py +269 -0
  12. wiz_central_toolkit/codeguardwiz/rust_engine.py +348 -0
  13. wiz_central_toolkit/codeguardwiz/static_engine.py +307 -0
  14. wiz_central_toolkit/common/__init__.py +1 -0
  15. wiz_central_toolkit/debugwiz/__init__.py +1 -0
  16. wiz_central_toolkit/debugwiz/__main__.py +360 -0
  17. wiz_central_toolkit/debugwiz/debug_wizard.py +364 -0
  18. wiz_central_toolkit/diffwiz/__init__.py +0 -0
  19. wiz_central_toolkit/diffwiz/__main__.py +21 -0
  20. wiz_central_toolkit/diffwiz/cli.py +134 -0
  21. wiz_central_toolkit/diffwiz/lib/__init__.py +1 -0
  22. wiz_central_toolkit/diffwiz/lib/cli.py +144 -0
  23. wiz_central_toolkit/diffwiz/lib/diff_wizard.py +89 -0
  24. wiz_central_toolkit/godfatherwiz/__init__.py +0 -0
  25. wiz_central_toolkit/godfatherwiz/__main__.py +18 -0
  26. wiz_central_toolkit/godfatherwiz/ai.py +136 -0
  27. wiz_central_toolkit/godfatherwiz/lib/__init__.py +1 -0
  28. wiz_central_toolkit/godfatherwiz/lib/cli.py +925 -0
  29. wiz_central_toolkit/godfatherwiz/lib/godfather_wizard.py +1552 -0
  30. wiz_central_toolkit/jsonwiz/__init__.py +0 -0
  31. wiz_central_toolkit/jsonwiz/__main__.py +21 -0
  32. wiz_central_toolkit/jsonwiz/cli.py +4 -0
  33. wiz_central_toolkit/jsonwiz/lib/__init__.py +1 -0
  34. wiz_central_toolkit/jsonwiz/lib/cli.py +138 -0
  35. wiz_central_toolkit/jsonwiz/lib/json_wizard.py +133 -0
  36. wiz_central_toolkit/pythonwiz/__init__.py +0 -0
  37. wiz_central_toolkit/pythonwiz/__main__.py +21 -0
  38. wiz_central_toolkit/pythonwiz/lib/__init__.py +1 -0
  39. wiz_central_toolkit/pythonwiz/lib/cli.py +247 -0
  40. wiz_central_toolkit/pythonwiz/lib/python_wizard.py +353 -0
  41. wiz_central_toolkit/readwiz/__init__.py +5 -0
  42. wiz_central_toolkit/readwiz/__main__.py +15 -0
  43. wiz_central_toolkit/readwiz/cli.py +46 -0
  44. wiz_central_toolkit/readwiz/lib/__init__.py +1 -0
  45. wiz_central_toolkit/readwiz/lib/cli.py +47 -0
  46. wiz_central_toolkit/readwiz/lib/read_wizard.py +121 -0
  47. wiz_central_toolkit/rustwiz/__init__.py +5 -0
  48. wiz_central_toolkit/rustwiz/__main__.py +7 -0
  49. wiz_central_toolkit/rustwiz/cli.py +4 -0
  50. wiz_central_toolkit/rustwiz/lib/__init__.py +1 -0
  51. wiz_central_toolkit/rustwiz/lib/cli.py +53 -0
  52. wiz_central_toolkit/rustwiz/lib/rust_wizard.py +128 -0
  53. wiz_central_toolkit/tmuxwiz/__init__.py +0 -0
  54. wiz_central_toolkit/tmuxwiz/__main__.py +15 -0
  55. wiz_central_toolkit/tmuxwiz/cli.py +7 -0
  56. wiz_central_toolkit/tmuxwiz/lib/__init__.py +1 -0
  57. wiz_central_toolkit/tmuxwiz/lib/cli.py +10 -0
  58. wiz_central_toolkit/tmuxwiz/lib/tmux_wizard.py +229 -0
  59. wiz_central_toolkit/todowiz/__init__.py +0 -0
  60. wiz_central_toolkit/todowiz/__main__.py +22 -0
  61. wiz_central_toolkit/todowiz/cli.py +4 -0
  62. wiz_central_toolkit/todowiz/lib/__init__.py +1 -0
  63. wiz_central_toolkit/todowiz/lib/cli.py +82 -0
  64. wiz_central_toolkit/todowiz/lib/todo_wizard.py +134 -0
  65. wiz_central_toolkit/tools_registry.py +62 -0
  66. wiz_central_toolkit/wiz/__init__.py +0 -0
  67. wiz_central_toolkit/wiz/__main__.py +141 -0
  68. wiz_central_toolkit/writewiz/__init__.py +1 -0
  69. wiz_central_toolkit/writewiz/__main__.py +9 -0
  70. wiz_central_toolkit/writewiz/cli.py +264 -0
  71. wiz_central_toolkit/writewiz/write_wizard.py +384 -0
  72. wizard_central_toolkit-0.2.0.dist-info/METADATA +203 -0
  73. wizard_central_toolkit-0.2.0.dist-info/RECORD +77 -0
  74. wizard_central_toolkit-0.2.0.dist-info/WHEEL +5 -0
  75. wizard_central_toolkit-0.2.0.dist-info/entry_points.txt +50 -0
  76. wizard_central_toolkit-0.2.0.dist-info/licenses/LICENSE +201 -0
  77. wizard_central_toolkit-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,89 @@
1
+ """diff-wizard core: standalone change-truth doctor. Unified file diffs, filtered tree diffs."""
2
+ import difflib
3
+ from pathlib import Path
4
+
5
+ SKIP_DIRS = {".venv", ".git", "__pycache__", ".bootstrap", "node_modules"}
6
+
7
+ class DiffWizard:
8
+ """Read-only truth engine. Never modifies either side of a comparison."""
9
+
10
+ def diff_files(self, path_a: Path, path_b: Path) -> str:
11
+ a_lines = path_a.read_text(errors="replace").splitlines(keepends=True)
12
+ b_lines = path_b.read_text(errors="replace").splitlines(keepends=True)
13
+ return "".join(difflib.unified_diff(
14
+ a_lines, b_lines,
15
+ fromfile=str(path_a), tofile=str(path_b),
16
+ ))
17
+
18
+ def diff_dirs(self, dir_a: Path, dir_b: Path) -> dict:
19
+ files_a = {
20
+ str(p.relative_to(dir_a)): p
21
+ for p in dir_a.rglob("*")
22
+ if p.is_file() and not any(part in SKIP_DIRS for part in p.parts)
23
+ }
24
+ files_b = {
25
+ str(p.relative_to(dir_b)): p
26
+ for p in dir_b.rglob("*")
27
+ if p.is_file() and not any(part in SKIP_DIRS for part in p.parts)
28
+ }
29
+ only_a = sorted(set(files_a) - set(files_b))
30
+ only_b = sorted(set(files_b) - set(files_a))
31
+ common = sorted(set(files_a) & set(files_b))
32
+ differing = [rel for rel in common
33
+ if files_a[rel].read_bytes() != files_b[rel].read_bytes()]
34
+ return {"only_a": only_a, "only_b": only_b, "differing": differing}
35
+
36
+ def diff_files_context(self, path_a: Path, path_b: Path, context: int = 3) -> str:
37
+ a_lines = path_a.read_text(errors="replace").splitlines(keepends=True)
38
+ b_lines = path_b.read_text(errors="replace").splitlines(keepends=True)
39
+ return "".join(difflib.unified_diff(
40
+ a_lines, b_lines,
41
+ fromfile=str(path_a), tofile=str(path_b),
42
+ n=context,
43
+ ))
44
+
45
+ def diff_summary(self, source_a: Path, source_b: Path) -> dict:
46
+ if source_a.is_dir() and source_b.is_dir():
47
+ result = self.diff_dirs(source_a, source_b)
48
+ added = len(result["only_b"])
49
+ removed = len(result["only_a"])
50
+ changed = len(result["differing"])
51
+ if not (added or removed or changed):
52
+ return {"identical": True}
53
+ return {
54
+ "identical": False,
55
+ "added_lines": added + changed,
56
+ "removed_lines": removed + changed,
57
+ "hunks": added + removed + changed,
58
+ }
59
+ diff_text = self.diff_files(source_a, source_b)
60
+ if not diff_text:
61
+ return {"identical": True}
62
+ added = sum(1 for line in diff_text.splitlines() if line.startswith("+") and not line.startswith("+++"))
63
+ removed = sum(1 for line in diff_text.splitlines() if line.startswith("-") and not line.startswith("---"))
64
+ changed_hunks = sum(1 for line in diff_text.splitlines() if "@@" in line)
65
+ return {"identical": False, "added_lines": added, "removed_lines": removed, "hunks": changed_hunks}
66
+
67
+ def diff_word_level(self, source_a: Path, source_b: Path) -> str:
68
+ a_text = source_a.read_text(errors="replace")
69
+ b_text = source_b.read_text(errors="replace")
70
+ a_words = a_text.split()
71
+ b_words = b_text.split()
72
+ matcher = difflib.SequenceMatcher(None, a_words, b_words)
73
+ output = []
74
+ for tag, i1, i2, j1, j2 in matcher.get_opcodes():
75
+ if tag == "equal":
76
+ output.append(" ".join(a_words[i1:i2]))
77
+ elif tag == "replace":
78
+ output.append(f"<del>{' '.join(a_words[i1:i2])}</del><ins>{' '.join(b_words[j1:j2])}</ins>")
79
+ elif tag == "delete":
80
+ output.append(f"<del>{' '.join(a_words[i1:i2])}</del>")
81
+ elif tag == "insert":
82
+ output.append(f"<ins>{' '.join(b_words[j1:j2])}</ins>")
83
+ return "\n".join(output)
84
+
85
+ def files_identical(self, path_a: Path, path_b: Path) -> bool:
86
+ return path_a.read_bytes() == path_b.read_bytes()
87
+
88
+ def diff_files_reverse(self, path_a: Path, path_b: Path) -> str:
89
+ return self.diff_files(path_b, path_a)
File without changes
@@ -0,0 +1,18 @@
1
+ """Entry point for godfatherwiz dispatch."""
2
+ import sys
3
+ from .lib import cli as internal_cli
4
+
5
+ def main(argv=None):
6
+ old_argv = sys.argv
7
+ if argv is not None:
8
+ sys.argv = [sys.argv[0]] + list(argv)
9
+ try:
10
+ return internal_cli.main()
11
+ finally:
12
+ sys.argv = old_argv
13
+
14
+ # Expose main at module level for tools_registry.py
15
+ __all__ = ['main']
16
+
17
+ if __name__ == "__main__":
18
+ sys.exit(main())
@@ -0,0 +1,136 @@
1
+ """Optional AI providers for Godfatherwiz.
2
+
3
+ AI is disabled by default. Requests are made only when ask() is called with
4
+ a configured provider.
5
+ """
6
+
7
+ import json
8
+ import os
9
+ from urllib.request import Request, urlopen
10
+
11
+
12
+ class AIProviderError(RuntimeError):
13
+ """Raised when AI is disabled or a provider request fails."""
14
+
15
+
16
+ def provider_name() -> str:
17
+ """Return the configured provider name, defaulting to disabled."""
18
+ return os.environ.get("WIZ_AI_PROVIDER", "none").strip().lower() or "none"
19
+
20
+
21
+ def provider_config() -> dict:
22
+ """Return the active provider configuration from environment variables."""
23
+ provider = provider_name()
24
+ defaults = {
25
+ "ollama": "http://localhost:11434",
26
+ "openai": "https://api.openai.com/v1",
27
+ "openai-compatible": "https://api.example.com/v1",
28
+ "anthropic": "https://api.anthropic.com/v1",
29
+ }
30
+ return {
31
+ "provider": provider,
32
+ "model": os.environ.get("WIZ_AI_MODEL", ""),
33
+ "base_url": os.environ.get("WIZ_AI_BASE_URL", defaults.get(provider, "")),
34
+ "api_key": (
35
+ os.environ.get("WIZ_AI_API_KEY")
36
+ or os.environ.get("OPENAI_API_KEY", "")
37
+ if provider in ("openai", "openai-compatible")
38
+ else os.environ.get("WIZ_AI_API_KEY")
39
+ or os.environ.get("ANTHROPIC_API_KEY", "")
40
+ if provider == "anthropic"
41
+ else ""
42
+ ),
43
+ }
44
+
45
+
46
+ def status_text() -> str:
47
+ """Describe AI availability without exposing credentials."""
48
+ provider = provider_name()
49
+ if provider == "none":
50
+ return (
51
+ "AI provider: none\n"
52
+ "Deterministic tool routing is available.\n"
53
+ "Set WIZ_AI_PROVIDER to enable optional AI."
54
+ )
55
+ config = provider_config()
56
+ return (
57
+ f"AI provider: {provider}\n"
58
+ f"AI model: {config['model'] or '(default)'}\n"
59
+ f"AI endpoint: {config['base_url']}"
60
+ )
61
+
62
+
63
+ def _post_json(url: str, payload: dict, headers: dict) -> dict:
64
+ request = Request(
65
+ url,
66
+ data=json.dumps(payload).encode("utf-8"),
67
+ headers={"Content-Type": "application/json", **headers},
68
+ method="POST",
69
+ )
70
+ try:
71
+ with urlopen(request, timeout=60) as response:
72
+ return json.loads(response.read().decode("utf-8"))
73
+ except Exception as exc:
74
+ raise AIProviderError(f"AI provider request failed: {exc}") from exc
75
+
76
+
77
+ def ask(prompt: str) -> str:
78
+ """Send a prompt to the configured provider and return its response."""
79
+ config = provider_config()
80
+ provider = config["provider"]
81
+ if provider == "none":
82
+ raise AIProviderError("No AI provider configured")
83
+ if provider not in ("ollama", "openai", "openai-compatible", "anthropic"):
84
+ raise AIProviderError(f"Unsupported AI provider: {provider}")
85
+
86
+ model = config["model"]
87
+ base_url = config["base_url"].rstrip("/")
88
+
89
+ if provider == "ollama":
90
+ data = _post_json(
91
+ f"{base_url}/api/chat",
92
+ {
93
+ "model": model or "llama3.2",
94
+ "messages": [{"role": "user", "content": prompt}],
95
+ "stream": False,
96
+ },
97
+ {},
98
+ )
99
+ try:
100
+ return data["message"]["content"]
101
+ except (KeyError, TypeError) as exc:
102
+ raise AIProviderError("Unexpected Ollama response") from exc
103
+
104
+ api_key = config["api_key"]
105
+ if not api_key:
106
+ raise AIProviderError(f"No API key configured for {provider}")
107
+
108
+ if provider == "anthropic":
109
+ data = _post_json(
110
+ f"{base_url}/messages",
111
+ {
112
+ "model": model or "claude-3-5-sonnet-latest",
113
+ "max_tokens": 1024,
114
+ "messages": [{"role": "user", "content": prompt}],
115
+ },
116
+ {"x-api-key": api_key, "anthropic-version": "2023-06-01"},
117
+ )
118
+ try:
119
+ return "".join(
120
+ item["text"] for item in data["content"] if item.get("type") == "text"
121
+ )
122
+ except (KeyError, TypeError) as exc:
123
+ raise AIProviderError("Unexpected Anthropic response") from exc
124
+
125
+ data = _post_json(
126
+ f"{base_url}/chat/completions",
127
+ {
128
+ "model": model or "gpt-4o-mini",
129
+ "messages": [{"role": "user", "content": prompt}],
130
+ },
131
+ {"Authorization": f"Bearer {api_key}"},
132
+ )
133
+ try:
134
+ return data["choices"][0]["message"]["content"]
135
+ except (KeyError, IndexError, TypeError) as exc:
136
+ raise AIProviderError("Unexpected OpenAI-compatible response") from exc
@@ -0,0 +1 @@
1
+ # wizard package init