buildai-cli 0.3.72__tar.gz → 0.3.74__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 (36) hide show
  1. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/.gitignore +23 -0
  2. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/PKG-INFO +1 -1
  3. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/cli/commands/gigcamera.py +236 -1
  4. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/cli/commands/processing.py +2 -2
  5. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/pyproject.toml +1 -1
  6. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/AGENTS.md +0 -0
  7. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/CLAUDE.md +0 -0
  8. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/buildai_bootstrap.py +0 -0
  9. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/cli/__init__.py +0 -0
  10. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/cli/_has_core.py +0 -0
  11. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/cli/auth_local.py +0 -0
  12. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/cli/commands/__init__.py +0 -0
  13. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/cli/commands/api_proxy.py +0 -0
  14. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/cli/commands/auth.py +0 -0
  15. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/cli/commands/db/__init__.py +0 -0
  16. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/cli/commands/db/broker.py +0 -0
  17. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/cli/commands/db/common.py +0 -0
  18. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/cli/commands/db/migrate.py +0 -0
  19. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/cli/commands/db/query.py +0 -0
  20. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/cli/commands/db/schema.py +0 -0
  21. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/cli/commands/db/status.py +0 -0
  22. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/cli/commands/db/tunnel.py +0 -0
  23. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/cli/commands/dev.py +0 -0
  24. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/cli/commands/doctor.py +0 -0
  25. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/cli/config.py +0 -0
  26. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/cli/console.py +0 -0
  27. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/cli/context.py +0 -0
  28. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/cli/db_broker.py +0 -0
  29. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/cli/guard.py +0 -0
  30. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/cli/internal_api.py +0 -0
  31. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/cli/main.py +0 -0
  32. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/cli/nl_query/__init__.py +0 -0
  33. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/cli/nl_query/dataset_tools.py +0 -0
  34. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/cli/ops_init.py +0 -0
  35. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/cli/output.py +0 -0
  36. {buildai_cli-0.3.72 → buildai_cli-0.3.74}/cli/pagination.py +0 -0
@@ -17,6 +17,10 @@ ENV/
17
17
  .eggs/
18
18
  *.egg-info/
19
19
  dist/
20
+ !apps/buildai-openai-ingest/frontend/
21
+ !apps/buildai-openai-ingest/frontend/dist/
22
+ !apps/buildai-openai-ingest/frontend/dist/index.html
23
+ !apps/buildai-openai-ingest/frontend/dist/mac-mini.png
20
24
  build/
21
25
  .pytest_cache/
22
26
  .mypy_cache/
@@ -55,6 +59,10 @@ Thumbs.db
55
59
 
56
60
  # Local tool state
57
61
  .superpowers/
62
+
63
+ # EgoExo sync trial script spill (default output is under experiments/egoexo-pipeline/out/)
64
+ /*.sync_trial.json
65
+ /run_config.json
58
66
  .entire/*
59
67
  !.entire/
60
68
  !.entire/settings.json
@@ -68,6 +76,13 @@ Thumbs.db
68
76
  buildai-workspace/
69
77
  apps/buildai-sd-card-reader-android/
70
78
 
79
+ # Local DDS verification refresh artifacts. The reviewed dry-run manifest is
80
+ # tracked separately so tests and handoffs use one stable repair plan.
81
+ .codex/dds-gcs-inventory*.txt
82
+ .codex/dds-gcs-repair-dry-run.current.json
83
+ .codex/dds-gcs-repair-verify*.json
84
+ .codex/dds-*-smoke-current.png
85
+
71
86
  # Generated artifacts
72
87
  outputs/
73
88
  logs/
@@ -75,8 +90,14 @@ reports/
75
90
  scratchpad/
76
91
  local-archive/
77
92
  reference/
93
+ recording-cache/
78
94
  /datasets/1m-hour-dataset/*
79
95
  !/datasets/1m-hour-dataset/README.md
96
+ !/datasets/1m-hour-dataset/anonymized-remap-summary.json
97
+ !/datasets/1m-hour-dataset/clip_mapping.parquet
98
+ !/datasets/1m-hour-dataset/factory_mapping.csv
99
+ !/datasets/1m-hour-dataset/object_rename_mapping.parquet
100
+ !/datasets/1m-hour-dataset/worker_mapping.csv
80
101
  /datasets/250k-hour-dataset/*
81
102
  !/datasets/250k-hour-dataset/README.md
82
103
  !/datasets/250k-hour-dataset/build/
@@ -93,6 +114,8 @@ reference/
93
114
  /datasets/**/*.mp4
