cctally 1.82.0 → 1.83.0
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/CHANGELOG.md +70 -0
- package/README.md +52 -74
- package/bin/_cctally_alerts.py +8 -1
- package/bin/_cctally_cache.py +963 -149
- package/bin/_cctally_config.py +43 -4
- package/bin/_cctally_core.py +933 -759
- package/bin/_cctally_dashboard.py +157 -47
- package/bin/_cctally_dashboard_cache_report.py +13 -6
- package/bin/_cctally_dashboard_conversation.py +1 -0
- package/bin/_cctally_dashboard_envelope.py +186 -8
- package/bin/_cctally_dashboard_share.py +60 -20
- package/bin/_cctally_dashboard_sources.py +427 -128
- package/bin/_cctally_db.py +605 -128
- package/bin/_cctally_doctor.py +413 -28
- package/bin/_cctally_five_hour.py +12 -5
- package/bin/_cctally_journal.py +2050 -156
- package/bin/_cctally_journal_repair.py +519 -0
- package/bin/_cctally_milestone_history.py +142 -56
- package/bin/_cctally_milestones.py +179 -111
- package/bin/_cctally_parser.py +42 -0
- package/bin/_cctally_project.py +24 -18
- package/bin/_cctally_quota.py +139 -25
- package/bin/_cctally_record.py +279 -108
- package/bin/_cctally_rederive.py +1052 -0
- package/bin/_cctally_reporting.py +58 -53
- package/bin/_cctally_setup.py +1 -0
- package/bin/_cctally_source_analytics.py +4 -1
- package/bin/_cctally_statusline.py +11 -11
- package/bin/_cctally_store.py +1039 -31
- package/bin/_cctally_sync_week.py +17 -8
- package/bin/_cctally_tui.py +421 -54
- package/bin/_cctally_update.py +133 -8
- package/bin/_cctally_weekrefs.py +14 -0
- package/bin/_lib_aggregators.py +10 -6
- package/bin/_lib_cache_report.py +101 -9
- package/bin/_lib_codex_pools.py +82 -0
- package/bin/_lib_conversation_query.py +126 -33
- package/bin/_lib_dashboard_sources.py +126 -1
- package/bin/_lib_diff_kernel.py +28 -15
- package/bin/_lib_doctor.py +342 -4
- package/bin/_lib_journal.py +924 -2
- package/bin/_lib_jsonl.py +43 -14
- package/bin/_lib_pricing.py +140 -21
- package/bin/_lib_readme_refresh.py +401 -0
- package/bin/_lib_rederive.py +395 -0
- package/bin/_lib_share.py +58 -2
- package/bin/cctally +56 -8
- package/dashboard/static/assets/{index-DJP4gEB7.js → index-3bgCMVHb.js} +52 -52
- package/dashboard/static/assets/index-D27EIHEI.css +1 -0
- package/dashboard/static/dashboard.html +2 -2
- package/package.json +6 -1
- package/dashboard/static/assets/index-Dk1nplOz.css +0 -1
|
@@ -376,64 +376,69 @@ def cmd_weekly(args: argparse.Namespace) -> int:
|
|
|
376
376
|
|
|
377
377
|
conn = open_db()
|
|
378
378
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
379
|
+
try:
|
|
380
|
+
# Build the subscription-week list spanning the range. Boundaries are
|
|
381
|
+
# anchored in `weekly_usage_snapshots` when available and otherwise
|
|
382
|
+
# extrapolated (see `_compute_subscription_weeks`). Pass the
|
|
383
|
+
# `--config`-honoring resolved config (issue #88) so the no-snapshot
|
|
384
|
+
# calendar-week fallback uses the explicit override's `week_start`.
|
|
385
|
+
weeks = c._compute_subscription_weeks(
|
|
386
|
+
conn, range_start, range_end, config=config,
|
|
387
|
+
account_key=acct_key, # #341: None = merged (all-accounts) read
|
|
388
|
+
)
|
|
388
389
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
390
|
+
# Fetch entries and aggregate.
|
|
391
|
+
# Cover each SubWeek's full [start_ts, end_ts) on the range_start side —
|
|
392
|
+
# `_compute_subscription_weeks` can emit weeks whose start_ts precedes
|
|
393
|
+
# range_start (any week overlapping the range). Without widening,
|
|
394
|
+
# boundary weeks get tail-only cost divided by full-week usedPct →
|
|
395
|
+
# understated totalCost and $/1%. range_end stays as the upper bound so
|
|
396
|
+
# historical `--until <past>` queries still clip the tail week (paired
|
|
397
|
+
# with the as_of_utc bound on get_latest_usage_for_week below).
|
|
398
|
+
if weeks:
|
|
399
|
+
fetch_start = min(
|
|
400
|
+
range_start,
|
|
401
|
+
parse_iso_datetime(weeks[0].start_ts, "week_start_at"),
|
|
402
|
+
)
|
|
403
|
+
else:
|
|
404
|
+
fetch_start = range_start
|
|
405
|
+
all_entries = c.get_entries(
|
|
406
|
+
fetch_start, range_end, account_key=acct_key
|
|
401
407
|
)
|
|
402
|
-
else:
|
|
403
|
-
fetch_start = range_start
|
|
404
|
-
all_entries = c.get_entries(fetch_start, range_end, account_key=acct_key)
|
|
405
408
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
+
c._emit_debug_samples_if_set(
|
|
410
|
+
args, all_entries, command_label="weekly",
|
|
411
|
+
)
|
|
409
412
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
413
|
+
# Bound the usage-snapshot lookup to `<= range_end` so historical
|
|
414
|
+
# `--until <past date>` queries pick the usage% that was current at
|
|
415
|
+
# the end of the requested window rather than the globally latest
|
|
416
|
+
# snapshot for the week. Cost is already truncated to `range_end` by
|
|
417
|
+
# `_aggregate_weekly`, so using a later usedPct would produce a
|
|
418
|
+
# silently wrong $/1%. Match the stored `captured_at_utc` format
|
|
419
|
+
# (see now_utc_iso): UTC, seconds precision, `Z` suffix — otherwise
|
|
420
|
+
# lexicographic string compare inside SQLite would misorder `+00:00`
|
|
421
|
+
# vs. `Z` at the same instant.
|
|
422
|
+
as_of_utc = (
|
|
423
|
+
range_end.astimezone(dt.timezone.utc)
|
|
424
|
+
.replace(microsecond=0)
|
|
425
|
+
.isoformat()
|
|
426
|
+
.replace("+00:00", "Z")
|
|
427
|
+
)
|
|
425
428
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
429
|
+
# Build the unified weekly view (spec §5.3): runs _aggregate_weekly,
|
|
430
|
+
# overlays weekly_usage_snapshots per WeekRef. view.aggregated is
|
|
431
|
+
# the BucketUsage tuple newest-first; view.overlay is the parallel
|
|
432
|
+
# (used_pct, dollar_per_pct) tuple. We reverse both for CLI's
|
|
433
|
+
# default asc rendering so the existing renderer's len-equality
|
|
434
|
+
# assertions stay aligned.
|
|
435
|
+
view = c.build_weekly_view(
|
|
436
|
+
conn, all_entries, weeks=weeks, now_utc=now_utc,
|
|
437
|
+
display_tz=args._resolved_tz, as_of_utc=as_of_utc, mode=args.mode,
|
|
438
|
+
account_key=acct_key,
|
|
439
|
+
)
|
|
440
|
+
finally:
|
|
441
|
+
conn.close()
|
|
437
442
|
buckets = list(reversed(view.aggregated))
|
|
438
443
|
overlay = list(reversed(view.overlay))
|
|
439
444
|
|
package/bin/_cctally_setup.py
CHANGED
|
@@ -489,7 +489,9 @@ def load_qualified_codex_entries(
|
|
|
489
489
|
if sync:
|
|
490
490
|
cache_mod = c._load_sibling("_cctally_cache")
|
|
491
491
|
stats, conn = cache_mod._run_cache_operation_with_recovery(
|
|
492
|
-
conn,
|
|
492
|
+
conn,
|
|
493
|
+
lambda active_conn: c.sync_codex_cache(active_conn),
|
|
494
|
+
origin="source.analytics.codex_sync",
|
|
493
495
|
)
|
|
494
496
|
if stats.lock_contended:
|
|
495
497
|
raise QualifiedMetadataUnavailable("Codex qualified project metadata is unavailable")
|
|
@@ -2334,6 +2336,7 @@ def cmd_source_report(args: object) -> int:
|
|
|
2334
2336
|
cache_mod._run_cache_operation_with_recovery(
|
|
2335
2337
|
cache,
|
|
2336
2338
|
lambda active_conn: c.sync_codex_cache(active_conn),
|
|
2339
|
+
origin="source.report.codex_sync",
|
|
2337
2340
|
)
|
|
2338
2341
|
)
|
|
2339
2342
|
finally:
|
|
@@ -1497,7 +1497,7 @@ def _build_statusline_injections(warn_once):
|
|
|
1497
1497
|
"SELECT se.timestamp_utc, se.model, "
|
|
1498
1498
|
" se.input_tokens, se.output_tokens, "
|
|
1499
1499
|
" se.cache_create_tokens, se.cache_read_tokens, "
|
|
1500
|
-
" se.cost_usd_raw "
|
|
1500
|
+
" se.cost_usd_raw, se.cache_create_1h_tokens, se.speed "
|
|
1501
1501
|
"FROM session_entries se "
|
|
1502
1502
|
"LEFT JOIN session_files sf ON sf.path = se.source_path "
|
|
1503
1503
|
"WHERE sf.session_id = ?"
|
|
@@ -1514,16 +1514,16 @@ def _build_statusline_injections(warn_once):
|
|
|
1514
1514
|
return None
|
|
1515
1515
|
total = 0.0
|
|
1516
1516
|
for r in rows:
|
|
1517
|
-
usage =
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
#
|
|
1526
|
-
#
|
|
1517
|
+
usage = c.claude_usage_dict( # #195 chokepoint
|
|
1518
|
+
input_tokens=r[2],
|
|
1519
|
+
output_tokens=r[3],
|
|
1520
|
+
cache_creation_tokens=r[4],
|
|
1521
|
+
cache_read_tokens=r[5],
|
|
1522
|
+
cache_1h_tokens=r[7],
|
|
1523
|
+
speed=r[8],
|
|
1524
|
+
)
|
|
1525
|
+
# #413: speed is materialized, so the hot statusline path keeps the
|
|
1526
|
+
# effective tier without parsing the legacy usage_extra_json blob.
|
|
1527
1527
|
try:
|
|
1528
1528
|
total += c._calculate_entry_cost(
|
|
1529
1529
|
r[1], usage, mode="auto", cost_usd=r[6],
|