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.
- huitzo_cli/__init__.py +14 -0
- huitzo_cli/auth.py +146 -0
- huitzo_cli/commands/__init__.py +1 -0
- huitzo_cli/commands/auth.py +61 -0
- huitzo_cli/commands/build.py +81 -0
- huitzo_cli/commands/config_cmd.py +98 -0
- huitzo_cli/commands/dashboard.py +145 -0
- huitzo_cli/commands/dev.py +113 -0
- huitzo_cli/commands/init.py +142 -0
- huitzo_cli/commands/registry.py +105 -0
- huitzo_cli/commands/secrets.py +197 -0
- huitzo_cli/commands/test.py +56 -0
- huitzo_cli/commands/validate.py +64 -0
- huitzo_cli/config.py +219 -0
- huitzo_cli/docs_server/__init__.py +1 -0
- huitzo_cli/docs_server/server.py +128 -0
- huitzo_cli/main.py +72 -0
- huitzo_cli/sandbox/__init__.py +1 -0
- huitzo_cli/sandbox/proxy.py +75 -0
- huitzo_cli/templates/dashboard/App.css.j2 +19 -0
- huitzo_cli/templates/dashboard/App.tsx.j2 +21 -0
- huitzo_cli/templates/dashboard/index.css.j2 +58 -0
- huitzo_cli/templates/dashboard/index.html.j2 +13 -0
- huitzo_cli/templates/dashboard/main.tsx.j2 +10 -0
- huitzo_cli/templates/dashboard/package.json.j2 +19 -0
- huitzo_cli/templates/dashboard/tsconfig.json.j2 +25 -0
- huitzo_cli/templates/dashboard/tsconfig.node.json.j2 +10 -0
- huitzo_cli/templates/dashboard/vite.config.ts.j2 +15 -0
- huitzo_cli/templates/pack/.gitignore.j2 +13 -0
- huitzo_cli/templates/pack/README.md.j2 +52 -0
- huitzo_cli/templates/pack/__init__.py.j2 +3 -0
- huitzo_cli/templates/pack/hello.py.j2 +20 -0
- huitzo_cli/templates/pack/huitzo.yaml.j2 +11 -0
- huitzo_cli/templates/pack/pyproject.toml.j2 +31 -0
- huitzo_cli/templates/pack/test_hello.py.j2 +22 -0
- huitzo_cli/validator.py +133 -0
- huitzo_cli/version.py +14 -0
- huitzo_cli-0.0.0.dist-info/METADATA +19 -0
- huitzo_cli-0.0.0.dist-info/RECORD +41 -0
- huitzo_cli-0.0.0.dist-info/WHEEL +4 -0
- 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)
|