buildai-cli 0.3.34__tar.gz → 0.3.36__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 (47) hide show
  1. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/.gitignore +5 -0
  2. buildai_cli-0.3.36/CLAUDE 2.md +35 -0
  3. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/CLAUDE.md +1 -3
  4. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/PKG-INFO +1 -1
  5. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/commands/database.py +59 -92
  6. buildai_cli-0.3.36/cli/commands/jobs.py +193 -0
  7. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/commands/medoid.py +2 -2
  8. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/config.py +0 -18
  9. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/context.py +4 -19
  10. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/main.py +1 -6
  11. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/ops_init.py +5 -122
  12. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/pyproject.toml +1 -1
  13. buildai_cli-0.3.34/cli/commands/dev.py +0 -279
  14. buildai_cli-0.3.34/cli/commands/jobs.py +0 -344
  15. buildai_cli-0.3.34/cli/commands/permissions.py +0 -160
  16. buildai_cli-0.3.34/cli/dev_context.py +0 -365
  17. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/AGENTS.md +0 -0
  18. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/__init__.py +0 -0
  19. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/_has_core.py +0 -0
  20. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/commands/__init__.py +0 -0
  21. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/commands/api_proxy.py +0 -0
  22. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/commands/assets_cli.py +0 -0
  23. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/commands/auth_lite.py +0 -0
  24. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/commands/clips.py +0 -0
  25. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/commands/embed.py +0 -0
  26. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/commands/external.py +0 -0
  27. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/commands/inference.py +0 -0
  28. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/commands/keys.py +0 -0
  29. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/commands/operations.py +0 -0
  30. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/commands/partners.py +0 -0
  31. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/commands/projection.py +0 -0
  32. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/commands/query.py +0 -0
  33. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/commands/query_api.py +0 -0
  34. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/commands/reports.py +0 -0
  35. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/commands/schema.py +0 -0
  36. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/commands/search.py +0 -0
  37. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/commands/stats.py +0 -0
  38. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/commands/sync/ddl.py +0 -0
  39. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/commands/sync/models.py +0 -0
  40. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/commands/sync/queries.py +0 -0
  41. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/console.py +0 -0
  42. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/guard.py +0 -0
  43. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/nl_query/__init__.py +0 -0
  44. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/nl_query/dataset_tools.py +0 -0
  45. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/output.py +0 -0
  46. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/pagination.py +0 -0
  47. {buildai_cli-0.3.34 → buildai_cli-0.3.36}/cli/sdk_client.py +0 -0
@@ -27,6 +27,7 @@ test-results/
27
27
  # Node
28
28
  node_modules/
29
29
  .next/
30
+ .expect/
30
31
  out/
31
32
 
32
33
  # IDE
@@ -70,6 +71,7 @@ reference/
70
71
  scripts/output/
71
72
  scripts/dead_assets.json
72
73
  scripts/dead_assets.ids.txt
74
+ scripts/figure_h264_1000h/
73
75
 
74
76
  # Service account keys
75
77
  *-sa-key.json
@@ -85,3 +87,6 @@ scripts/dead_assets.ids.txt
85
87
  *.ts.net.key
86
88
  test-buildai-data/
87
89
  .claude/worktrees/