94
115
  /datasets/**/*.mov
95
116
  /datasets/**/*.parquet
117
+ !/datasets/1m-hour-dataset/clip_mapping.parquet
118
+ !/datasets/1m-hour-dataset/object_rename_mapping.parquet
96
119
  /datasets/**/manifest.csv
97
120
  /datasets/**/scored_device_on_head_candidates.jsonl
98
121
  /datasets/label-viz-clips/candidates/*.decisions.jsonl
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: buildai-cli
3
- Version: 0.3.72
3
+ Version: 0.3.74
4
4
  Summary: Build AI CLI (Typer)
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: httpx>=0.27.0
@@ -4,9 +4,11 @@ from __future__ import annotations
4
4
 
5
5
  import asyncio
6
6
  import hashlib
7
+ from dataclasses import asdict, is_dataclass
7
8
  from datetime import date
8
9
  from decimal import Decimal
9
- from typing import Literal
10
+ from time import monotonic
11
+ from typing import Literal, cast
10
12
  from uuid import UUID
11
13
 
12
14
  import typer
@@ -40,6 +42,7 @@ _SUBMITTED_BY = "00000000-0000-0000-0000-000000000000"
40
42
  # `last_error` on quarantined rows; reset to `pending` and zero `attempt_count`
41
43
  # manually once the underlying problem is fixed.
42
44
  _RECAP_QUEUE_MAX_ATTEMPTS = 5
45
+ _READ_MODEL_VALIDATION_PROFILES = frozenset({"internal_admin", "internal_viewer"})
43
46
 
44
47
 
45
48
  @app.callback()
@@ -59,6 +62,11 @@ def gigcamera_callback(
59
62
  "-p",
60
63
  help="Auth workflow profile. Use internal_admin for writes.",
61
64
  ),
65
+ all_proxy: str | None = typer.Option(
66
+ None,
67
+ "--all-proxy",
68
+ help="Proxy URL passed to alloydb-auth-proxy for private-only DB lanes.",
69
+ ),
62
70
  verbose: bool = typer.Option(False, "--verbose", "-v", help="Verbose logging."),
63
71
  ) -> None:
64
72
  """Capture DB-targeting flags; subcommands initialize the heavy ops context."""
@@ -68,6 +76,7 @@ def gigcamera_callback(
68
76
  ctx.obj["_env"] = env
69
77
  ctx.obj["_auth"] = auth
70
78
  ctx.obj["_user"] = user
79
+ ctx.obj["_all_proxy"] = all_proxy
71
80
  ctx.obj["_verbose"] = verbose
72
81
  ctx.obj.setdefault("allow_write", False)
73
82
  ctx.obj.setdefault("_ops_ready", False)
@@ -93,6 +102,61 @@ def _require_internal_admin_for_write(ctx: typer.Context, *, write: bool) -> Non
93
102
  raise typer.BadParameter("writes require --profile internal_admin")
94
103
 
95
104
 
105
+ def _require_read_model_validation_profile(ctx: typer.Context) -> None:
106
+ """Prevent misleading read-model parity runs under scoped developer profiles."""
107
+
108
+ profile = (ctx.obj or {}).get("cli_profile") or "engineers-dev"
109
+ if profile not in _READ_MODEL_VALIDATION_PROFILES:
110
+ raise typer.BadParameter(
111
+ "read-model validation requires --profile internal_admin or --profile internal_viewer"
112
+ )
113
+
114
+
115
+ def _context_with_org_id_constraints(
116
+ dal_ctx: object,
117
+ *,
118
+ scope_org_ids: list[UUID] | None,
119
+ ) -> object:
120
+ """Stamp a DAL context with explicit org visibility for scoped read-model rows."""
121
+
122
+ if not scope_org_ids:
123
+ return dal_ctx
124
+ from dal.constraints import DataConstraints, ResourceType
125
+
126
+ requested = frozenset(str(value) for value in scope_org_ids)
127
+ existing = getattr(dal_ctx, "constraints", None)
128
+ if existing is not None:
129
+ allowed_ids = getattr(existing, "allowed_ids", None)
130
+ if isinstance(allowed_ids, dict) and any(
131
+ str(resource_type) != "organization" for resource_type in allowed_ids
132
+ ):
133
+ raise typer.BadParameter(
134
+ "--scope-org-id cannot be combined with non-organization constraints"
135
+ )
136
+ if existing.has_constraint("organization"):
137
+ existing_ids = frozenset(str(value) for value in existing.uuid_ids_for("organization"))
138
+ if not requested.issubset(existing_ids):
139
+ raise typer.BadParameter(
140
+ "--scope-org-id must stay within the active profile's organization constraints"
141
+ )
142
+
143
+ constraints = DataConstraints(allowed_ids={cast(ResourceType, "organization"): requested})
144
+ if is_dataclass(dal_ctx) and not isinstance(dal_ctx, type):
145
+ from dataclasses import replace
146
+
147
+ return replace(dal_ctx, constraints=constraints)
148
+ setattr(dal_ctx, "constraints", constraints)
149
+ return dal_ctx
150
+
151
+
152
+ def _renderable_result(value: object) -> object:
153
+ """Convert dataclass DAL summaries to normal JSON-friendly dictionaries."""
154
+
155
+ if is_dataclass(value) and not isinstance(value, type):
156
+ return asdict(value)
157
+ return value
158
+
159
+
96
160
  def _parse_ist_day(value: str, *, field_name: str) -> date:
97
161
  """Parse one ISO calendar day supplied as a GigCamera IST boundary."""
98
162
 
@@ -109,6 +173,177 @@ def _idempotency_key(base: str, *, run_suffix: str | None) -> str:
109
173
  return f"{base}:{suffix}" if suffix else base
110
174
 
111
175
 
176
+ @app.command("admin-analytics-read-model-refresh")
177
+ def admin_analytics_read_model_refresh(
178
+ ctx: typer.Context,
179
+ range_name: Literal["7d", "30d", "all"] = typer.Option(
180
+ "30d",
181
+ "--range",
182
+ help="Canonical analytics range to refresh when not using --recent.",
183
+ ),
184
+ org_slug: str | None = typer.Option(
185
+ None,
186
+ "--org-slug",
187
+ help="Optional organization slug matching the admin analytics API filter.",
188
+ ),
189
+ scope_org_ids: list[UUID] | None = typer.Option(
190
+ None,
191
+ "--scope-org-id",
192
+ "--org-id",
193
+ help=(
194
+ "Organization id to apply as DAL visibility constraints. Repeat for "
195
+ "multi-org scoped admins."
196
+ ),
197
+ ),
198
+ recent: bool = typer.Option(
199
+ False,
200
+ "--recent",
201
+ help="Refresh today plus the configured late-update window instead of the full range.",
202
+ ),
203
+ late_window_days: int = typer.Option(
204
+ 3,
205
+ "--late-window-days",
206
+ min=0,
207
+ max=29,
208
+ help="Number of prior IST days to refresh with --recent.",
209
+ ),
210
+ write: bool = typer.Option(False, "--write", help="Actually upsert read-model rows."),
211
+ format: Format = format_option(),
212
+ ) -> None:
213
+ """Refresh the admin analytics shadow read model through the canonical DAL path."""
214
+
215
+ settings = _settings_for_command(ctx, write=write)
216
+ scope_org_id_values = [str(value) for value in scope_org_ids or []]
217
+ if not write:
218
+ render(
219
+ {
220
+ "dry_run": True,
221
+ "mode": "recent" if recent else "range",
222
+ "range": range_name,
223
+ "org_slug": org_slug,
224
+ "scope_org_ids": scope_org_id_values,
225
+ "late_window_days": late_window_days if recent else None,
226
+ "message": "pass --write with --profile internal_admin to upsert read-model rows",
227
+ },
228
+ format=format,
229
+ )
230
+ return
231
+ _require_internal_admin_for_write(ctx, write=True)
232
+
233
+ async def run() -> None:
234
+ from dal.product import dashboard as dashboard_dal
235
+
236
+ started_at = monotonic()
237
+ async with get_cli_context(settings, profile=(ctx.obj or {}).get("cli_profile")) as (
238
+ _db,
239
+ dal_ctx,
240
+ ):
241
+ scoped_dal_ctx = _context_with_org_id_constraints(
242
+ dal_ctx,
243
+ scope_org_ids=scope_org_ids,
244
+ )
245
+ if recent:
246
+ result = (
247
+ await dashboard_dal.refresh_recent_gigcamera_admin_daily_analytics_read_model(
248
+ scoped_dal_ctx,
249
+ org_slug=org_slug,
250
+ late_window_days=late_window_days,
251
+ )
252
+ )
253
+ else:
254
+ result = await dashboard_dal.refresh_gigcamera_admin_daily_analytics_read_model(
255
+ scoped_dal_ctx,
256
+ range_name=range_name,
257
+ org_slug=org_slug,
258
+ )
259
+
260
+ render(
261
+ {
262
+ "dry_run": False,
263
+ "mode": "recent" if recent else "range",
264
+ "range": range_name,
265
+ "org_slug": org_slug,
266
+ "scope_org_ids": scope_org_id_values,
267
+ "late_window_days": late_window_days if recent else None,
268
+ "duration_ms": round((monotonic() - started_at) * 1000, 2),
269
+ "result": _renderable_result(result),
270
+ },
271
+ format=format,
272
+ )
273
+
274
+ asyncio.run(run())
275
+
276
+
277
+ @app.command("admin-analytics-read-model-parity")
278
+ def admin_analytics_read_model_parity(
279
+ ctx: typer.Context,
280
+ range_name: Literal["7d", "30d", "all"] = typer.Option(
281
+ "30d",
282
+ "--range",
283
+ help="Canonical analytics range to compare against read-model rows.",
284
+ ),
285
+ org_slug: str | None = typer.Option(
286
+ None,
287
+ "--org-slug",
288
+ help="Optional organization slug matching the admin analytics API filter.",
289
+ ),
290
+ scope_org_ids: list[UUID] | None = typer.Option(
291
+ None,
292
+ "--scope-org-id",
293
+ "--org-id",
294
+ help=(
295
+ "Organization id to apply as DAL visibility constraints. Repeat for "
296
+ "multi-org scoped admins."
297
+ ),
298
+ ),
299
+ max_mismatches: int = typer.Option(
300
+ 20,
301
+ "--max-mismatches",
302
+ min=1,
303
+ max=200,
304
+ help="Maximum mismatch examples to include in the parity payload.",
305
+ ),
306
+ format: Format = format_option(),
307
+ ) -> None:
308
+ """Compare read-model rows with canonical admin analytics without mutating data."""
309
+
310
+ settings = _settings_for_command(ctx, write=False)
311
+ _require_read_model_validation_profile(ctx)
312
+
313
+ async def run() -> None:
314
+ from dal.product import dashboard as dashboard_dal
315
+
316
+ started_at = monotonic()
317
+ async with get_cli_context(settings, profile=(ctx.obj or {}).get("cli_profile")) as (
318
+ _db,
319
+ dal_ctx,
320
+ ):
321
+ scoped_dal_ctx = _context_with_org_id_constraints(
322
+ dal_ctx,
323
+ scope_org_ids=scope_org_ids,
324
+ )
325
+ result = await dashboard_dal.get_gigcamera_admin_daily_analytics_read_model_parity(
326
+ scoped_dal_ctx,
327
+ range_name=range_name,
328
+ org_slug=org_slug,
329
+ max_mismatches=max_mismatches,
330
+ )
331
+
332
+ render(
333
+ {
334
+ "range": range_name,
335
+ "org_slug": org_slug,
336
+ "scope_org_ids": [str(value) for value in scope_org_ids or []],
337
+ "max_mismatches": max_mismatches,
338
+ "duration_ms": round((monotonic() - started_at) * 1000, 2),
339
+ "parity": result,
340
+ },
341
+ format=format,
342
+ )
343
+
344
+ asyncio.run(run())
345
+
346
+
112
347
  async def _stale_consolidated_source_rows(
113
348
  db: object,
114
349
  *,
@@ -332,13 +332,13 @@ def tick_egoexo_stage_jobs(
332
332
  "stages": _parse_csv(
333
333
  stages,
334
334
  option_name="--stages",
335
- allowed={"frame_index", "pose_2d", "pose_3d_definition_run"},
335
+ allowed={"frame_extract", "pose_2d", "pose_3d_definition_run"},
336
336
  ),
337
337
  "run_key": run_key,
338
338
  "plan_limit": plan_limit,
339
339
  "priority": priority,
340
340
  "skip_existing_pose_3d": skip_existing_pose_3d,
341
- "launch_frame_index": launch,
341
+ "launch_frame_extract": launch,
342
342
  "launch_pose_2d": launch,
343
343
  "launch_pose_3d": launch,
344
344
  "dry_run": dry_run or not write,
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "buildai-cli"
7
- version = "0.3.72"
7
+ version = "0.3.74"
8
8
  description = "Build AI CLI (Typer)"
9
9
  requires-python = ">=3.11"
10
10
  dependencies = [
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes