proxima-cli 1.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.
@@ -0,0 +1,141 @@
1
+ """prox knowledge — manage knowledge sources and bases."""
2
+
3
+ from pathlib import Path
4
+ from typing import Optional
5
+
6
+ import typer
7
+ from rich.console import Console
8
+ from rich.table import Table
9
+
10
+ from ..api import gateway_get, gateway_post, gateway_delete, APIError
11
+
12
+ app = typer.Typer(help="Manage knowledge sources and bases.")
13
+ console = Console()
14
+
15
+ source_app = typer.Typer(help="Manage knowledge sources.")
16
+ app.add_typer(source_app, name="source")
17
+
18
+ base_app = typer.Typer(help="Manage knowledge bases.")
19
+ app.add_typer(base_app, name="base")
20
+
21
+
22
+ @source_app.command("list")
23
+ def list_sources():
24
+ """List knowledge sources."""
25
+ try:
26
+ data = gateway_get("/build/knowledge/sources")
27
+ sources = data.get("sources", [])
28
+ if not sources:
29
+ console.print("[dim]No sources.[/dim]")
30
+ return
31
+ table = Table(title="Knowledge Sources")
32
+ table.add_column("ID", style="bold")
33
+ table.add_column("Name")
34
+ table.add_column("Provider")
35
+ for s in sources:
36
+ table.add_row(s.get("id"), s.get("name", ""), s.get("provider", ""))
37
+ console.print(table)
38
+ except APIError as e:
39
+ console.print(f"[red]Error:[/red] {e.detail}")
40
+ raise typer.Exit(1)
41
+
42
+
43
+ @source_app.command("create")
44
+ def create_source(
45
+ id: str = typer.Option(..., "--id"),
46
+ name: str = typer.Option(..., "--name"),
47
+ provider: str = typer.Option(..., "--provider", help="azure_blob, s3, snowflake, etc."),
48
+ secret: Optional[str] = typer.Option(None, "--secret", help="Secret name for credentials"),
49
+ container: Optional[str] = typer.Option(None, "--container"),
50
+ path: Optional[str] = typer.Option(None, "--path"),
51
+ ):
52
+ """Create a knowledge source."""
53
+ payload = {"id": id, "name": name, "provider": provider}
54
+ if secret:
55
+ payload["credential_secret"] = secret
56
+ if container:
57
+ payload["container"] = container
58
+ if path:
59
+ payload["path"] = path
60
+ try:
61
+ gateway_post("/build/knowledge/sources", payload)
62
+ console.print(f"[green]✓[/green] Source created: {id}")
63
+ except APIError as e:
64
+ console.print(f"[red]Error:[/red] {e.detail}")
65
+ raise typer.Exit(1)
66
+
67
+
68
+ @source_app.command("test")
69
+ def test_source(source_id: str = typer.Argument(help="Source ID to test")):
70
+ """Test a source connection."""
71
+ try:
72
+ data = gateway_post(f"/build/knowledge/sources/{source_id}/test")
73
+ if data.get("success"):
74
+ console.print(f"[green]✓[/green] Connection successful ({data.get('rows', '?')} rows)")
75
+ else:
76
+ console.print(f"[red]✗[/red] {data.get('error', 'Connection failed')}")
77
+ except APIError as e:
78
+ console.print(f"[red]Error:[/red] {e.detail}")
79
+ raise typer.Exit(1)
80
+
81
+
82
+ @source_app.command("delete")
83
+ def delete_source(source_id: str = typer.Argument(), confirm: bool = typer.Option(False, "--yes", "-y")):
84
+ """Delete a knowledge source."""
85
+ if not confirm:
86
+ typer.confirm(f"Delete source '{source_id}'?", abort=True)
87
+ try:
88
+ gateway_delete(f"/build/knowledge/sources/{source_id}")
89
+ console.print(f"[green]✓[/green] Deleted: {source_id}")
90
+ except APIError as e:
91
+ console.print(f"[red]Error:[/red] {e.detail}")
92
+ raise typer.Exit(1)
93
+
94
+
95
+ @base_app.command("list")
96
+ def list_bases():
97
+ """List knowledge bases."""
98
+ try:
99
+ data = gateway_get("/build/knowledge/bases")
100
+ bases = data.get("bases", [])
101
+ if not bases:
102
+ console.print("[dim]No bases.[/dim]")
103
+ return
104
+ table = Table(title="Knowledge Bases")
105
+ table.add_column("ID", style="bold")
106
+ table.add_column("Name")
107
+ table.add_column("Sources", justify="right")
108
+ for b in bases:
109
+ table.add_row(b.get("id"), b.get("name", ""), str(len(b.get("sources", []))))
110
+ console.print(table)
111
+ except APIError as e:
112
+ console.print(f"[red]Error:[/red] {e.detail}")
113
+ raise typer.Exit(1)
114
+
115
+
116
+ @base_app.command("create")
117
+ def create_base(
118
+ id: str = typer.Option(..., "--id"),
119
+ name: str = typer.Option(..., "--name"),
120
+ sources: str = typer.Option(..., "--sources", help="Comma-separated source IDs"),
121
+ ):
122
+ """Create a knowledge base."""
123
+ try:
124
+ gateway_post("/build/knowledge/bases", {"id": id, "name": name, "sources": sources.split(",")})
125
+ console.print(f"[green]✓[/green] Base created: {id}")
126
+ except APIError as e:
127
+ console.print(f"[red]Error:[/red] {e.detail}")
128
+ raise typer.Exit(1)
129
+
130
+
131
+ @base_app.command("delete")
132
+ def delete_base(base_id: str = typer.Argument(), confirm: bool = typer.Option(False, "--yes", "-y")):
133
+ """Delete a knowledge base."""
134
+ if not confirm:
135
+ typer.confirm(f"Delete base '{base_id}'?", abort=True)
136
+ try:
137
+ gateway_delete(f"/build/knowledge/bases/{base_id}")
138
+ console.print(f"[green]✓[/green] Deleted: {base_id}")
139
+ except APIError as e:
140
+ console.print(f"[red]Error:[/red] {e.detail}")
141
+ raise typer.Exit(1)
prox/commands/model.py ADDED
@@ -0,0 +1,69 @@
1
+ """prox model — manage LLM model connections."""
2
+
3
+ from typing import Optional
4
+ import typer
5
+ from rich.console import Console
6
+ from rich.table import Table
7
+ from ..api import gateway_get, gateway_post, gateway_delete, APIError
8
+
9
+ app = typer.Typer(help="Manage LLM models.")
10
+ console = Console()
11
+
12
+
13
+ @app.command("list")
14
+ def list_models():
15
+ """List deployed models."""
16
+ try:
17
+ data = gateway_get("/build/models")
18
+ models = data.get("models", [])
19
+ if not models:
20
+ console.print("[dim]No models.[/dim]")
21
+ return
22
+ table = Table(title="Models")
23
+ table.add_column("ID", style="bold")
24
+ table.add_column("Provider")
25
+ table.add_column("Model")
26
+ table.add_column("Status")
27
+ for m in models:
28
+ st = m.get("status", "unknown")
29
+ st_style = "green" if st == "active" else "yellow"
30
+ table.add_row(m.get("id"), m.get("provider", ""), m.get("model", ""), f"[{st_style}]{st}[/{st_style}]")
31
+ console.print(table)
32
+ except APIError as e:
33
+ console.print(f"[red]Error:[/red] {e.detail}")
34
+ raise typer.Exit(1)
35
+
36
+
37
+ @app.command("deploy")
38
+ def deploy_model(
39
+ id: str = typer.Option(..., "--id"),
40
+ provider: str = typer.Option(..., "--provider", help="openai, anthropic, bedrock, watsonx, ollama"),
41
+ model: str = typer.Option(..., "--model", help="Model name (e.g. gpt-4.1-mini)"),
42
+ endpoint_secret: Optional[str] = typer.Option(None, "--endpoint-secret"),
43
+ key_secret: Optional[str] = typer.Option(None, "--key-secret"),
44
+ ):
45
+ """Deploy a model connection."""
46
+ payload = {"id": id, "provider": provider, "model": model}
47
+ if endpoint_secret:
48
+ payload["endpoint_secret"] = endpoint_secret
49
+ if key_secret:
50
+ payload["key_secret"] = key_secret
51
+ try:
52
+ gateway_post("/build/models", payload)
53
+ console.print(f"[green]✓[/green] Model deployed: {id}")
54
+ except APIError as e:
55
+ console.print(f"[red]Error:[/red] {e.detail}")
56
+ raise typer.Exit(1)
57
+
58
+
59
+ @app.command("delete")
60
+ def delete_model(model_id: str = typer.Argument(), confirm: bool = typer.Option(False, "--yes", "-y")):
61
+ """Delete a model."""
62
+ if not confirm:
63
+ typer.confirm(f"Delete model '{model_id}'?", abort=True)
64
+ try:
65
+ gateway_delete(f"/build/models/{model_id}")
66
+ console.print(f"[green]✓[/green] Deleted: {model_id}")
67
+ except APIError as e:
68
+ console.print(f"[red]Error:[/red] {e.detail}")
69
+ raise typer.Exit(1)
@@ -0,0 +1,209 @@
1
+ """prox ontology — manage ontologies on the platform."""
2
+
3
+ from pathlib import Path
4
+ from typing import Optional
5
+
6
+ import typer
7
+ import yaml
8
+ from rich.console import Console
9
+ from rich.table import Table
10
+
11
+ from ..api import gateway_get, gateway_post, gateway_put, gateway_delete, APIError
12
+
13
+ app = typer.Typer(help="Manage ontologies.")
14
+ console = Console()
15
+
16
+
17
+ @app.command("list")
18
+ def list_ontologies():
19
+ """List all ontologies on the platform."""
20
+ try:
21
+ data = gateway_get("/build/ontology")
22
+ except APIError as e:
23
+ console.print(f"[red]Error:[/red] {e.detail}")
24
+ raise typer.Exit(1)
25
+
26
+ ontologies = data.get("ontologies", [])
27
+ if not ontologies:
28
+ console.print("[dim]No ontologies.[/dim]")
29
+ return
30
+
31
+ table = Table(title=f"Ontologies ({len(ontologies)})")
32
+ table.add_column("ID", style="bold")
33
+ table.add_column("Name")
34
+ table.add_column("Entities", justify="right")
35
+ table.add_column("Actions", justify="right")
36
+ table.add_column("Version")
37
+
38
+ for o in ontologies:
39
+ table.add_row(o.get("id"), o.get("name", ""), str(o.get("entities", 0)), str(o.get("actions", 0)), o.get("version", "—"))
40
+
41
+ console.print(table)
42
+
43
+
44
+ @app.command("show")
45
+ def show_ontology(ontology_id: str = typer.Argument(help="Ontology ID")):
46
+ """Show ontology details."""
47
+ try:
48
+ data = gateway_get(f"/build/ontology/{ontology_id}")
49
+ except APIError as e:
50
+ console.print(f"[red]Error:[/red] {e.detail}")
51
+ raise typer.Exit(1)
52
+
53
+ o = data.get("ontology", {})
54
+ console.print(f"\n[bold]{o.get('name', ontology_id)}[/bold] (v{o.get('version', '?')})")
55
+ console.print(f"[dim]{o.get('description', '')}[/dim]\n")
56
+
57
+ entities = o.get("entities", {})
58
+ relationships = o.get("relationships", [])
59
+ actions = o.get("actions", {})
60
+ events = o.get("events", [])
61
+
62
+ console.print(f" Entities: {len(entities)}")
63
+ console.print(f" Relationships: {len(relationships)}")
64
+ console.print(f" Actions: {len(actions)}")
65
+ console.print(f" Events: {len(events)}")
66
+ console.print()
67
+
68
+ if entities:
69
+ console.print(" [bold]Entities:[/bold]")
70
+ for name, edef in entities.items():
71
+ props = len(edef.get("properties", {}))
72
+ console.print(f" • {name} ({props} properties)")
73
+ if actions:
74
+ console.print(f"\n [bold]Actions:[/bold]")
75
+ for aname, adef in actions.items():
76
+ console.print(f" • {aname} → {adef.get('entity', '?')}")
77
+ console.print()
78
+
79
+
80
+ @app.command("create")
81
+ def create_ontology(
82
+ from_file: Path = typer.Option(..., "--from", help="YAML ontology definition file"),
83
+ ):
84
+ """Create an ontology from a YAML file."""
85
+ if not from_file.exists():
86
+ console.print(f"[red]Error:[/red] File not found: {from_file}")
87
+ raise typer.Exit(1)
88
+
89
+ data = yaml.safe_load(from_file.read_text())
90
+ if not data.get("id"):
91
+ console.print("[red]Error:[/red] Ontology YAML must have an 'id' field")
92
+ raise typer.Exit(1)
93
+
94
+ try:
95
+ gateway_post("/build/ontology", data)
96
+ console.print(f"[green]✓[/green] Ontology created: {data['id']}")
97
+ except APIError as e:
98
+ console.print(f"[red]Error:[/red] {e.detail}")
99
+ raise typer.Exit(1)
100
+
101
+
102
+ @app.command("update")
103
+ def update_ontology(
104
+ ontology_id: str = typer.Argument(help="Ontology ID"),
105
+ from_file: Path = typer.Option(..., "--from", help="YAML ontology definition file"),
106
+ ):
107
+ """Update an existing ontology from a YAML file."""
108
+ if not from_file.exists():
109
+ console.print(f"[red]Error:[/red] File not found: {from_file}")
110
+ raise typer.Exit(1)
111
+
112
+ data = yaml.safe_load(from_file.read_text())
113
+ data["id"] = ontology_id
114
+
115
+ try:
116
+ gateway_put(f"/build/ontology/{ontology_id}", data)
117
+ console.print(f"[green]✓[/green] Ontology updated: {ontology_id}")
118
+ except APIError as e:
119
+ console.print(f"[red]Error:[/red] {e.detail}")
120
+ raise typer.Exit(1)
121
+
122
+
123
+ @app.command("validate")
124
+ def validate_ontology_cmd(
125
+ from_file: Path = typer.Option(..., "--from", help="YAML ontology file to validate"),
126
+ ):
127
+ """Validate an ontology YAML file without creating it."""
128
+ if not from_file.exists():
129
+ console.print(f"[red]Error:[/red] File not found: {from_file}")
130
+ raise typer.Exit(1)
131
+
132
+ data = yaml.safe_load(from_file.read_text())
133
+
134
+ # Import the validator
135
+ import sys
136
+ from pathlib import Path as P
137
+ platform_path = str(P(__file__).resolve().parent.parent.parent.parent.parent)
138
+ sys.path.insert(0, platform_path)
139
+ try:
140
+ from ontology import validate_ontology
141
+ errors = validate_ontology(data)
142
+ if errors:
143
+ console.print(f"[red]✗ Validation failed ({len(errors)} errors):[/red]")
144
+ for err in errors:
145
+ console.print(f" • {err}")
146
+ raise typer.Exit(1)
147
+ else:
148
+ entities = len(data.get("entities", {}))
149
+ actions = len(data.get("actions", {}))
150
+ console.print(f"[green]✓[/green] Valid ontology: {data.get('id', '?')} ({entities} entities, {actions} actions)")
151
+ except ImportError:
152
+ # Fallback: basic validation
153
+ if not data.get("id"):
154
+ console.print("[red]✗[/red] Missing 'id' field")
155
+ raise typer.Exit(1)
156
+ console.print(f"[green]✓[/green] Basic validation passed: {data.get('id', '?')}")
157
+
158
+
159
+ @app.command("publish")
160
+ def publish_ontology(ontology_id: str = typer.Argument(help="Ontology ID to publish")):
161
+ """Publish an ontology (creates immutable version)."""
162
+ try:
163
+ data = gateway_post(f"/build/ontology/{ontology_id}/publish")
164
+ console.print(f"[green]✓[/green] Published: {ontology_id} → v{data.get('version', '?')}")
165
+ except APIError as e:
166
+ console.print(f"[red]Error:[/red] {e.detail}")
167
+ raise typer.Exit(1)
168
+
169
+
170
+ @app.command("versions")
171
+ def list_versions(ontology_id: str = typer.Argument(help="Ontology ID")):
172
+ """List published versions of an ontology."""
173
+ try:
174
+ data = gateway_get(f"/build/ontology/{ontology_id}/versions")
175
+ except APIError as e:
176
+ console.print(f"[red]Error:[/red] {e.detail}")
177
+ raise typer.Exit(1)
178
+
179
+ versions = data.get("versions", [])
180
+ if not versions:
181
+ console.print("[dim]No published versions yet.[/dim]")
182
+ return
183
+
184
+ table = Table(title=f"Versions — {ontology_id}")
185
+ table.add_column("Version", style="bold")
186
+ table.add_column("Entities", justify="right")
187
+ table.add_column("Published")
188
+
189
+ for v in versions:
190
+ table.add_row(f"v{v.get('version', '?')}", str(v.get("entities", 0)), v.get("published_at", "—"))
191
+
192
+ console.print(table)
193
+
194
+
195
+ @app.command("delete")
196
+ def delete_ontology(
197
+ ontology_id: str = typer.Argument(help="Ontology ID to delete"),
198
+ confirm: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
199
+ ):
200
+ """Delete an ontology."""
201
+ if not confirm:
202
+ typer.confirm(f"Delete ontology '{ontology_id}'? This cannot be undone", abort=True)
203
+
204
+ try:
205
+ gateway_delete(f"/build/ontology/{ontology_id}")
206
+ console.print(f"[green]✓[/green] Deleted: {ontology_id}")
207
+ except APIError as e:
208
+ console.print(f"[red]Error:[/red] {e.detail}")
209
+ raise typer.Exit(1)
@@ -0,0 +1,50 @@
1
+ """prox platform — health, status, service management."""
2
+
3
+ import typer
4
+ from rich.console import Console
5
+ from rich.table import Table
6
+ from ..api import gateway_get, APIError
7
+ from ..config import get_value
8
+
9
+ app = typer.Typer(help="Platform health and status.")
10
+ console = Console()
11
+
12
+
13
+ @app.command("health")
14
+ def health():
15
+ """Check platform health."""
16
+ gateway = get_value("gateway") or "not configured"
17
+ console.print(f"\n [bold]Platform Health[/bold] ({gateway})\n")
18
+
19
+ services = [
20
+ ("Gateway", "/health"),
21
+ ("Orchestrator", "/orchestrator/health"),
22
+ ("Knowledge", "/knowledge/health"),
23
+ ("Runtime", "/runtime/health"),
24
+ ]
25
+
26
+ for name, path in services:
27
+ try:
28
+ data = gateway_get(path)
29
+ status = data.get("status", "ok")
30
+ console.print(f" [green]●[/green] {name:15} {status}")
31
+ except APIError:
32
+ console.print(f" [red]●[/red] {name:15} unreachable")
33
+ except Exception:
34
+ console.print(f" [red]●[/red] {name:15} error")
35
+
36
+ console.print()
37
+
38
+
39
+ @app.command("status")
40
+ def status():
41
+ """Show platform status summary."""
42
+ try:
43
+ data = gateway_get("/health")
44
+ console.print(f"\n Gateway: [green]healthy[/green]")
45
+ console.print(f" Version: {data.get('version', '—')}")
46
+ console.print(f" Agents: {data.get('agents_registered', '—')}")
47
+ console.print(f" Uptime: {data.get('uptime', '—')}")
48
+ except APIError as e:
49
+ console.print(f" Gateway: [red]error[/red] ({e.detail[:100]})")
50
+ console.print()
@@ -0,0 +1,84 @@
1
+ """prox routine — manage scheduled agent execution."""
2
+
3
+ from pathlib import Path
4
+ from typing import Optional
5
+ import typer
6
+ from rich.console import Console
7
+ from rich.table import Table
8
+ from ..api import gateway_get, gateway_post, gateway_delete, APIError
9
+
10
+ app = typer.Typer(help="Manage routines (scheduled agent execution).")
11
+ console = Console()
12
+
13
+
14
+ @app.command("list")
15
+ def list_routines():
16
+ """List routines."""
17
+ try:
18
+ data = gateway_get("/build/routines")
19
+ routines = data.get("routines", [])
20
+ if not routines:
21
+ console.print("[dim]No routines.[/dim]")
22
+ return
23
+ table = Table(title="Routines")
24
+ table.add_column("ID", style="bold")
25
+ table.add_column("Agent")
26
+ table.add_column("Trigger")
27
+ table.add_column("Status")
28
+ for r in routines:
29
+ trigger = r.get("trigger", {})
30
+ ttype = trigger.get("type", "manual") if isinstance(trigger, dict) else str(trigger)
31
+ st = r.get("status", "draft")
32
+ st_style = "green" if st == "active" else "dim"
33
+ table.add_row(r.get("id"), r.get("agent_id", ""), ttype, f"[{st_style}]{st}[/{st_style}]")
34
+ console.print(table)
35
+ except APIError as e:
36
+ console.print(f"[red]Error:[/red] {e.detail}")
37
+ raise typer.Exit(1)
38
+
39
+
40
+ @app.command("create")
41
+ def create_routine(
42
+ id: str = typer.Option(..., "--id"),
43
+ agent: str = typer.Option(..., "--agent", help="Agent ID"),
44
+ prompt: str = typer.Option(..., "--prompt", help="Prompt to send"),
45
+ cron: Optional[str] = typer.Option(None, "--cron", help="Cron schedule (e.g. '0 6 * * *')"),
46
+ ):
47
+ """Create a routine."""
48
+ payload = {"id": id, "agent_id": agent, "prompt": prompt, "status": "active"}
49
+ if cron:
50
+ payload["trigger"] = {"type": "cron", "schedule": cron}
51
+ else:
52
+ payload["trigger"] = {"type": "manual"}
53
+ try:
54
+ gateway_post("/build/routines", payload)
55
+ console.print(f"[green]✓[/green] Routine created: {id}")
56
+ except APIError as e:
57
+ console.print(f"[red]Error:[/red] {e.detail}")
58
+ raise typer.Exit(1)
59
+
60
+
61
+ @app.command("trigger")
62
+ def trigger_routine(routine_id: str = typer.Argument(help="Routine ID")):
63
+ """Manually trigger a routine."""
64
+ try:
65
+ data = gateway_post(f"/build/routines/{routine_id}/trigger")
66
+ console.print(f"[green]✓[/green] Triggered: {routine_id}")
67
+ if data.get("run_id"):
68
+ console.print(f" Run ID: {data['run_id']}")
69
+ except APIError as e:
70
+ console.print(f"[red]Error:[/red] {e.detail}")
71
+ raise typer.Exit(1)
72
+
73
+
74
+ @app.command("delete")
75
+ def delete_routine(routine_id: str = typer.Argument(), confirm: bool = typer.Option(False, "--yes", "-y")):
76
+ """Delete a routine."""
77
+ if not confirm:
78
+ typer.confirm(f"Delete routine '{routine_id}'?", abort=True)
79
+ try:
80
+ gateway_delete(f"/build/routines/{routine_id}")
81
+ console.print(f"[green]✓[/green] Deleted: {routine_id}")
82
+ except APIError as e:
83
+ console.print(f"[red]Error:[/red] {e.detail}")
84
+ raise typer.Exit(1)
@@ -0,0 +1,59 @@
1
+ """prox secret — manage platform secrets."""
2
+
3
+ from typing import Optional
4
+ import typer
5
+ from rich.console import Console
6
+ from rich.table import Table
7
+ from ..api import gateway_get, gateway_post, gateway_delete, APIError
8
+
9
+ app = typer.Typer(help="Manage platform secrets.")
10
+ console = Console()
11
+
12
+
13
+ @app.command("list")
14
+ def list_secrets():
15
+ """List secrets (names only, never values)."""
16
+ try:
17
+ data = gateway_get("/build/secrets")
18
+ secrets = data.get("secrets", [])
19
+ if not secrets:
20
+ console.print("[dim]No secrets.[/dim]")
21
+ return
22
+ for s in secrets:
23
+ console.print(f" • {s.get('name', s) if isinstance(s, dict) else s}")
24
+ except APIError as e:
25
+ console.print(f"[red]Error:[/red] {e.detail}")
26
+ raise typer.Exit(1)
27
+
28
+
29
+ @app.command("set")
30
+ def set_secret(
31
+ name: str = typer.Argument(help="Secret name"),
32
+ value: Optional[str] = typer.Option(None, "--value", "-v", help="Secret value"),
33
+ from_file: Optional[str] = typer.Option(None, "--from-file", help="Read value from file"),
34
+ ):
35
+ """Set a secret."""
36
+ if from_file:
37
+ from pathlib import Path
38
+ value = Path(from_file).read_text().strip()
39
+ if not value:
40
+ value = typer.prompt("Secret value", hide_input=True)
41
+ try:
42
+ gateway_post("/build/secrets", {"name": name, "value": value})
43
+ console.print(f"[green]✓[/green] Secret set: {name}")
44
+ except APIError as e:
45
+ console.print(f"[red]Error:[/red] {e.detail}")
46
+ raise typer.Exit(1)
47
+
48
+
49
+ @app.command("delete")
50
+ def delete_secret(name: str = typer.Argument(), confirm: bool = typer.Option(False, "--yes", "-y")):
51
+ """Delete a secret."""
52
+ if not confirm:
53
+ typer.confirm(f"Delete secret '{name}'?", abort=True)
54
+ try:
55
+ gateway_delete(f"/build/secrets/{name}")
56
+ console.print(f"[green]✓[/green] Deleted: {name}")
57
+ except APIError as e:
58
+ console.print(f"[red]Error:[/red] {e.detail}")
59
+ raise typer.Exit(1)