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.
- package/README.md +28 -82
- package/config.example.json +25 -0
- package/dist/contracts/index.d.ts +1 -0
- package/dist/contracts/index.js +1 -0
- package/dist/contracts/progress.d.ts +23 -0
- package/dist/contracts/progress.js +49 -0
- package/dist/contracts/release.d.ts +1 -1
- package/dist/contracts/release.js +1 -1
- package/dist/core/logging.d.ts +16 -0
- package/dist/core/logging.js +68 -0
- package/dist/core/query/repository.d.ts +2 -0
- package/dist/core/query/repository.js +48 -0
- package/dist/core/runtime/catalog.js +17 -7
- package/dist/core/runtime/configuration.d.ts +3 -1
- package/dist/core/runtime/configuration.js +5 -1
- package/dist/core/runtime/opencode-go.d.ts +58 -0
- package/dist/core/runtime/opencode-go.js +246 -0
- package/dist/core/runtime/secrets.js +51 -13
- package/dist/core/runtime/self-update.d.ts +44 -0
- package/dist/core/runtime/self-update.js +138 -0
- package/dist/elt/application/detached-sync.d.ts +50 -0
- package/dist/elt/application/detached-sync.js +99 -0
- package/dist/elt/application/management.d.ts +15 -0
- package/dist/elt/application/management.js +435 -19
- package/dist/elt/ingestion/importer.d.ts +3 -2
- package/dist/elt/ingestion/importer.js +5 -4
- package/dist/elt/ingestion/providers/intervals.d.ts +12 -1
- package/dist/elt/ingestion/providers/intervals.js +37 -2
- package/dist/elt/ingestion/providers/registry.d.ts +13 -0
- package/dist/elt/ingestion/providers/registry.js +18 -1
- package/dist/elt/ingestion/providers/strava/service.js +7 -1
- package/dist/elt/normalization/normalizers.js +111 -15
- package/dist/elt/storage/database.d.ts +21 -2
- package/dist/elt/storage/database.js +184 -6
- package/dist/elt/storage/migrations.js +25 -1
- package/dist/elt/storage/progress-sidecar.d.ts +61 -0
- package/dist/elt/storage/progress-sidecar.js +200 -0
- package/dist/interfaces/cli/commands.js +116 -1
- package/dist/interfaces/http/generation-status.d.ts +19 -0
- package/dist/interfaces/http/generation-status.js +55 -0
- package/dist/interfaces/http/server.d.ts +7 -1
- package/dist/interfaces/http/server.js +184 -1
- package/dist/interfaces/mcp/server.d.ts +2 -1
- package/dist/interfaces/mcp/server.js +50 -13
- package/dist/runtime/index.d.ts +8 -1
- package/dist/runtime/index.js +4 -1
- package/docs/architecture.md +11 -1
- package/docs/configuration.md +243 -0
- package/docs/deployment/docker.md +528 -0
- package/docs/deployment/local-console.md +263 -0
- package/docs/deployment/local-mcp.md +329 -0
- package/docs/development.md +337 -0
- package/docs/distribution.md +58 -0
- package/docs/llm-providers.md +249 -6
- package/docs/mcp-activity-retrieval.md +37 -30
- package/package.json +5 -1
- package/pyproject.toml +1 -1
- package/python/catence/providers/garmin/cli.py +43 -9
- package/python/catence/providers/garmin/progress.py +85 -0
- package/python/catence/providers/garmin/registry.py +1 -0
- package/python/catence/providers/garmin/streams.py +38 -0
- package/python/catence/providers/garmin/swim_backfill.py +83 -0
- package/python/catence/providers/garmin/worker.py +372 -11
- package/scripts/discover-opencode-go.mjs +102 -0
- package/scripts/discover-opencode-zen.mjs +218 -0
- 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()
|