90
+
91
+ # Generated multi-service app factory (local dev only)
92
+ _combined_factory.py
@@ -0,0 +1,35 @@
1
+ # BuildAI CLI
2
+
3
+ Typer-based CLI with two modes: standalone (PyPI, API-backed) and workspace (repo-local, DB-direct).
4
+
5
+ ## Two Planes
6
+
7
+ | Mode | Install | Auth | DB Access |
8
+ |------|---------|------|-----------|
9
+ | Standalone (`buildai`) | `uv tool install buildai-cli` | API key / JWT | Through API |
10
+ | Workspace (`uv run buildai`) | Repo-local editable install | IAM / password | Direct via `admin` |
11
+
12
+ ## Key Commands
13
+
14
+ ```bash
15
+ buildai query "SELECT count(*) FROM core.clips" # API-backed
16
+ buildai admin query "SELECT count(*) FROM core.clips" # DB-direct
17
+ buildai admin schema tables # Schema introspection
18
+ buildai admin schema describe core.clips # Table details
19
+ buildai admin --write database migrate all # Run migrations
20
+ buildai admin database diff --from preview --to production # Migration delta
21
+ ```
22
+
23
+ ## Guards
24
+
25
+ - `admin` subcommands require workspace install + gcloud IAM.
26
+ - Writes require `--write` flag.
27
+ - Production migrations prompt for confirmation.
28
+ - Worktree app/runtime targeting comes from explicit env vars and the repo Makefile, not a saved CLI context layer.
29
+ - `buildai admin --env` selects the explicit DB lane: `production`, `preview`, `dev`.
30
+ - Do not confuse `--env preview` with any old local preset names. The CLI no longer manages worktree deployment profiles.
31
+
32
+ ## Reference
33
+
34
+ CLI mode guide: `docs/how-to/choose-buildai-mode.md`
35
+ Database roles: `docs/database-roles.md`
@@ -16,8 +16,6 @@ buildai query "SELECT count(*) FROM core.clips" # API-backed
16
16
  buildai admin query "SELECT count(*) FROM core.clips" # DB-direct
17
17
  buildai admin schema tables # Schema introspection
18
18
  buildai admin schema describe core.clips # Table details
19
- uv run buildai dev use local # Set this worktree's deployment profile
20
- uv run buildai dev current # Show resolved worktree target
21
19
  buildai admin --write database migrate all # Run migrations
22
20
  buildai admin database status # Migration status
23
21
  ```
@@ -27,7 +25,7 @@ buildai admin database status # Migration status
27
25
  - `admin` subcommands require workspace install + gcloud IAM.
28
26
  - Writes require `--write` flag.
29
27
  - Production migrations prompt for confirmation.
30
- - Worktree app/runtime targeting comes from `uv run buildai dev use <profile>`.
28
+ - Worktree app/runtime targeting comes from explicit env vars and the repo Makefile, not a saved CLI context layer.
31
29
  - `buildai admin --env` selects the explicit DB lane: `production`, `dev`.
32
30
 
33
31
  ## Reference
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: buildai-cli
3
- Version: 0.3.34
3
+ Version: 0.3.36
4
4
  Summary: Build AI CLI (Typer)
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: buildai-data
@@ -12,7 +12,6 @@ All database operations under one command group:
12
12
  buildai admin database discover # Retired legacy discovery flow
13
13
  buildai admin database status # Overall database health summary
14
14
  buildai admin database roles # Role management
15
- buildai admin database roles-setup # Apply least-privilege roles/grants
16
15
  buildai admin database audit # Permission auditing
17
16
  buildai admin database seed # Run seed data scripts
18
17
  """
@@ -26,9 +25,7 @@ from typing import Any, Sequence
26
25
 
27
26
  import asyncpg
28
27
  import typer
29
- from infra.auth_principal_targets import get_runtime_targets_with_database_access
30
- from infra.migration_tracking import audit_migration_tracking, load_tracker_policy
31
- from infra.settings import Settings, switch_environment
28
+ from api.service_catalog import list_api_services
32
29
  from rich.panel import Panel
33
30
  from rich.syntax import Syntax
34
31
  from rich.table import Table
@@ -62,6 +59,8 @@ from cli.context import (
62
59
  resolve_admin_connection_config,
63
60
  )
64
61
  from cli.guard import require_write
62
+ from infra.migration_tracking import audit_migration_tracking, load_tracker_policy
63
+ from infra.settings import Settings, switch_environment
65
64
 
66
65
  # =============================================================================
67
66
  # Constants
@@ -84,7 +83,7 @@ async def _get_admin_connection(settings: Settings):
84
83
 
85
84
  Resolution order:
86
85
  1. ALLOYDB_ADMIN_IAM_USER_* env vars (explicit override)
87
- 2. deployment_profiles.yaml database_admin section (canonical source of truth)
86
+ 2. explicit ALLOYDB_ADMIN_IAM_USER_* / ALLOYDB_ADMIN_IMPERSONATE_SA_* env wiring
88
87
  3. ALLOYDB_PSW_PROD password fallback
89
88
  """
90
89
  return await open_admin_database(settings)
@@ -227,6 +226,60 @@ class DatabaseStatus:
227
226
  return "READY FOR TESTING"
228
227
 
229
228
 
229
+ @dataclass(frozen=True)
230
+ class _RuntimeDbTarget:
231
+ service_slug: str
232
+ gcp_service_account_email: str
233
+ alloydb_iam_user: str
234
+
235
+
236
+ def _runtime_db_targets() -> list[_RuntimeDbTarget]:
237
+ """Return the current runtime principals that should exist as AlloyDB IAM users."""
238
+ api_targets = [
239
+ _RuntimeDbTarget(
240
+ service_slug=service.service_slug,
241
+ gcp_service_account_email=service.runtime_service_account,
242
+ alloydb_iam_user=service.runtime_db_user,
243
+ )
244
+ for service in list_api_services()
245
+ ]
246
+ worker_targets = [
247
+ _RuntimeDbTarget(
248
+ service_slug="mcp",
249
+ gcp_service_account_email="mcp-sa@data-470400.iam.gserviceaccount.com",
250
+ alloydb_iam_user="mcp-sa@data-470400.iam",
251
+ ),
252
+ _RuntimeDbTarget(
253
+ service_slug="frame-extractor",
254
+ gcp_service_account_email="frame-extractor-sa@data-470400.iam.gserviceaccount.com",
255
+ alloydb_iam_user="frame-extractor-sa@data-470400.iam",
256
+ ),
257
+ _RuntimeDbTarget(
258
+ service_slug="frame-embed",
259
+ gcp_service_account_email="frame-embed-sa@data-470400.iam.gserviceaccount.com",
260
+ alloydb_iam_user="frame-embed-sa@data-470400.iam",
261
+ ),
262
+ _RuntimeDbTarget(
263
+ service_slug="frame-inference",
264
+ gcp_service_account_email="frame-inference-sa@data-470400.iam.gserviceaccount.com",
265
+ alloydb_iam_user="frame-inference-sa@data-470400.iam",
266
+ ),
267
+ _RuntimeDbTarget(
268
+ service_slug="migrations-runner",
269
+ gcp_service_account_email="migrations-runner-sa@data-470400.iam.gserviceaccount.com",
270
+ alloydb_iam_user="migrations-runner-sa@data-470400.iam",
271
+ ),
272
+ ]
273
+ seen: set[str] = set()
274
+ targets: list[_RuntimeDbTarget] = []
275
+ for target in [*api_targets, *worker_targets]:
276
+ if target.gcp_service_account_email in seen:
277
+ continue
278
+ seen.add(target.gcp_service_account_email)
279
+ targets.append(target)
280
+ return targets
281
+
282
+
230
283
  def _migration_requires_system_admin(path: Path) -> bool:
231
284
  for line in path.read_text(encoding="utf-8").splitlines()[:10]:
232
285
  if line.strip().lower() == "-- requires-system-admin: true":
@@ -1392,90 +1445,6 @@ def roles(ctx: typer.Context) -> None:
1392
1445
  asyncio.run(run())
1393
1446
 
1394
1447
 
1395
- @app.command("roles-setup")
1396
- def roles_setup(
1397
- ctx: typer.Context,
1398
- dry_run: Annotated[bool, typer.Option("--dry-run", help="Show SQL without executing")] = False,
1399
- as_admin: Annotated[
1400
- bool,
1401
- typer.Option(
1402
- "--as-admin",
1403
- help="Connect as postgres user via password auth (requires ALLOYDB_PSW_PROD)",
1404
- ),
1405
- ] = False,
1406
- yes: Annotated[
1407
- bool, typer.Option("--yes", "-y", help="Skip confirmation for production")
1408
- ] = False,
1409
- ) -> None:
1410
- """
1411
- Apply least-privilege role and grant setup.
1412
-
1413
- Uses scripts/ops/create-db-roles-leastpriv.sql.
1414
- """
1415
- settings: Settings = ctx.obj["settings"]
1416
-
1417
- if not dry_run:
1418
- require_write(ctx, "Database role setup")
1419
-
1420
- sql_path = REPO_ROOT / "scripts" / "ops" / "create-db-roles-leastpriv.sql"
1421
-
1422
- if not sql_path.exists():
1423
- error(f"SQL file not found: {sql_path}")
1424
- raise typer.Exit(1)
1425
-
1426
- sql_content = sql_path.read_text()
1427
-
1428
- if dry_run:
1429
- info("SQL to execute:")
1430
- console.print(Syntax(sql_content, "sql", theme="monokai", line_numbers=False))
1431
- return
1432
-
1433
- if settings.is_production and not yes:
1434
- warning("This will modify PRODUCTION database roles!")
1435
- if not typer.confirm("Are you sure you want to continue?"):
1436
- raise typer.Abort()
1437
-
1438
- async def run() -> None:
1439
- db = None
1440
- connection_context = None
1441
- using_owner_role = False
1442
-
1443
- try:
1444
- if as_admin:
1445
- info("Connecting as postgres (password auth)...")
1446
- db = await _get_admin_connection(settings)
1447
- conn = db.conn
1448
- else:
1449
- connection_context = get_connection(settings)
1450
- conn = await connection_context.__aenter__()
1451
- using_owner_role = await _set_owner_role(conn)
1452
- if using_owner_role:
1453
- dim("Using role: buildai_owner")
1454
- else:
1455
- dim("Role buildai_owner not available, using default permissions")
1456
-
1457
- await conn.execute(sql_content)
1458
- success("Role setup completed.")
1459
-
1460
- except Exception as e:
1461
- error(f"Role setup failed: {e}")
1462
- raise typer.Exit(1)
1463
-
1464
- finally:
1465
- if using_owner_role:
1466
- try:
1467
- await conn.execute("RESET ROLE")
1468
- except Exception:
1469
- pass
1470
-
1471
- if db is not None:
1472
- await db.close()
1473
- elif connection_context is not None:
1474
- await connection_context.__aexit__(None, None, None)
1475
-
1476
- asyncio.run(run())
1477
-
1478
-
1479
1448
  @app.command()
1480
1449
  def audit(
1481
1450
  ctx: typer.Context,
@@ -1502,7 +1471,7 @@ def audit(
1502
1471
 
1503
1472
  # Check IAM roles
1504
1473
  info("Checking GCP IAM roles...")
1505
- runtime_targets = get_runtime_targets_with_database_access()
1474
+ runtime_targets = _runtime_db_targets()
1506
1475
  required_roles = ["roles/alloydb.client", "roles/alloydb.databaseUser"]
1507
1476
 
1508
1477
  iam_table = Table(title="IAM Role Audit")
@@ -1516,8 +1485,6 @@ def audit(
1516
1485
  for target in runtime_targets:
1517
1486
  sa_email = target.gcp_service_account_email
1518
1487
  db_login = target.alloydb_iam_user
1519
- if sa_email is None or db_login is None:
1520
- continue
1521
1488
  sa_name = sa_email.split("@", 1)[0]
1522
1489
  row = [sa_name, db_login]
1523
1490
 
@@ -0,0 +1,193 @@
1
+ """CLI commands for jobs (SDK-backed data-plane)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import itertools
6
+ import time
7
+ from typing import Any
8
+
9
+ import typer
10
+
11
+ from cli.console import info, success, warning
12
+ from cli.output import Format, format_option, output
13
+ from cli.sdk_client import get_sdk_client
14
+
15
+ app = typer.Typer(
16
+ name="jobs",
17
+ help="Manage processing jobs.",
18
+ no_args_is_help=True,
19
+ )
20
+
21
+
22
+ def _job_row(job: Any) -> dict[str, Any]:
23
+ return {
24
+ "job_id": job.job_id,
25
+ "job_type": job.job_type,
26
+ "status": job.status,
27
+ "progress_pct": getattr(job, "progress_pct", 0.0),
28
+ "total_items": getattr(job, "total_items", None),
29
+ "chunk_count": getattr(job, "chunk_count", None),
30
+ "created_at": getattr(job, "created_at", None),
31
+ }
32
+
33
+
34
+ def _launch_job(
35
+ client: Any,
36
+ job_id: str,
37
+ *,
38
+ max_tasks: int,
39
+ spot: bool,
40
+ platform: str,
41
+ region: str | None,
42
+ cpu: str,
43
+ memory: str,
44
+ ) -> Any:
45
+ return client.jobs.submit(
46
+ job_id,
47
+ max_tasks=max_tasks,
48
+ spot=spot,
49
+ platform=platform,
50
+ region=region,
51
+ cpu=cpu,
52
+ memory=memory,
53
+ )
54
+
55
+
56
+ def _watch_job(client: Any, job_id: str, *, interval: float) -> None:
57
+ last_line: str | None = None
58
+ while True:
59
+ progress = client.jobs.progress(job_id)
60
+ line = (
61
+ f"{progress.status} "
62
+ f"chunks={progress.chunks_succeeded + progress.chunks_failed}/{progress.total_chunks} "
63
+ f"items={progress.items_succeeded} ok/{progress.items_failed} failed "
64
+ f"items_per_sec={progress.items_per_second} "
65
+ f"chunks_per_sec={progress.chunks_per_second} "
66
+ f"eta_s={progress.eta_seconds}"
67
+ )
68
+ if line != last_line:
69
+ info(f"{job_id}: {line}")
70
+ last_line = line
71
+ if progress.status in {"succeeded", "failed", "cancelled"}:
72
+ if progress.status != "succeeded":
73
+ failures = client.jobs.failures(job_id, page_size=5)
74
+ if failures.data:
75
+ for failure in failures.data:
76
+ warning(
77
+ f"{failure.entity_id} | "
78
+ f"{failure.error_code or 'error'} | "
79
+ f"{failure.error_message or ''}"
80
+ )
81
+ return
82
+ time.sleep(interval)
83
+
84
+
85
+ @app.command("list")
86
+ def jobs_list(
87
+ status: str | None = typer.Option(None, "--status", "-s", help="Filter by status"),
88
+ type: str | None = typer.Option(None, "--type", "-t", help="Filter by job type"),
89
+ limit: int = typer.Option(20, "--limit", "-n"),
90
+ format: Format | None = format_option(),
91
+ ) -> None:
92
+ """List processing jobs."""
93
+ client = get_sdk_client()
94
+ results = list(
95
+ itertools.islice(
96
+ client.jobs.iter(status=status, type=type),
97
+ limit,
98
+ )
99
+ )
100
+ output(
101
+ [_job_row(job) for job in results],
102
+ format=format,
103
+ columns=["job_id", "job_type", "status", "progress_pct", "total_items", "chunk_count"],
104
+ )
105
+
106
+
107
+ @app.command("get")
108
+ def jobs_get(
109
+ job_id: str = typer.Argument(..., help="Manifest ID"),
110
+ format: Format | None = format_option(),
111
+ ) -> None:
112
+ """Get details for a single job."""
113
+ client = get_sdk_client()
114
+ job = client.jobs.get(job_id)
115
+ output(job, format=format)
116
+
117
+
118
+ @app.command("submit")
119
+ def jobs_submit(
120
+ ctx: typer.Context,
121
+ job_id: str = typer.Argument(..., help="Manifest ID"),
122
+ max_tasks: int = typer.Option(750, "--max-tasks"),
123
+ spot: bool = typer.Option(True, "--spot/--no-spot"),
124
+ platform: str = typer.Option("auto", "--platform"),
125
+ region: str | None = typer.Option(None, "--region"),
126
+ cpu: str = typer.Option("8", "--cpu"),
127
+ memory: str = typer.Option("8Gi", "--memory"),
128
+ watch: bool = typer.Option(False, "--watch"),
129
+ interval: float = typer.Option(5.0, "--interval"),
130
+ ) -> None:
131
+ """Submit an existing manifest to execution."""
132
+ del ctx # API enforces permissions via token scope
133
+ client = get_sdk_client()
134
+ result = _launch_job(
135
+ client,
136
+ job_id,
137
+ max_tasks=max_tasks,
138
+ spot=spot,
139
+ platform=platform,
140
+ region=region,
141
+ cpu=cpu,
142
+ memory=memory,
143
+ )
144
+ success(
145
+ f"Submitted {job_id} on {result.platform} "
146
+ f"as {result.batch_job_name} with {result.task_count} tasks"
147
+ )
148
+ if watch:
149
+ _watch_job(client, job_id, interval=interval)
150
+
151
+
152
+ @app.command("watch")
153
+ def jobs_watch(
154
+ job_id: str = typer.Argument(..., help="Manifest ID"),
155
+ interval: float = typer.Option(5.0, "--interval"),
156
+ ) -> None:
157
+ """Watch a job until completion."""
158
+ client = get_sdk_client()
159
+ _watch_job(client, job_id, interval=interval)
160
+
161
+
162
+ @app.command("retry")
163
+ def jobs_retry(
164
+ ctx: typer.Context,
165
+ job_id: str = typer.Argument(..., help="Manifest ID"),
166
+ dry_run: bool = typer.Option(False, "--dry-run"),
167
+ ) -> None:
168
+ """Retry a failed job."""
169
+ if dry_run:
170
+ info(f"Would retry job {job_id}")
171
+ return
172
+
173
+ del ctx # API enforces permissions via token scope
174
+ client = get_sdk_client()
175
+ client.jobs.retry(job_id)
176
+ success(f"Retried job {job_id}")
177
+
178
+
179
+ @app.command("cancel")
180
+ def jobs_cancel(
181
+ ctx: typer.Context,
182
+ job_id: str = typer.Argument(..., help="Manifest ID"),
183
+ dry_run: bool = typer.Option(False, "--dry-run"),
184
+ ) -> None:
185
+ """Cancel a running job."""
186
+ if dry_run:
187
+ info(f"Would cancel job {job_id}")
188
+ return
189
+
190
+ del ctx # API enforces permissions via token scope
191
+ client = get_sdk_client()
192
+ client.jobs.cancel(job_id)
193
+ success(f"Cancelled job {job_id}")
@@ -239,7 +239,7 @@ async def _compute(
239
239
  ) -> None:
240
240
  from dal.processing import jobs
241
241
 
242
- from services.load_config import load_service_config, resolve_profile
242
+ from processing_runtime.profile_catalog import load_runtime_profile, resolve_profile
243
243
 
244
244
  settings = ctx.obj["settings"]
245
245
 
@@ -300,7 +300,7 @@ async def _compute(
300
300
  raise typer.Exit(1)
301
301
 
302
302
  # Get GPU config based on data size for display
303
- embedding_config = load_service_config("media_embedding")
303
+ embedding_config = load_runtime_profile("embedding_runtime")
304
304
  batch_config = resolve_profile(
305
305
  embedding_config,
306
306
  job_type="compute_medoids",
@@ -7,8 +7,6 @@ import os
7
7
  from pathlib import Path
8
8
  from typing import Any
9
9
 
10
- from cli.dev_context import load_dev_context
11
-
12
10
  CONFIG_DIR = Path.home() / ".buildai"
13
11
  CREDENTIALS_PATH = CONFIG_DIR / "credentials.json"
14
12
  DEFAULT_API_URL = "https://api.build.ai"
@@ -41,13 +39,6 @@ def clear_credential() -> None:
41
39
  CREDENTIALS_PATH.unlink()
42
40
 
43
41
 
44
- def _workspace_context() -> dict[str, Any] | None:
45
- try:
46
- return load_dev_context()
47
- except Exception:
48
- return None
49
-
50
-
51
42
  def _clean_str(value: Any) -> str | None:
52
43
  if not isinstance(value, str):
53
44
  return None
@@ -74,11 +65,6 @@ def resolve_api_url(*, include_workspace: bool = True) -> str:
74
65
  env_api_url = os.getenv("BUILDAI_API_URL")
75
66
  if env_api_url:
76
67
  return env_api_url.rstrip("/")
77
- if include_workspace:
78
- workspace_context = _workspace_context()
79
- workspace_api_url = _clean_str((workspace_context or {}).get("api_url"))
80
- if workspace_api_url:
81
- return workspace_api_url.rstrip("/")
82
68
  api_url = load_credentials().get("api_url")
83
69
  if isinstance(api_url, str) and api_url:
84
70
  return api_url.rstrip("/")
@@ -109,8 +95,4 @@ def resolve_cli_profile(explicit: str | None = None) -> str:
109
95
  env_profile = _clean_str(os.getenv("BUILDAI_CLI_PROFILE"))
110
96
  if env_profile:
111
97
  return env_profile
112
- workspace_context = _workspace_context()
113
- workspace_profile = _clean_str((workspace_context or {}).get("cli_access_profile"))
114
- if workspace_profile:
115
- return workspace_profile
116
98
  return "internal_viewer"
@@ -22,9 +22,9 @@ from dataclasses import dataclass
22
22
  from typing import TYPE_CHECKING, AsyncGenerator
23
23
 
24
24
  import asyncpg
25
- from infra.settings import Settings, get_settings
26
25
 
27
26
  from infra import Database, get_logger
27
+ from infra.settings import Settings, get_settings
28
28
 
29
29
  if TYPE_CHECKING:
30
30
  from dal.context import Context
@@ -38,7 +38,6 @@ _UNRESTRICTED_CLI_PROFILES = frozenset(
38
38
  {
39
39
  "internal_admin",
40
40
  "internal_viewer",
41
- "analyst",
42
41
  "developer",
43
42
  "operator",
44
43
  "ml_engineer",
@@ -82,25 +81,12 @@ def resolve_admin_connection_config(
82
81
  ) -> AdminConnectionConfig | None:
83
82
  """Resolve the canonical admin DB login for this environment, if one exists.
84
83
 
85
- Resolution order is explicit env overrides first, then the checked-in
86
- deployment profile, then the production password fallback already used by
87
- privileged database commands.
84
+ Resolution order is explicit env overrides first, then the production
85
+ password fallback already used by privileged database commands.
88
86
  """
89
- from infra.deployment_profiles import resolve_deployment_profile
90
-
91
87
  admin_iam_user = settings.effective_alloydb_admin_iam_user
92
88
  admin_impersonate_sa = settings.effective_alloydb_admin_impersonate_sa
93
89
 
94
- if not admin_iam_user or not admin_impersonate_sa:
95
- try:
96
- profile_name = os.environ.get("BUILDAI_DEPLOYMENT_PROFILE", "production")
97
- profile = resolve_deployment_profile(profile_name)
98
- if profile.database_admin:
99
- admin_iam_user = admin_iam_user or profile.database_admin.iam_user
100
- admin_impersonate_sa = admin_impersonate_sa or profile.database_admin.impersonate_sa
101
- except (ValueError, KeyError):
102
- pass
103
-
104
90
  if admin_iam_user and admin_impersonate_sa:
105
91
  return AdminConnectionConfig(
106
92
  user=admin_iam_user,
@@ -137,8 +123,7 @@ async def open_admin_database(settings: Settings) -> Database:
137
123
  if config is None:
138
124
  raise RuntimeError(
139
125
  "Admin credentials not configured for this environment. "
140
- "Set ALLOYDB_ADMIN_IAM_USER_* / ALLOYDB_ADMIN_IMPERSONATE_SA_* or "
141
- "define database_admin in deployment_profiles.yaml."
126
+ "Set ALLOYDB_ADMIN_IAM_USER_* / ALLOYDB_ADMIN_IMPERSONATE_SA_*."
142
127
  )
143
128
 
144
129
  ip_type = "PRIVATE" if settings.use_private_ip else "PUBLIC"
@@ -18,7 +18,6 @@ from cli.commands.api_proxy import api
18
18
  from cli.commands.assets_cli import app as assets_app
19
19
  from cli.commands.auth_lite import login, logout, whoami
20
20
  from cli.commands.clips import app as clips_app
21
- from cli.commands.dev import app as dev_app
22
21
  from cli.commands.inference import app as inference_app
23
22
  from cli.commands.jobs import app as jobs_app
24
23
  from cli.commands.query_api import query
@@ -208,7 +207,6 @@ Build AI CLI — query data, run inference, sign media URLs.
208
207
 
209
208
  Get started:
210
209
  uv tool install buildai-cli Install the standalone CLI
211
- uv run buildai dev use local Set this worktree's dev target
212
210
  buildai login Authenticate (opens browser)
213
211
  buildai whoami Show your identity and permissions
214
212
  buildai query "SELECT count(*) FROM core.clips"
@@ -253,7 +251,6 @@ app.command("whoami")(whoami)
253
251
  app.command("logout")(logout)
254
252
  app.command("query")(query)
255
253
 
256
- app.add_typer(dev_app, name="dev")
257
254
  app.add_typer(clips_app, name="clips")
258
255
  app.add_typer(assets_app, name="assets")
259
256
  app.add_typer(inference_app, name="inference")
@@ -275,7 +272,7 @@ def admin_callback(
275
272
  None,
276
273
  "--env",
277
274
  "-e",
278
- help="Target environment. Defaults to APP_ENV, then worktree context, then production.",
275
+ help="Target environment. Defaults to APP_ENV, then production.",
279
276
  ),
280
277
  auth: str = typer.Option(None, "--auth", "-a", help="Auth method: iam or password."),
281
278
  user: str = typer.Option(None, "--user", "-u", help="Override database user."),
@@ -306,7 +303,6 @@ if has_core():
306
303
  from cli.commands.medoid import app as medoid_app
307
304
  from cli.commands.operations import app as operations_app
308
305
  from cli.commands.partners import app as partners_app
309
- from cli.commands.permissions import app as permissions_app
310
306
  from cli.commands.projection import app as projection_app
311
307
  from cli.commands.query import query as admin_query
312
308
  from cli.commands.schema import app as schema_app
@@ -321,7 +317,6 @@ if has_core():
321
317
  admin_app.add_typer(embed_app, name="embeddings")
322
318
  admin_app.add_typer(database_app, name="database")
323
319
  admin_app.add_typer(external_app, name="external")
324
- admin_app.add_typer(permissions_app, name="permissions")
325
320
  admin_app.add_typer(projection_app, name="projection")
326
321
  admin_app.add_typer(medoid_app, name="medoid")
327
322