buildai-cli 0.3.35__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 (46) hide show
  1. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/CLAUDE 2.md +2 -4
  2. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/CLAUDE.md +1 -3
  3. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/PKG-INFO +1 -1
  4. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/commands/database.py +59 -92
  5. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/config.py +0 -18
  6. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/context.py +4 -19
  7. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/main.py +1 -6
  8. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/ops_init.py +5 -122
  9. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/pyproject.toml +1 -1
  10. buildai_cli-0.3.35/cli/commands/dev.py +0 -279
  11. buildai_cli-0.3.35/cli/commands/permissions.py +0 -160
  12. buildai_cli-0.3.35/cli/dev_context.py +0 -365
  13. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/.gitignore +0 -0
  14. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/AGENTS.md +0 -0
  15. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/__init__.py +0 -0
  16. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/_has_core.py +0 -0
  17. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/commands/__init__.py +0 -0
  18. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/commands/api_proxy.py +0 -0
  19. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/commands/assets_cli.py +0 -0
  20. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/commands/auth_lite.py +0 -0
  21. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/commands/clips.py +0 -0
  22. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/commands/embed.py +0 -0
  23. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/commands/external.py +0 -0
  24. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/commands/inference.py +0 -0
  25. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/commands/jobs.py +0 -0
  26. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/commands/keys.py +0 -0
  27. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/commands/medoid.py +0 -0
  28. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/commands/operations.py +0 -0
  29. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/commands/partners.py +0 -0
  30. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/commands/projection.py +0 -0
  31. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/commands/query.py +0 -0
  32. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/commands/query_api.py +0 -0
  33. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/commands/reports.py +0 -0
  34. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/commands/schema.py +0 -0
  35. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/commands/search.py +0 -0
  36. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/commands/stats.py +0 -0
  37. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/commands/sync/ddl.py +0 -0
  38. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/commands/sync/models.py +0 -0
  39. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/commands/sync/queries.py +0 -0
  40. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/console.py +0 -0
  41. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/guard.py +0 -0
  42. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/nl_query/__init__.py +0 -0
  43. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/nl_query/dataset_tools.py +0 -0
  44. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/output.py +0 -0
  45. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/pagination.py +0 -0
  46. {buildai_cli-0.3.35 → buildai_cli-0.3.36}/cli/sdk_client.py +0 -0
@@ -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 diff --from preview --to production # Migration delta
23
21
  ```
@@ -27,9 +25,9 @@ buildai admin database diff --from preview --to production # Migration delta
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`, `preview`, `dev`.
32
- - Do not confuse `--env preview` with the deployment profiles `local`, `preview`, or `staging`.
30
+ - Do not confuse `--env preview` with any old local preset names. The CLI no longer manages worktree deployment profiles.
33
31
 
34
32
  ## Reference
35
33
 
@@ -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.35
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
 
@@ -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
 
@@ -11,7 +11,6 @@ import os
11
11
  from enum import Enum
12
12
 
13
13
  import typer
14
- from infra.deployment_profiles import resolve_deployment_profile
15
14
 
16
15
  from cli.console import error, info, warning
17
16
 
@@ -34,87 +33,11 @@ def _parse_env(value: str) -> str:
34
33
  return normalized
35
34
 
36
35
 
37
- def _resolve_context_app_env(context: dict[str, object] | None) -> str | None:
38
- if not context:
39
- return None
40
- value = context.get("app_env")
41
- if not isinstance(value, str):
42
- return None
43
- cleaned = value.strip().lower()
44
- return cleaned or None
45
-
46
-
47
- def _apply_context_database_target(
48
- context: dict[str, object] | None,
49
- *,
50
- deployment_profile: str | None,
51
- ) -> None:
52
- database_target: dict[str, object] = {}
53
- runtime_target: dict[str, object] = {}
54
- if deployment_profile:
55
- resolved_profile = resolve_deployment_profile(
56
- deployment_profile,
57
- env_id=(
58
- str(context.get("preview_env_id")).strip()
59
- if isinstance(context, dict) and context.get("preview_env_id")
60
- else None
61
- ),
62
- )
63
- database_target = resolved_profile.database_target.as_context_dict()
64
- runtime_target["region"] = resolved_profile.runtime_region
65
- if context and (
66
- (context.get("deployment_profile") or context.get("profile")) == deployment_profile
67
- ):
68
- raw_runtime_target = context.get("runtime_target")
69
- if isinstance(raw_runtime_target, dict):
70
- runtime_target.update(raw_runtime_target)
71
- raw_database_target = context.get("database_target")
72
- if isinstance(raw_database_target, dict):
73
- database_target.update(raw_database_target)
74
-
75
- if database_target.get("cluster") or database_target.get("instance"):
76
- # Neutralize stale .env overrides so the resolved context target wins.
77
- os.environ.setdefault("ALLOYDB_INSTANCE_URI", "")
78
-
79
- cluster = database_target.get("cluster")
80
- if isinstance(cluster, str) and cluster.strip():
81
- os.environ.setdefault("ALLOYDB_CLUSTER", cluster.strip())
82
-
83
- instance = database_target.get("instance")
84
- if isinstance(instance, str) and instance.strip():
85
- os.environ.setdefault("ALLOYDB_INSTANCE", instance.strip())
86
-
87
- runtime_region = runtime_target.get("region")
88
- if isinstance(runtime_region, str) and runtime_region.strip():
89
- os.environ.setdefault("GCP_REGION", runtime_region.strip())
90
-
91
- region = database_target.get("region")
92
- if isinstance(region, str) and region.strip():
93
- os.environ.setdefault("ALLOYDB_REGION", region.strip())
94
-
95
- database_name = database_target.get("database")
96
- if isinstance(database_name, str) and database_name.strip():
97
- os.environ.setdefault("DB_NAME", database_name.strip())
98
-
99
- normalized_profile = (deployment_profile or "").strip().lower()
100
- if normalized_profile == "production":
101
- user_key = "ALLOYDB_USER_PROD"
102
- iam_key = "ALLOYDB_IAM_AUTH_PROD"
103
- else:
104
- user_key = "ALLOYDB_USER_PREVIEW"
105
- iam_key = "ALLOYDB_IAM_AUTH_PREVIEW"
106
-
107
- user = database_target.get("user")
108
- if isinstance(user, str) and user.strip():
109
- os.environ.setdefault(user_key, user.strip())
110
-
111
- if "use_iam_auth" in database_target:
112
- os.environ.setdefault(iam_key, "true" if bool(database_target["use_iam_auth"]) else "false")
113
-
114
-
115
36
  def _auth_env_prefix(settings_app_env: object) -> str:
