huitzo-cli 0.0.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 (41) hide show
  1. huitzo_cli/__init__.py +14 -0
  2. huitzo_cli/auth.py +146 -0
  3. huitzo_cli/commands/__init__.py +1 -0
  4. huitzo_cli/commands/auth.py +61 -0
  5. huitzo_cli/commands/build.py +81 -0
  6. huitzo_cli/commands/config_cmd.py +98 -0
  7. huitzo_cli/commands/dashboard.py +145 -0
  8. huitzo_cli/commands/dev.py +113 -0
  9. huitzo_cli/commands/init.py +142 -0
  10. huitzo_cli/commands/registry.py +105 -0
  11. huitzo_cli/commands/secrets.py +197 -0
  12. huitzo_cli/commands/test.py +56 -0
  13. huitzo_cli/commands/validate.py +64 -0
  14. huitzo_cli/config.py +219 -0
  15. huitzo_cli/docs_server/__init__.py +1 -0
  16. huitzo_cli/docs_server/server.py +128 -0
  17. huitzo_cli/main.py +72 -0
  18. huitzo_cli/sandbox/__init__.py +1 -0
  19. huitzo_cli/sandbox/proxy.py +75 -0
  20. huitzo_cli/templates/dashboard/App.css.j2 +19 -0
  21. huitzo_cli/templates/dashboard/App.tsx.j2 +21 -0
  22. huitzo_cli/templates/dashboard/index.css.j2 +58 -0
  23. huitzo_cli/templates/dashboard/index.html.j2 +13 -0
  24. huitzo_cli/templates/dashboard/main.tsx.j2 +10 -0
  25. huitzo_cli/templates/dashboard/package.json.j2 +19 -0
  26. huitzo_cli/templates/dashboard/tsconfig.json.j2 +25 -0
  27. huitzo_cli/templates/dashboard/tsconfig.node.json.j2 +10 -0
  28. huitzo_cli/templates/dashboard/vite.config.ts.j2 +15 -0
  29. huitzo_cli/templates/pack/.gitignore.j2 +13 -0
  30. huitzo_cli/templates/pack/README.md.j2 +52 -0
  31. huitzo_cli/templates/pack/__init__.py.j2 +3 -0
  32. huitzo_cli/templates/pack/hello.py.j2 +20 -0
  33. huitzo_cli/templates/pack/huitzo.yaml.j2 +11 -0
  34. huitzo_cli/templates/pack/pyproject.toml.j2 +31 -0
  35. huitzo_cli/templates/pack/test_hello.py.j2 +22 -0
  36. huitzo_cli/validator.py +133 -0
  37. huitzo_cli/version.py +14 -0
  38. huitzo_cli-0.0.0.dist-info/METADATA +19 -0
  39. huitzo_cli-0.0.0.dist-info/RECORD +41 -0
  40. huitzo_cli-0.0.0.dist-info/WHEEL +4 -0
  41. huitzo_cli-0.0.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,142 @@
