catence 0.2.0-beta.2 → 0.2.0-beta.21

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 (66) hide show
  1. package/README.md +28 -82
  2. package/config.example.json +25 -0
  3. package/dist/contracts/index.d.ts +1 -0
  4. package/dist/contracts/index.js +1 -0
  5. package/dist/contracts/progress.d.ts +23 -0
  6. package/dist/contracts/progress.js +49 -0
  7. package/dist/contracts/release.d.ts +1 -1
  8. package/dist/contracts/release.js +1 -1
  9. package/dist/core/logging.d.ts +16 -0
  10. package/dist/core/logging.js +68 -0
  11. package/dist/core/query/repository.d.ts +2 -0
  12. package/dist/core/query/repository.js +48 -0
  13. package/dist/core/runtime/catalog.js +17 -7
  14. package/dist/core/runtime/configuration.d.ts +3 -1
  15. package/dist/core/runtime/configuration.js +5 -1
  16. package/dist/core/runtime/opencode-go.d.ts +58 -0
  17. package/dist/core/runtime/opencode-go.js +246 -0
  18. package/dist/core/runtime/secrets.js +51 -13
  19. package/dist/core/runtime/self-update.d.ts +44 -0
  20. package/dist/core/runtime/self-update.js +138 -0
  21. package/dist/elt/application/detached-sync.d.ts +50 -0
  22. package/dist/elt/application/detached-sync.js +99 -0
  23. package/dist/elt/application/management.d.ts +15 -0
  24. package/dist/elt/application/management.js +435 -19
  25. package/dist/elt/ingestion/importer.d.ts +3 -2
  26. package/dist/elt/ingestion/importer.js +5 -4
  27. package/dist/elt/ingestion/providers/intervals.d.ts +12 -1
  28. package/dist/elt/ingestion/providers/intervals.js +37 -2
  29. package/dist/elt/ingestion/providers/registry.d.ts +13 -0
  30. package/dist/elt/ingestion/providers/registry.js +18 -1
  31. package/dist/elt/ingestion/providers/strava/service.js +7 -1
  32. package/dist/elt/normalization/normalizers.js +111 -15
  33. package/dist/elt/storage/database.d.ts +21 -2
  34. package/dist/elt/storage/database.js +184 -6
  35. package/dist/elt/storage/migrations.js +25 -1
  36. package/dist/elt/storage/progress-sidecar.d.ts +61 -0
  37. package/dist/elt/storage/progress-sidecar.js +200 -0
  38. package/dist/interfaces/cli/commands.js +116 -1
  39. package/dist/interfaces/http/generation-status.d.ts +19 -0
  40. package/dist/interfaces/http/generation-status.js +55 -0
  41. package/dist/interfaces/http/server.d.ts +7 -1
  42. package/dist/interfaces/http/server.js +184 -1
  43. package/dist/interfaces/mcp/server.d.ts +2 -1
  44. package/dist/interfaces/mcp/server.js +50 -13
  45. package/dist/runtime/index.d.ts +8 -1
  46. package/dist/runtime/index.js +4 -1
  47. package/docs/architecture.md +11 -1
  48. package/docs/configuration.md +243 -0
  49. package/docs/deployment/docker.md +528 -0
  50. package/docs/deployment/local-console.md +263 -0
  51. package/docs/deployment/local-mcp.md +329 -0
  52. package/docs/development.md +337 -0
  53. package/docs/distribution.md +58 -0
  54. package/docs/llm-providers.md +249 -6
  55. package/docs/mcp-activity-retrieval.md +37 -30
  56. package/package.json +5 -1
  57. package/pyproject.toml +1 -1
  58. package/python/catence/providers/garmin/cli.py +43 -9
  59. package/python/catence/providers/garmin/progress.py +85 -0
  60. package/python/catence/providers/garmin/registry.py +1 -0
  61. package/python/catence/providers/garmin/streams.py +38 -0
  62. package/python/catence/providers/garmin/swim_backfill.py +83 -0
  63. package/python/catence/providers/garmin/worker.py +372 -11
  64. package/scripts/discover-opencode-go.mjs +102 -0
  65. package/scripts/discover-opencode-zen.mjs +218 -0
  66. package/uv.lock +3 -3