116
37
  if str(settings_app_env) == "production":
117
38
  return "PROD"
39
+ if str(settings_app_env) == "development":
40
+ return "DEV"
118
41
  if str(settings_app_env) == "test":
119
42
  return "DEV"
120
43
  return "PREVIEW"
@@ -133,20 +56,17 @@ def init_ops_context(ctx: typer.Context):
133
56
  return ctx.obj["settings"]
134
57
 
135
58
  from cli._has_core import require_core
136
- from cli.dev_context import load_dev_context
137
59
 
138
60
  require_core("this command")
139
61
 
62
+ from infra import init_observability
140
63
  from infra.auth import AuthError, get_effective_user_for_iam, validate_auth_config
141
64
  from infra.settings import Environment, ExecutionContext, Settings, get_settings
142
65
 
143
- from infra import init_observability
144
-
145
66
  verbose = ctx.obj.get("_verbose", False)
146
67
  log_level = logging.DEBUG if verbose else logging.WARNING
147
68
  init_observability("cli", log_level=log_level, enable_json_logs=False)
148
69
 
149
- workspace_context = load_dev_context()
150
70
  env_flag = ctx.obj.get("_env")
151
71
  auth_flag = ctx.obj.get("_auth")
152
72
  user_flag = ctx.obj.get("_user")
@@ -161,40 +81,13 @@ def init_ops_context(ctx: typer.Context):
161
81
  )
162
82
  raise typer.Exit(1)
163
83
  else:
164
- app_env_str = (
165
- os.getenv("APP_ENV")
166
- or _resolve_context_app_env(workspace_context)
167
- or Environment.PRODUCTION.value
168
- )
84
+ app_env_str = os.getenv("APP_ENV") or Environment.PRODUCTION.value
169
85
  try:
170
86
  app_env = Environment(_parse_env(app_env_str))
171
87
  except ValueError:
172
88
  app_env = Environment.PRODUCTION
173
89
 
174
90
  os.environ["APP_ENV"] = app_env.value
175
- deployment_profile = None
176
- if isinstance(workspace_context, dict):
177
- profile_name = workspace_context.get("deployment_profile") or workspace_context.get(
178
- "profile"
179
- )
180
- if isinstance(profile_name, str) and profile_name.strip():
181
- deployment_profile = profile_name.strip()
182
- preview_env_id = workspace_context.get("preview_env_id")
183
- if isinstance(preview_env_id, str) and preview_env_id.strip():
184
- os.environ["BUILDAI_PREVIEW_ENV_ID"] = preview_env_id.strip()
185
- if env_flag is not None:
186
- deployment_profile = (
187
- ""
188
- if app_env == Environment.TEST
189
- else "local"
190
- if app_env == Environment.DEVELOPMENT
191
- else "production"
192
- )
193
- elif deployment_profile is None and app_env != Environment.TEST:
194
- deployment_profile = "local" if app_env == Environment.DEVELOPMENT else "production"
195
- if deployment_profile:
196
- os.environ["BUILDAI_DEPLOYMENT_PROFILE"] = deployment_profile
197
- _apply_context_database_target(workspace_context, deployment_profile=deployment_profile)
198
91
  settings = Settings(app_env=app_env, execution_context=ExecutionContext.HUMAN)
199
92
 
200
93
  # --- resolve auth -------------------------------------------------------
@@ -255,17 +148,7 @@ def init_ops_context(ctx: typer.Context):
255
148
  ctx.obj["settings"] = settings
256
149
  ctx.obj["_ops_ready"] = True
257
150
 
258
- context_profile = None
259
- if isinstance(workspace_context, dict):
260
- raw_profile = workspace_context.get("cli_access_profile")
261
- if isinstance(raw_profile, str) and raw_profile.strip():
262
- context_profile = raw_profile.strip()
263
- profile = (
264
- ctx.obj.get("cli_profile")
265
- or os.getenv("BUILDAI_CLI_PROFILE")
266
- or context_profile
267
- or "internal_admin"
268
- )
151
+ profile = ctx.obj.get("cli_profile") or os.getenv("BUILDAI_CLI_PROFILE") or "internal_admin"
269
152
  ctx.obj["cli_profile"] = profile
270
153
  os.environ["BUILDAI_CLI_PROFILE"] = profile
271
154
  if ctx.obj.get("allow_write"):
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "buildai-cli"
7
- version = "0.3.35"
7
+ version = "0.3.36"
8
8
  description = "Build AI CLI (Typer)"
9
9
  requires-python = ">=3.11"
10
10
  dependencies = [