1
+ """
2
+ Module: init_command
3
+ Description: Pack initialization command with scaffolding
4
+
5
+ Implements:
6
+ - docs/cli/reference.md#huitzo-init
7
+ """
8
+
9
+ import re
10
+ from pathlib import Path
11
+ from typing import Optional
12
+
13
+ import typer
14
+ from jinja2 import Environment, PackageLoader
15
+ from rich.console import Console
16
+
17
+ console = Console()
18
+ init_app = typer.Typer(help="Initialize a new Intelligence Pack")
19
+
20
+
21
+ def validate_name(name: str) -> bool:
22
+ """Validate pack name (lowercase with hyphens only)."""
23
+ return bool(re.match(r"^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", name))
24
+
25
+
26
+ def validate_namespace(namespace: str) -> bool:
27
+ """Validate namespace (lowercase with hyphens only)."""
28
+ return bool(re.match(r"^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", namespace))
29
+
30
+
31
+ def name_to_module(name: str) -> str:
32
+ """Convert pack name to Python module name."""
33
+ return name.replace("-", "_")
34
+
35
+
36
+ @init_app.command()
37
+ def init(
38
+ name: Optional[str] = typer.Argument(None, help="Pack name (e.g., my-pack)"),
39
+ namespace: Optional[str] = typer.Option(None, "--namespace", help="Pack namespace"),
40
+ author: Optional[str] = typer.Option(None, "--author", help="Author name"),
41
+ author_email: Optional[str] = typer.Option(None, "--email", help="Author email"),
42
+ description: Optional[str] = typer.Option(None, "--description", help="Pack description"),
43
+ ) -> None:
44
+ """Initialize a new Intelligence Pack with scaffolding.
45
+
46
+ Creates a project structure with:
47
+ - pyproject.toml (package configuration)
48
+ - huitzo.yaml (pack manifest)
49
+ - Example command and tests
50
+ - README and gitignore
51
+ """
52
+ # Get pack name
53
+ if not name:
54
+ name = typer.prompt("Pack name (lowercase, hyphens OK)")
55
+
56
+ if not validate_name(name):
57
+ console.print(
58
+ "[red]❌ Invalid pack name. Use lowercase letters, numbers, and hyphens.[/red]"
59
+ )
60
+ raise typer.Exit(1)
61
+
62
+ # Get namespace
63
+ if not namespace:
64
+ namespace = typer.prompt("Namespace (lowercase, hyphens OK)", default=name)
65
+
66
+ if not validate_namespace(namespace):
67
+ console.print(
68
+ "[red]❌ Invalid namespace. Use lowercase letters, numbers, and hyphens.[/red]"
69
+ )
70
+ raise typer.Exit(1)
71
+
72
+ # Get author info
73
+ if not author:
74
+ author = typer.prompt("Author name", default="Developer")
75
+
76
+ if not author_email:
77
+ author_email = typer.prompt("Author email", default="dev@example.com")
78
+
79
+ # Get description
80
+ if not description:
81
+ description = typer.prompt("Description", default=f"Intelligence Pack: {name}")
82
+
83
+ # Create project directory
84
+ project_root = Path.cwd() / name
85
+ if project_root.exists():
86
+ console.print(f"[red]❌ Directory '{name}' already exists[/red]")
87
+ raise typer.Exit(1)
88
+
89
+ project_root.mkdir(parents=True, exist_ok=True)
90
+ module_name = name_to_module(name)
91
+
92
+ # Set up Jinja2 environment
93
+ env = Environment(loader=PackageLoader("huitzo_cli", "templates/pack"))
94
+
95
+ # Template context
96
+ context = {
97
+ "pack_name": name,
98
+ "namespace": namespace,
99
+ "module_name": module_name,
100
+ "author": author,
101
+ "author_email": author_email,
102
+ "description": description,
103
+ }
104
+
105
+ # Create directory structure
106
+ src_dir = project_root / "src" / module_name
107
+ src_dir.mkdir(parents=True, exist_ok=True)
108
+
109
+ commands_dir = src_dir / "commands"
110
+ commands_dir.mkdir(parents=True, exist_ok=True)
111
+
112
+ tests_dir = project_root / "tests"
113
+ tests_dir.mkdir(parents=True, exist_ok=True)
114
+
115
+ # Render templates
116
+ files_to_create = {
117
+ "pyproject.toml.j2": project_root / "pyproject.toml",
118
+ "huitzo.yaml.j2": project_root / "huitzo.yaml",
119
+ "README.md.j2": project_root / "README.md",
120
+ ".gitignore.j2": project_root / ".gitignore",
121
+ "__init__.py.j2": src_dir / "__init__.py",
122
+ "hello.py.j2": commands_dir / "hello.py",
123
+ "test_hello.py.j2": tests_dir / "test_hello.py",
124
+ }
125
+
126
+ for template_name, target_path in files_to_create.items():
127
+ template = env.get_template(template_name)
128
+ content = template.render(**context)
129
+ target_path.write_text(content)
130
+
131
+ # Create __init__.py for commands directory
132
+ (commands_dir / "__init__.py").write_text('"""Commands for this pack."""\n')
133
+
134
+ # Create __init__.py for tests directory
135
+ (tests_dir / "__init__.py").write_text('"""Tests for this pack."""\n')
136
+
137
+ console.print(f"\n[green]✅ Pack '{name}' initialized successfully![/green]")
138
+ console.print("\n[bold]Next steps:[/bold]")
139
+ console.print(f" cd {name}")
140
+ console.print(" huitzo validate")
141
+ console.print(" huitzo test")
142
+ console.print(" huitzo dev")
@@ -0,0 +1,105 @@
1
+ """
2
+ Module: registry_command
3
+ Description: Registry operations (publish, install, list, run)
4
+
5
+ Implements:
6
+ - docs/cli/reference.md#huitzo-publish
7
+ - docs/cli/reference.md#huitzo-install
8
+ - docs/cli/reference.md#huitzo-list
9
+ - docs/cli/reference.md#huitzo-run
10
+ """
11
+
12
+ import subprocess
13
+ from pathlib import Path
14
+
15
+ import typer
16
+ from rich.console import Console
17
+
18
+ console = Console()
19
+ registry_app = typer.Typer(help="Registry operations")
20
+
21
+
22
+ @registry_app.command()
23
+ def publish() -> None:
24
+ """Publish pack to Huitzo Registry.
25
+
26
+ Note: Requires Huitzo Cloud (not yet available).
27
+ """
28
+ console.print("[yellow]ℹ️ Huitzo Cloud Registry is not yet available[/yellow]")
29
+ console.print("This feature is coming soon!")
30
+ raise typer.Exit(1)
31
+
32
+
33
+ @registry_app.command()
34
+ def install(
35
+ package: str = typer.Argument(..., help="Package name or path to .whl file"),
36
+ ) -> None:
37
+ """Install a pack from registry or local file.
38
+
39
+ Args:
40
+ package: Package name (from registry) or path to .whl file
41
+ """
42
+ # Check if it's a local file
43
+ if package.endswith(".whl"):
44
+ path = Path(package)
45
+ if not path.exists():
46
+ console.print(f"[red]❌ File not found: {package}[/red]")
47
+ raise typer.Exit(1)
48
+
49
+ console.print(f"[bold]Installing from: {package}[/bold]")
50
+ try:
51
+ result = subprocess.run(["pip", "install", str(path)], check=False)
52
+ if result.returncode != 0:
53
+ raise typer.Exit(result.returncode)
54
+ console.print("[green]✅ Installation successful[/green]")
55
+ except FileNotFoundError:
56
+ console.print("[red]❌ pip not found[/red]")
57
+ raise typer.Exit(1)
58
+ else:
59
+ # Try to install from registry
60
+ console.print(f"[bold]Installing: {package}[/bold]")
61
+ console.print("[yellow]ℹ️ Huitzo Cloud Registry is not yet available[/yellow]")
62
+ console.print("Use a local .whl file or install from PyPI")
63
+ raise typer.Exit(1)
64
+
65
+
66
+ @registry_app.command()
67
+ def list_packs() -> None:
68
+ """List installed packs."""
69
+ console.print("[bold]Installed Huitzo Packs:[/bold]")
70
+
71
+ try:
72
+ result = subprocess.run(
73
+ ["pip", "list", "--format", "json"],
74
+ capture_output=True,
75
+ text=True,
76
+ check=True,
77
+ )
78
+ import json
79
+
80
+ packages = json.loads(result.stdout)
81
+ huitzo_packs = [p for p in packages if "pack" in p["name"].lower()]
82
+
83
+ if huitzo_packs:
84
+ for pack in huitzo_packs:
85
+ console.print(f" • {pack['name']} ({pack['version']})")
86
+ else:
87
+ console.print(" (none installed)")
88
+ except (FileNotFoundError, json.JSONDecodeError):
89
+ console.print("[yellow]⚠️ Could not list packages[/yellow]")
90
+
91
+
92
+ @registry_app.command()
93
+ def run(
94
+ pack_command: str = typer.Argument(..., help="Pack command (e.g., my-pack:hello)"),
95
+ ) -> None:
96
+ """Run a pack command.
97
+
98
+ Note: Requires Huitzo Cloud (not yet available).
99
+
100
+ Args:
101
+ pack_command: Pack and command name (format: pack-name:command-name)
102
+ """
103
+ console.print("[yellow]ℹ️ Cloud execution is not yet available[/yellow]")
104
+ console.print("This feature is coming soon!")
105
+ raise typer.Exit(1)
@@ -0,0 +1,197 @@
1
+ """
2
+ Module: secrets_command
3
+ Description: Secrets management commands (local storage only)
4
+
5
+ Implements:
6
+ - docs/cli/reference.md#huitzo-secrets
7
+ """
8
+
9
+ import json
10
+ import os
11
+ from pathlib import Path
12
+ from typing import Any, Optional
13
+
14
+ import typer
15
+ from rich.console import Console
16
+ from rich.table import Table
17
+
18
+ console = Console()
19
+ secrets_app = typer.Typer(help="Manage secrets")
20
+
21
+
22
+ class SecretsStore:
23
+ """Local secrets storage."""
24
+
25
+ @staticmethod
26
+ def _get_secrets_dir() -> Path:
27
+ """Get platform-specific secrets directory."""
28
+ if os.name == "nt": # Windows
29
+ base = Path(os.environ.get("APPDATA", Path.home() / "AppData" / "Roaming"))
30
+ return base / "huitzo" / "secrets"
31
+ else: # Unix-like
32
+ return Path.home() / ".huitzo" / "secrets"
33
+
34
+ @classmethod
35
+ def _get_secrets_file(cls, pack_name: str = "default") -> Path:
36
+ """Get secrets file for pack."""
37
+ return cls._get_secrets_dir() / f"{pack_name}.json"
38
+
39
+ @classmethod
40
+ def set(cls, name: str, value: str, pack: str = "default") -> None:
41
+ """Store a secret."""
42
+ secrets_dir = cls._get_secrets_dir()
43
+ secrets_dir.mkdir(parents=True, exist_ok=True)
44
+
45
+ # Set restrictive permissions on directory
46
+ if os.name != "nt":
47
+ secrets_dir.chmod(0o700)
48
+
49
+ secrets_file = cls._get_secrets_file(pack)
50
+
51
+ # Load existing secrets
52
+ secrets = {}
53
+ if secrets_file.exists():
54
+ try:
55
+ with open(secrets_file) as f:
56
+ secrets = json.load(f)
57
+ except (json.JSONDecodeError, OSError):
58
+ pass
59
+
60
+ # Add new secret
61
+ secrets[name] = value
62
+
63
+ # Save secrets
64
+ with open(secrets_file, "w") as f:
65
+ json.dump(secrets, f)
66
+
67
+ # Set restrictive permissions on file
68
+ if os.name != "nt":
69
+ secrets_file.chmod(0o600)
70
+
71
+ @classmethod
72
+ def get(cls, name: str, pack: str = "default") -> Optional[str]:
73
+ """Get a secret."""
74
+ secrets_file = cls._get_secrets_file(pack)
75
+
76
+ if not secrets_file.exists():
77
+ return None
78
+
79
+ try:
80
+ with open(secrets_file) as f:
81
+ secrets: dict[str, Any] = json.load(f)
82
+ value: str | None = secrets.get(name)
83
+ return value
84
+ except (json.JSONDecodeError, OSError):
85
+ return None
86
+
87
+ @classmethod
88
+ def list_secrets(cls, pack: str = "default") -> dict[str, Any]:
89
+ """List all secrets for a pack."""
90
+ secrets_file = cls._get_secrets_file(pack)
91
+
92
+ if not secrets_file.exists():
93
+ return {}
94
+
95
+ try:
96
+ with open(secrets_file) as f:
97
+ result: dict[str, Any] = json.load(f)
98
+ return result
99
+ except (json.JSONDecodeError, OSError):
100
+ return {}
101
+
102
+ @classmethod
103
+ def remove(cls, name: str, pack: str = "default") -> None:
104
+ """Remove a secret."""
105
+ secrets_file = cls._get_secrets_file(pack)
106
+
107
+ if not secrets_file.exists():
108
+ return
109
+
110
+ try:
111
+ with open(secrets_file) as f:
112
+ secrets = json.load(f)
113
+
114
+ if name in secrets:
115
+ del secrets[name]
116
+
117
+ with open(secrets_file, "w") as f:
118
+ json.dump(secrets, f)
119
+
120
+ except (json.JSONDecodeError, OSError):
121
+ pass
122
+
123
+
124
+ @secrets_app.command()
125
+ def set(
126
+ name: str,
127
+ value: str,
128
+ pack: str = typer.Option("default", "--pack", help="Pack name"),
129
+ ) -> None:
130
+ """Set a secret.
131
+
132
+ Args:
133
+ name: Secret name
134
+ value: Secret value
135
+ """
136
+ console.print("[yellow]ℹ️ Secrets are stored locally only[/yellow]")
137
+ console.print("[yellow]Cloud sync is not yet available[/yellow]")
138
+ console.print()
139
+
140
+ SecretsStore.set(name, value, pack=pack)
141
+ console.print(f"[green]✅ Secret '{name}' set[/green]")
142
+
143
+
144
+ @secrets_app.command()
145
+ def list_secrets(pack: str = typer.Option("default", "--pack", help="Pack name")) -> None:
146
+ """List all secrets."""
147
+ console.print("[yellow]ℹ️ Secrets are stored locally only[/yellow]")
148
+ console.print()
149
+
150
+ secrets = SecretsStore.list_secrets(pack=pack)
151
+
152
+ if not secrets:
153
+ console.print(f"[yellow]No secrets for pack '{pack}'[/yellow]")
154
+ return
155
+
156
+ table = Table(title=f"Secrets (pack: {pack})")
157
+ table.add_column("Name", style="cyan")
158
+ table.add_column("Value", style="yellow")
159
+
160
+ for name, value in secrets.items():
161
+ masked_value = "*" * 8 if value else "(empty)"
162
+ table.add_row(name, masked_value)
163
+
164
+ console.print(table)
165
+
166
+
167
+ @secrets_app.command()
168
+ def remove(
169
+ name: str,
170
+ pack: str = typer.Option("default", "--pack", help="Pack name"),
171
+ ) -> None:
172
+ """Remove a secret.
173
+
174
+ Args:
175
+ name: Secret name
176
+ """
177
+ SecretsStore.remove(name, pack=pack)
178
+ console.print(f"[green]✅ Secret '{name}' removed[/green]")
179
+
180
+
181
+ @secrets_app.command()
182
+ def show(
183
+ name: str,
184
+ pack: str = typer.Option("default", "--pack", help="Pack name"),
185
+ ) -> None:
186
+ """Show secret value.
187
+
188
+ Args:
189
+ name: Secret name
190
+ """
191
+ value = SecretsStore.get(name, pack=pack)
192
+
193
+ if value is None:
194
+ console.print(f"[red]❌ Secret '{name}' not found[/red]")
195
+ raise typer.Exit(1)
196
+
197
+ console.print(f"{name} = {value}")
@@ -0,0 +1,56 @@
1
+ """
2
+ Module: test_command
3
+ Description: Test runner command
4
+
5
+ Implements:
6
+ - docs/cli/reference.md#huitzo-test
7
+ """
8
+
9
+ import subprocess
10
+ from typing import Optional
11
+
12
+ import typer
13
+ from rich.console import Console
14
+
15
+ console = Console()
16
+ test_app = typer.Typer(help="Run pack tests")
17
+
18
+
19
+ @test_app.command()
20
+ def test(
21
+ coverage: bool = typer.Option(False, "--coverage", help="Generate coverage report"),
22
+ verbose: bool = typer.Option(False, "--verbose", "-v", help="Verbose output"),
23
+ parallel: bool = typer.Option(False, "--parallel", "-p", help="Run tests in parallel"),
24
+ filter_pattern: Optional[str] = typer.Option(
25
+ None, "--filter", "-k", help="Filter tests by pattern"
26
+ ),
27
+ ) -> None:
28
+ """Run pack tests using pytest.
29
+
30
+ Options:
31
+ - --coverage: Generate coverage report (requires pytest-cov)
32
+ - --verbose: Verbose test output
33
+ - --parallel: Run tests in parallel (requires pytest-xdist)
34
+ - --filter: Filter tests by pattern (pytest -k syntax)
35
+ """
36
+ cmd = ["pytest", "tests/"]
37
+
38
+ if verbose:
39
+ cmd.append("-v")
40
+
41
+ if coverage:
42
+ cmd.extend(["--cov=src", "--cov-report=term-missing"])
43
+
44
+ if parallel:
45
+ cmd.extend(["-n", "auto"])
46
+
47
+ if filter_pattern:
48
+ cmd.extend(["-k", filter_pattern])
49
+
50
+ try:
51
+ result = subprocess.run(cmd, check=False)
52
+ if result.returncode != 0:
53
+ raise typer.Exit(result.returncode)
54
+ except FileNotFoundError:
55
+ console.print("[red]❌ pytest not found. Install with: pip install pytest[/red]")
56
+ raise typer.Exit(1)
@@ -0,0 +1,64 @@
1
+ """
2
+ Module: validate_command
3
+ Description: Pack validation command
4
+
5
+ Implements:
6
+ - docs/cli/reference.md#huitzo-validate
7
+ """
8
+
9
+ from pathlib import Path
10
+ from typing import Optional
11
+
12
+ import typer
13
+ from rich.console import Console
14
+
15
+ from huitzo_cli.validator import PackValidator
16
+
17
+ console = Console()
18
+ validate_app = typer.Typer(help="Validate pack structure")
19
+
20
+
21
+ @validate_app.command()
22
+ def validate(
23
+ path: Optional[Path] = typer.Option(None, "--path", help="Path to pack root"),
24
+ strict: bool = typer.Option(False, "--strict", help="Treat warnings as errors"),
25
+ ) -> None:
26
+ """Validate Intelligence Pack structure.
27
+
28
+ Checks for:
29
+ - pyproject.toml with required fields
30
+ - huitzo.yaml manifest with required fields
31
+ - Source directory with Python package
32
+ - Test directory with tests
33
+ - README documentation
34
+ """
35
+ pack_root = path or Path.cwd()
36
+
37
+ if not pack_root.exists():
38
+ console.print(f"[red]❌ Pack path not found: {pack_root}[/red]")
39
+ raise typer.Exit(1)
40
+
41
+ validator = PackValidator(pack_root=pack_root)
42
+ result = validator.validate(strict=strict)
43
+
44
+ # Print results
45
+ console.print()
46
+ if result.valid:
47
+ console.print("[green]✅ Validation: PASSED[/green]")
48
+ else:
49
+ console.print("[red]❌ Validation: FAILED[/red]")
50
+
51
+ if result.errors:
52
+ console.print("\n[red]Errors:[/red]")
53
+ for error in result.errors:
54
+ console.print(f" • {error}")
55
+
56
+ if result.warnings:
57
+ console.print("\n[yellow]Warnings:[/yellow]")
58
+ for warning in result.warnings:
59
+ console.print(f" • {warning}")
60
+
61
+ console.print()
62
+
63
+ if not result.valid:
64
+ raise typer.Exit(1)