Goal: End Mac dependency + recurring macOS-Keychain "Chrome Safe Storage" prompts + HS-cookie staleness by running the credential/metrics extractors natively on the Hetzner box (178.156.250.227, key ~/.ssh/skyrun_cloud, user skyrun).
Why: Mac extractors hard-depend on (a) security find-generic-password Keychain (Chrome Safe Storage key for cookie-DB decrypt + SkyRun/cloudflare_api_token), (b) macOS Chrome profile paths, (c) AppleScript chrome_bridge. All break on Linux. The Mac launchd HS-cookie refresh was hourly but the shortest HS cookie TTL is ~20 min → cloud reconciler went 401/phantom-gap ~20 min into every hour (the symptom chased for days).
How to apply: Port pattern = replace cookie-DB-decrypt with live Playwright connect_over_cdp(http://127.0.0.1:9222) → context.cookies() (no Keychain, no Linux-encryption reverse-eng), and replace security ... cloudflare_api_token with the box file ~/.config/skyrun/cf_api_token (chmod 600, skyrun — provisioned 2026-05-16 from Mac Keychain; that read is SILENT/non-prompting unlike Chrome Safe Storage). Keep payload schema + CF KV keys IDENTICAL so cloud Workers need zero changes. Wrap as skyrun-<x>.service + .timer, created DISABLED until explicit cutover (reference_cloud_box.md discipline — box timers stay off; do not flip without Joseph's call).
DONE (verified 2026-05-16):
extract_hs_cookies_box.py(/home/skyrun/SkyRun/) — CDP cookies, box CF token. End-to-end proven: 13 jar cookies, csrf.app present, pushed to CF KVhs_session_cookies, KV readback OK. systemdskyrun-hs-cookie.serviceruns clean;skyrun-hs-cookie.timer(*:0/10) installed DISABLED.extract_sessions_box.py— ports extract_track_cookie.py + extract_bv_session.py + extract_keydata_session.py in ONE CDP pass. Verified: track TrackAuth(32)→track_auth_cookie, bv _beenverified3_session(566, exp 2026-06-30)→bv_session_token+bv_session_expiry, keydata session-token(2640)→keydata_session_token. All box secret files chmod 600 skyrun. systemdskyrun-sessions.serviceclean;skyrun-sessions.timer(*:0/30) installed DISABLED.- CF API token provisioned to box:
~/.config/skyrun/cf_api_token(53B, 600, skyrun). - Box Max OAuth token live + headless-verified:
~/.config/skyrun/max_oauth_token(see reference_claude_setup_token_oauth.md). extract_track_metrics_box.py— port = ONE-LINE import swap (from chrome_bridge import ChromeBridge→from cloud_bridge import ChromeBridge). cloud_bridge.py is a TRUE drop-in: already has.js(tab,expr)(Playwright page.evaluate, _coerce to str) + full 10-method chrome_bridge interface. ROOT path works via box symlink/home/skyrun/Library/Application Support/SkyRun → /home/skyrun/SkyRun. tenant_config on box returns track_subdomain=skyrunwinterpark. Port verified: runs, cloud_bridge connects, correctly fail-closed to last-known-good when Track auth expired (hardening intact). systemdskyrun-track-metrics.service+.timer(*:0/30) installed DISABLED (env SKYRUN_BRIDGE=cloud SKYRUN_CDP_URL set in unit).- Vendor auth state on box Chrome (live-probed 2026-05-16 ~16:25Z): KeyData AUTHED, SmartLead AUTHED, BV AUTHED, HS OK (cookies extract w/ csrf.app). Track EXPIRED — box Chrome Track session lapsed; login page loaded+activated at https://skyrunwinterpark.trackhs.com/ (tab B1DB6DD6) for Joseph browser-only re-sign-in (VNC localhost:5903 pw Skyrun25). NON-BLOCKING for other ports. After Track re-login: clear /home/skyrun/SkyRun/state/track_auth_expired.flag (or wait 6h auto-reprobe) so extract_track_metrics_box stops serving LKG.
- chrome_bridge→cloud_bridge port pattern PROVEN trivial (1-line swap) — applies to any chrome_bridge-based extractor.
extract_keydata_metrics_box.py— Keychain(keydata_session_token,cloudflare_api_token)→box files; ALSO added render-poll fix (SPA needed longer paint in cold headless ctx; replaced fixed 1.5s sleep with 35×1s poll for 'My Performance'+'ADR' markers). Verified: portfolio ADR=237 occ=7.3% units=42 status=ok, CF KVkeydata_metricspush 200 + readback OK. NOTE: uses Playwrightlaunch(headless=True)→ requiredplaywright install chromiumin box venv (DONE; chromium-headless-shell 147 cached /home/skyrun/.cache/ms-playwright). systemdskyrun-keydata-metrics.timer DISABLED.extract_smartlead_metrics_box.py— Keychain(smartlead_jwt)→box file; pure JWT REST API (no Chrome). Verified: 3 campaigns, 600 sent, 6 replies, status=ok. systemdskyrun-smartlead-metrics.timer DISABLED.- SmartLead JWT provisioned to box
~/.config/skyrun/smartlead_jwt(1204B, 600, skyrun) from Mac Keychain (silent read; JWT has no expiry). NOT in box Chrome localStorage (probed — absent). - Box secret-file map now also: smartlead_jwt→smartlead_jwt.
- **sync_*_to_kv ports DONE+verified (2026-05-16): sync_dnc_to_kv_box.py (142 emails→KV dnc_active_homeowners), sync_active_deal_emails_to_kv_box.py (7→active_deal_emails), sync_active_deals_kg_to_kv_box.py (6→active_deals_kg), sync_sot_to_kv_box.py (867 leads/494KB→sot_data). All rc=0, KV readback fresh. CF-token Keychain call →
pathlib.Path.home()/.config/skyrun/cf_api_token(one box patcher, exact-string per-file replace, asserted 0find-generic-passwordrefs + ast.parse). Box source files present (DNC json/KG/SoT workbook synced). - fleet_status_push.py: NO PORT NEEDED — 0 Keychain refs (token from
.envFLEET_WRITE_TOKEN); on box cleanly no-ops "fleet not configured" (opt-in feature, returns 0). Works as-is. - ★ EXTRACTOR/SYNC MIGRATION FUNCTIONALLY COMPLETE 2026-05-16: every macOS-Keychain dep eliminated. 6 extractors + 4 sync scripts all run box-native + verified with live data. All box timers still DISABLED (cutover gated on Joseph).
- ★ CLOUD CLAUDE AGENT + MAX INSTRUMENTATION COMPLETE 2026-05-16:
box_claude_agent.sh(sources max_env.sh, runsclaude -p --output-format jsonheadless on Max token, generic task+prompt|@file),box_agent_ledger.py(parses CLI 2.1.143 json schema → ~/.config/skyrun/max_usage.jsonl 600),max_usage_analyzer.py(rolling 5h/24h/7d aggregates → ~/.config/skyrun/max_usage_health.json + ntfy on crit; AUTHORITATIVE cap signal = real rate_limit_errors; numeric thresholds ASSUMED/configurable via max_limits.json — Anthropic doesn't publish exact Max 20x caps, NOT fabricated). Canary verified ×2 (RC=0). systemdskyrun-box-canary.service(+analyzer ExecStartPost)+.timerinstalled DISABLED. Per-invocation cost-equiv ≈ $0.024 (mostly ~5.2K cache-creation system context; net new in/out tiny). Empirical per-task data accrues as real workloads run → answers "is Max 20x enough" by measurement. - ★ CUTOVER EXECUTED + VERIFIED 2026-05-16 ~23:05Z (Joseph-authorized): Track re-logged in by Joseph → flag cleared, extract_track_metrics_box.py produces real data (occ 13/74, api_status=ok). Built 4 sync box timers (skyrun-sync-dnc hourly, sync-sot 30m, sync-active-emails 15m, sync-active-kg 15m). 9 box timers ENABLED+active**: skyrun-hs-cookie(/10), skyrun-sessions(/30), skyrun-track-metrics(/30), skyrun-keydata-metrics, skyrun-smartlead-metrics, skyrun-sync-dnc, skyrun-sync-sot, skyrun-sync-active-emails, skyrun-sync-active-kg. (skyrun-box-canary deliberately LEFT DISABLED — canary only, no Mac job to replace; run on-demand.) 8 Mac launchd jobs UNLOADED (reversible — plists retained in ~/Library/LaunchAgents;
launchctl load <plist>to roll back): hs-cookie-refresh, track-metrics, keydata-metrics, smartlead-metrics, dnc-sync, sot-sync, active-deal-emails-sync, kg-active-deals-sync. Verified box-produced CF KV freshness post-cutover: hs_session_cookies age 0.3m csrf=True (the HS_401 staleness fix — /10 box refresh vs prior Mac hourly), dnc/active_deal_emails/active_deals_kg all <0.3m, sot_data sync clean (867 leads; schema has no top-level synced_at). NON-MIGRATED Mac jobs intentionally LEFT RUNNING (out of scope): session-keep-alive, deal-stage-reconciler, pwa-autorebuild, pwa-periodic-refresh, approvals-queue-reconciler, stalled-watchdog-drift-resolver, pending-drafts-aging-sweep, sync-queued-actions, extract-trigger-runner, insights-archiver, system-hygiene, quarterly-backup, caffeinate-business-hours, freshness-watchdog. - ★ BOX SESSION KEEP-ALIVE BUILT + ENABLED 2026-05-16 (closes the last gap):
box_session_keepalive.py— purpose-built (NOT a faithful port: Mac session_keep_alive.py has SOURCES=[] so its_warmpath is dead code → only read-only cookie monitoring, which is WHY Track kept expiring). Uses cloud_bridge to find each box Chrome vendor tab (track/hubspot/keydata/beenverified/smartlead) and fire the proven_warmJS (activity events + authenticated same-origin no-store fetch) every 10 min to reset server-side idle timers. Writes same-schema /home/skyrun/SkyRun/pwa/source_health.json + heartbeat + ntfy(skyrun-josephbowens) on flip-to-expired. Verified test run: all 5 sessions alive+warmed. systemdskyrun-keepalive.service+.timer(*:2/10) ENABLED+active. This is the active-warm behavior the Mac abandoned — vendor sessions (esp. Track) no longer idle-expire unattended. - RESIDUAL NOTES: (a) RESOLVED — box keep-alive now warms sessions */10. Mac com.skyrun.session-keep-alive still loaded but harmless (warms Mac Chrome only; out of migrated scope, left running). (b) Rollback path if ever needed:
launchctl loadthe 8 retained plists in ~/Library/LaunchAgents +systemctl disable --nowthe 10 box timers (9 migrated + skyrun-keepalive). (c) skyrun-box-canary still DISABLED — enable on-demand if a real recurring cloud-agent workload is assigned (Max-usage analyzer already wired to it). - ▣ FULL CUTOVER COMPLETE & HANDS-OFF 2026-05-16: every macOS-Keychain dep gone; box is sole live producer for HS cookies/sessions/metrics/syncs; sessions self-warm */10; cloud Claude agent on Max with usage instrumentation. Mac no longer required for the data pipeline.
- ★ PLUMBING MIGRATION WAVE 2026-05-16 PM ("everything else"): Retired 2 obsolete Mac jobs (session-keep-alive [box supersedes], caffeinate-business-hours [N/A on Linux]). Migrated+cutover 8 drop-in plumbing jobs (ran box-native unchanged — box symlink resolves Mac ROOT paths; no code changes): skyrun-{pwa-periodic-refresh,deal-stage-reconciler,approvals-queue-reconciler,stalled-watchdog-drift,pending-drafts-aging,sync-queued-actions,extract-trigger-runner,insights-archiver} — all enabled/active, 8 Mac counterparts unloaded (plists retained, reversible). 4 Mac jobs remain, NOT blind-migrated (honest holds): (1) freshness-watchdog → freshness_watchdog_box.py built (chrome_bridge→cloud_bridge alias swap, syntax ok, runs) but reports errors=3/fresh=0 — HOLD cutover (don't enable a misfiring watchdog → alert spam; investigate: likely transient post-cutover staleness vs real port gap). (2) pwa-autorebuild — ran rc=0 but PWA build output unverified — HOLD until confirmed valid box build. (3) quarterly-backup — CLAUDE flag was FALSE POSITIVE (comment/path strings; it backs up ~/.claude/memory). It's a Mac→iCloud backup → REDESIGN not port (box is now authoritative; need box→offsite). (4) system-hygiene — real Claude+chrome_bridge+keychain+CF consumer → belongs to #2 capacity track, not blind-migrated.
- ★ #2 QB CAPACITY CANARY LIVE 2026-05-17 00:06Z:
qb_box_tick.sh(runs qb_state_pack.py on box → Claude delta-reasoning via box_claude_agent.sh on Max → ntfy on high-sev → logs to qb_tick_log.jsonl + max_usage.jsonl). systemdskyrun-qb-tick.timerENABLED, conservative cadence--* 06,08,10,12,14,16,18,20,22:00box-local (~9/day, half real QB's hourly rate). qb_state_pack.py verified box-native (1s, 5.2KB, rc=0). First measured QB tick: total_cost_usd=$0.0469, duration=21.5s, output=850 tok, cache_read=12963, cache_creation=7370, input=3 — CACHE-DOMINATED (fleet = many small calls, not few huge). Extrapolation: real QB hourly ≈ ~$0.75/day cost-equiv; full fleet plausibly a few $/day cost-equiv = $0 billed on Max; binding constraint is the rolling-5h rate limit, authoritative signal max_usage_analyzer rate_limit_errors=0 so far. Measurement window now accumulating 3-5d; verdict pending real concurrent-load data. NOTE QB canary replicates Mode-A core (state-pack delta+severity) only — NOT the full GC-dispatch/commitment-Gmail machinery (Mac MCP deps, not the capacity point). Interactive QB drop-in session stays Mac/Cowork-side (unchanged). - pwa-autorebuild CUT OVER 2026-05-17: verified produces valid 133KB pwa/index.html box-native; WatchPaths→15-min timer (path-watch would storm given box timers' frequent data writes; pwa-periodic-refresh covers lighter in-between). skyrun-pwa-autorebuild.timer enabled/active; Mac com.skyrun.pwa-autorebuild unloaded. 3 Mac jobs remain: freshness-watchdog (HELD — see below), quarterly-backup (redesign), system-hygiene (#2 track).
- freshness-watchdog DEEP-DIVE VERDICT (HOLD, do not cut over): it's a META-watchdog that RE-FIRES other extractors when their output goes stale. On box: (a) partially REDUNDANT — skyrun timers now own scheduled freshness + keep-alive owns sessions; (b) its errors=3 = it fires Mac extractor names/invocations, not the _box.py versions w/ cloud env. Correct fix = rework to either target *_box.py extractors OR reduce to monitor-only (detect-stale→ntfy, since timers re-fire). Real rework, NOT a port. Non-critical (timers+keepalive already provide the freshness it enforced). freshness_watchdog_box.py exists (cloud_bridge alias swap, syntax ok) but NOT enabled. Mac com.skyrun.freshness-watchdog left loaded (harmless; will error trying Mac extractors but no bad action).
- MAX CAPACITY = THE GATING UNKNOWN (Joseph's key insight 2026-05-16): Max rate limits are PER-ACCOUNT not per-device → running the fleet on the box drains the SAME Max 20x pool as running it on the Mac; cloud buys availability/independence, NOT capacity, and may consume MORE (24/7 schedule + contends with Joseph's interactive use). Implication for productization ("friends"): one Max 20x CANNOT back multiple tenants' fleets — each tenant needs own Claude creds (own Max sub or API key). #2 (migrate QB as canary → let max_usage_analyzer collect 3-5d real data) is what answers "does 20x carry SkyRun at all" — gates whether/how #3 (tenant template) is even viable. Tenant template MUST provision per-tenant Claude auth, never share.
- Box secret-file convention (replaces all macOS Keychain SkyRun/* items):
~/.config/skyrun/<name>600 skyrun. Map: cloudflare_api_token→cf_api_token, track_auth_cookie→track_auth_cookie, bv_session_token→bv_session_token, bv_session_expiry→bv_session_expiry, keydata_session_token→keydata_session_token. (smartlead_jwt, keydata extras still TODO.)
TODO (same port pattern; all currently carry the macOS security dep → broken on box as-is):
extract_track_metrics.py(638L, importschrome_bridgeAppleScript → swap to cloud_bridge; reads SkyRun/track_auth_cookie → box file; output file track_metrics.json + last-known-good + health heartbeat).extract_keydata_metrics.py(335L; reads SkyRun/keydata_session_token + SkyRun/cloudflare_api_token → box files; CF KV keykeydata_metrics; output keydata_metrics.json).extract_smartlead_metrics.py(185L; reads SkyRun/smartlead_jwt → need to source smartlead_jwt onto box; output smartlead_metrics.json).fleet_status_push.py(190L; reads KG + pwa/data queues; CF KV push; config from .env/config.json).sync_dnc_to_kv.py,sync_sot_to_kv.py,sync_active_deal_emails_to_kv.py,sync_active_deals_kg_to_kv.py(CF KV sync — samesecurityCF-token line; swap to box file).- Cutover decision: enable box timers (recommend HS-cookie at */10 — parallel to Mac launchd is non-destructive and FIXES the staleness) + disable Mac launchd
com.skyrun.hs-cookie-refresh. Gated on Joseph. - Cloud Claude agent + Max-usage instrumentation (measure whether Max 20x sustains the fleet).