@@ -0,0 +1,83 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ from pathlib import Path
6
+
7
+ from .staging import StagingWriter
8
+ from .streams import fit_archive_to_swim_lengths
9
+
10
+ SWIM_SPORTS = ("lap_swimming", "swimming", "open_water_swimming", "openwaterswimming")
11
+
12
+
13
+ def parse_args() -> argparse.Namespace:
14
+ parser = argparse.ArgumentParser(description="Backfill swim lengths from already-archived Garmin activity FIT files.")
15
+ parser.add_argument("--data-dir", required=True, type=Path)
16
+ parser.add_argument("--output", required=True, type=Path)
17
+ parser.add_argument(
18
+ "--activities",
19
+ type=Path,
20
+ help=(
21
+ "JSON list of {remote_activity_id, started_on} rows produced by the TypeScript side. "
22
+ "Preferred over reading DuckDB because the parent process may hold the write lock."
23
+ ),
24
+ )
25
+ return parser.parse_args()
26
+
27
+
28
+ def load_swim_activities(activities_file: Path | None, database_path: Path) -> list[tuple[str, str | None]]:
29
+ if activities_file is not None:
30
+ payload = json.loads(activities_file.read_text())
31
+ return [(str(row["remote_activity_id"]), row.get("started_on")) for row in payload]
32
+ import duckdb
33
+
34
+ con = duckdb.connect(str(database_path), read_only=True)
35
+ rows = con.execute(
36
+ """
37
+ SELECT source.remote_activity_id, cast(activity.started_at_utc AS VARCHAR) AS started_at_utc
38
+ FROM activity_sources AS source
39
+ JOIN activities AS activity USING (activity_id)
40
+ WHERE source.provider = 'garmin'
41
+ AND lower(coalesce(activity.sport, '')) IN ({placeholders})
42
+ ORDER BY activity.started_at_utc
43
+ """.format(placeholders=", ".join(f"'{sport}'" for sport in SWIM_SPORTS)),
44
+ ).fetchall()
45
+ con.close()
46
+ return [(str(remote_id), started_at_utc) for remote_id, started_at_utc in rows]
47
+
48
+
49
+ def main() -> None:
50
+ args = parse_args()
51
+ database_path = args.data_dir / "catence.duckdb"
52
+ if args.activities is None and not database_path.exists():
53
+ raise SystemExit(f"Catalog database not found: {database_path}")
54
+ rows = load_swim_activities(args.activities, database_path)
55
+
56
+ writer = StagingWriter(args.data_dir, args.output)
57
+ staged = 0
58
+ for remote_id, started_at_utc in rows:
59
+ activity_id = str(remote_id)
60
+ zip_dir = args.data_dir / "raw" / "garmin" / "activity_original" / activity_id
61
+ if not zip_dir.is_dir():
62
+ continue
63
+ zips = sorted(zip_dir.glob("*.zip"))
64
+ if not zips:
65
+ continue
66
+ archive = zips[-1]
67
+ lengths = fit_archive_to_swim_lengths(f"garmin:{activity_id}", archive)
68
+ if not lengths:
69
+ continue
70
+ writer.source_entity(
71
+ "activity_swim_lengths",
72
+ f"{activity_id}:fit",
73
+ {"lengths": lengths},
74
+ archive.stem,
75
+ parent_remote_id=activity_id,
76
+ occurred_on=str(started_at_utc)[:10] if started_at_utc else None,
77
+ )
78
+ staged += 1
79
+ print(f"Staged swim lengths for {staged} activities; output: {args.output}")
80
+
81
+
82
+ if __name__ == "__main__":
83
+ main()