@m13v/s4l 1.6.197-rc.10
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 +143 -0
- package/SKILL.md +342 -0
- package/bin/cli.js +980 -0
- package/bin/cookie-helper.js +315 -0
- package/bin/platform.js +59 -0
- package/bin/scheduler/index.js +12 -0
- package/bin/scheduler/launchd.js +518 -0
- package/browser-agent-configs/all-agents-mcp.json +68 -0
- package/browser-agent-configs/linkedin-agent-mcp.json +16 -0
- package/browser-agent-configs/linkedin-agent.json +17 -0
- package/browser-agent-configs/linkedin-harness-mcp.json +21 -0
- package/browser-agent-configs/reddit-agent-mcp.json +16 -0
- package/browser-agent-configs/reddit-agent.json +17 -0
- package/browser-agent-configs/twitter-harness-mcp.json +18 -0
- package/config.example.json +45 -0
- package/mcp/dist/index.js +4212 -0
- package/mcp/dist/onboarding.js +200 -0
- package/mcp/dist/panel.html +176 -0
- package/mcp/dist/product-link.html +102 -0
- package/mcp/dist/repo.js +222 -0
- package/mcp/dist/runtime.js +1079 -0
- package/mcp/dist/screencast.js +323 -0
- package/mcp/dist/setup.js +545 -0
- package/mcp/dist/telemetry.js +306 -0
- package/mcp/dist/twitterAuth.js +138 -0
- package/mcp/dist/version.js +271 -0
- package/mcp/dist/version.json +4 -0
- package/mcp/install-runtime.mjs +70 -0
- package/mcp/install.mjs +169 -0
- package/mcp/manifest.json +80 -0
- package/mcp/menubar/dashboard_server.py +213 -0
- package/mcp/menubar/s4l_card.py +1336 -0
- package/mcp/menubar/s4l_log_relay.py +179 -0
- package/mcp/menubar/s4l_menubar.py +2439 -0
- package/mcp/menubar/s4l_state.py +891 -0
- package/mcp/package.json +34 -0
- package/mcp/shared/doctor.cjs +437 -0
- package/mcp/shared/onboarding-ledger.cjs +324 -0
- package/mcp-servers/browser-harness/server.py +968 -0
- package/package.json +160 -0
- package/requirements.txt +20 -0
- package/scripts/_compute_allowlist.py +58 -0
- package/scripts/_db_update.py +20 -0
- package/scripts/_filt.py +9 -0
- package/scripts/_li_notif_match.py +76 -0
- package/scripts/_li_notif_orchestrate.py +126 -0
- package/scripts/_lock_preempt_test.py +60 -0
- package/scripts/_run_icp_precheck.py +57 -0
- package/scripts/a16z_pearx_calendar_reminders.py +99 -0
- package/scripts/account_resolver.py +141 -0
- package/scripts/active_campaigns.py +114 -0
- package/scripts/active_users.py +190 -0
- package/scripts/amplitude_24h_signups.py +468 -0
- package/scripts/amplitude_signups.py +177 -0
- package/scripts/apply_onboarding_selections.py +131 -0
- package/scripts/audience_pages.py +243 -0
- package/scripts/audit_helper.py +120 -0
- package/scripts/author_history_block.py +353 -0
- package/scripts/autopilot_stall_watch.py +284 -0
- package/scripts/backfill_twitter_attempts_topic.py +81 -0
- package/scripts/backfill_twitter_log_post_no_id.py +322 -0
- package/scripts/bench_dashboard.sh +138 -0
- package/scripts/bh_send.py +39 -0
- package/scripts/build_persona.py +409 -0
- package/scripts/bulk_icp.py +18 -0
- package/scripts/campaign_bump.py +51 -0
- package/scripts/capture_thread_media.py +288 -0
- package/scripts/check_browser_lock_health.sh +81 -0
- package/scripts/check_external_pool_depth.py +253 -0
- package/scripts/check_unread_web_chats.py +28 -0
- package/scripts/claim_web_chat.py +47 -0
- package/scripts/classify_run_error.py +158 -0
- package/scripts/claude_job.py +988 -0
- package/scripts/clean_stale_singleton.sh +56 -0
- package/scripts/cleanup_harness_tabs.py +68 -0
- package/scripts/copy_browser_cookies.py +454 -0
- package/scripts/counterparty_history.py +350 -0
- package/scripts/db.py +57 -0
- package/scripts/discover_claude_profiles.py +120 -0
- package/scripts/discover_linkedin_candidates.py +984 -0
- package/scripts/dm_conversation.py +682 -0
- package/scripts/dm_db_update.py +69 -0
- package/scripts/dm_engage_helper.py +161 -0
- package/scripts/dm_outreach_helper.py +147 -0
- package/scripts/dm_outreach_twitter_helper.py +129 -0
- package/scripts/dm_send_log.py +106 -0
- package/scripts/dm_short_links.py +1084 -0
- package/scripts/dump_web_chat_history.py +47 -0
- package/scripts/engage_github.py +640 -0
- package/scripts/engage_reddit.py +1235 -0
- package/scripts/engage_twitter_helper.py +301 -0
- package/scripts/engagement_styles.py +1787 -0
- package/scripts/enrich_twitter_candidates.py +82 -0
- package/scripts/feedback_digest.py +448 -0
- package/scripts/fetch_prospect_profile.py +312 -0
- package/scripts/fetch_twitter_t1.py +134 -0
- package/scripts/find_threads.py +530 -0
- package/scripts/follow_gate_log.py +59 -0
- package/scripts/funnel_per_day.py +194 -0
- package/scripts/generate_daily_human_style.py +494 -0
- package/scripts/generation_trace.py +173 -0
- package/scripts/get_run_cost.py +107 -0
- package/scripts/github_engage_helper.py +93 -0
- package/scripts/github_tools.py +509 -0
- package/scripts/harness_overlay.py +556 -0
- package/scripts/harvest_twitter_following.py +243 -0
- package/scripts/heartbeat.sh +70 -0
- package/scripts/history_context.py +284 -0
- package/scripts/http_api.py +206 -0
- package/scripts/human_dm_replies_helper.py +169 -0
- package/scripts/identity.py +302 -0
- package/scripts/ig_batch_creator.sh +93 -0
- package/scripts/ig_post_type_picker.py +243 -0
- package/scripts/ig_scrape_transcribe.sh +91 -0
- package/scripts/ingest_human_dm_replies.py +271 -0
- package/scripts/ingest_web_chat_replies.py +229 -0
- package/scripts/install_fleet.py +187 -0
- package/scripts/invent_mcp_server.py +350 -0
- package/scripts/invent_topics.py +1462 -0
- package/scripts/learned_preferences.py +263 -0
- package/scripts/li_discovery.py +161 -0
- package/scripts/link_edit_helper.py +142 -0
- package/scripts/link_tail.py +592 -0
- package/scripts/linkedin_api.py +561 -0
- package/scripts/linkedin_browser.py +730 -0
- package/scripts/linkedin_cooldown.py +128 -0
- package/scripts/linkedin_exclusions.py +234 -0
- package/scripts/linkedin_killswitch.py +1333 -0
- package/scripts/linkedin_search_topic_schema.py +49 -0
- package/scripts/linkedin_unipile.py +658 -0
- package/scripts/linkedin_url.py +228 -0
- package/scripts/log_claude_session.py +636 -0
- package/scripts/log_draft.py +143 -0
- package/scripts/log_linkedin_search_attempts.py +126 -0
- package/scripts/log_post.py +651 -0
- package/scripts/log_run.py +364 -0
- package/scripts/log_thread_media.py +108 -0
- package/scripts/log_twitter_search_attempts.py +150 -0
- package/scripts/log_twitter_skips.py +211 -0
- package/scripts/lookup_post.py +78 -0
- package/scripts/mark_web_chat_processed.py +32 -0
- package/scripts/mcp_lock_proxy.py +370 -0
- package/scripts/memory_snapshot.py +972 -0
- package/scripts/merge_review_queue.py +215 -0
- package/scripts/mint_external_pool.py +182 -0
- package/scripts/mint_kent_pool.py +249 -0
- package/scripts/moltbook_post.py +320 -0
- package/scripts/moltbook_tools.py +159 -0
- package/scripts/pending_threads.py +188 -0
- package/scripts/pick_ig_account.py +177 -0
- package/scripts/pick_project.py +208 -0
- package/scripts/pick_search_topic.py +771 -0
- package/scripts/pick_thread_target.py +279 -0
- package/scripts/pick_twitter_thread_target.py +202 -0
- package/scripts/podlog_fetch_batch.sh +32 -0
- package/scripts/post_github.py +1311 -0
- package/scripts/post_reddit.py +2668 -0
- package/scripts/precompute_dashboard_stats.py +204 -0
- package/scripts/preflight.sh +297 -0
- package/scripts/progress.py +88 -0
- package/scripts/project_excludes.py +353 -0
- package/scripts/project_slugs.py +91 -0
- package/scripts/project_stats.py +241 -0
- package/scripts/project_stats_json.py +1563 -0
- package/scripts/project_topics.py +192 -0
- package/scripts/qualified_query_bank.py +436 -0
- package/scripts/reap_stale_claude_sessions.py +867 -0
- package/scripts/reddit_browser.py +2549 -0
- package/scripts/reddit_browser_fetch.py +141 -0
- package/scripts/reddit_browser_lock.py +593 -0
- package/scripts/reddit_chat_sync.py +710 -0
- package/scripts/reddit_query_bank.py +200 -0
- package/scripts/reddit_threads_helper.py +151 -0
- package/scripts/reddit_tools.py +956 -0
- package/scripts/refresh_instagram_tokens.py +280 -0
- package/scripts/release-mcpb.sh +513 -0
- package/scripts/reply_db.py +334 -0
- package/scripts/reply_insert.py +98 -0
- package/scripts/reply_risk_digest.py +761 -0
- package/scripts/reset-test-machine.sh +602 -0
- package/scripts/restore_twitter_session.py +177 -0
- package/scripts/ripen_reddit_plan.py +478 -0
- package/scripts/run_claude.sh +433 -0
- package/scripts/run_moltbook_cycle.py +555 -0
- package/scripts/s4l_box_update.sh +226 -0
- package/scripts/s4l_channel.py +103 -0
- package/scripts/s4l_ctl.sh +75 -0
- package/scripts/s4l_env.py +47 -0
- package/scripts/saps_activity.py +126 -0
- package/scripts/saps_mode.py +328 -0
- package/scripts/scan_dm_candidates.py +580 -0
- package/scripts/scan_github_replies.py +168 -0
- package/scripts/scan_instagram_comments.py +481 -0
- package/scripts/scan_moltbook_replies.py +252 -0
- package/scripts/scan_pii.py +190 -0
- package/scripts/scan_reddit_replies.py +377 -0
- package/scripts/scan_twitter_mentions_browser.py +327 -0
- package/scripts/scan_twitter_thread_followups.py +299 -0
- package/scripts/scan_x_profile.py +384 -0
- package/scripts/schedule_state.py +202 -0
- package/scripts/scheduled_tasks_snapshot.py +123 -0
- package/scripts/score_linkedin_candidates.py +419 -0
- package/scripts/score_twitter_candidates.py +718 -0
- package/scripts/scrape_linkedin_comment_stats.py +1755 -0
- package/scripts/scrape_linkedin_stats_browser.py +52 -0
- package/scripts/scrape_reddit_views.py +365 -0
- package/scripts/seed_search_queries.py +453 -0
- package/scripts/seed_search_topics.py +127 -0
- package/scripts/send_web_chat_reply.py +130 -0
- package/scripts/sentry_init.py +128 -0
- package/scripts/setup_twitter_auth.py +1320 -0
- package/scripts/snapshot.py +583 -0
- package/scripts/stats.py +2702 -0
- package/scripts/stats_helper.py +52 -0
- package/scripts/strike_alert.py +783 -0
- package/scripts/sweep_post_link_clicks.py +107 -0
- package/scripts/sync_ig_to_posts.py +147 -0
- package/scripts/test_browser_lock.py +189 -0
- package/scripts/test_installation_api.sh +52 -0
- package/scripts/test_percard_posting.py +142 -0
- package/scripts/top_dud_linkedin_queries.py +71 -0
- package/scripts/top_dud_reddit_queries.py +67 -0
- package/scripts/top_dud_twitter_queries.py +71 -0
- package/scripts/top_dud_twitter_topics.py +102 -0
- package/scripts/top_linkedin_queries.py +55 -0
- package/scripts/top_omitted_reddit_topics.py +91 -0
- package/scripts/top_performers.py +588 -0
- package/scripts/top_search_topics.py +180 -0
- package/scripts/top_twitter_queries.py +190 -0
- package/scripts/twitter_access_check.py +382 -0
- package/scripts/twitter_account.py +41 -0
- package/scripts/twitter_batch_phase.py +126 -0
- package/scripts/twitter_browser.py +2804 -0
- package/scripts/twitter_cookie_mirror.py +130 -0
- package/scripts/twitter_cycle_helper.py +310 -0
- package/scripts/twitter_gen_links.py +287 -0
- package/scripts/twitter_post_plan.py +1188 -0
- package/scripts/twitter_scan.py +324 -0
- package/scripts/twitter_supply_signal.py +57 -0
- package/scripts/twitter_threads_helper.py +152 -0
- package/scripts/unclaim_web_chat.py +29 -0
- package/scripts/update_instagram_stats.py +261 -0
- package/scripts/update_linkedin_stats_from_feed.py +328 -0
- package/scripts/version.py +72 -0
- package/scripts/watchdog_hung_runs.py +343 -0
- package/scripts/write_generation_trace.py +73 -0
- package/setup/SKILL.md +277 -0
- package/skill/amplitude-24h-signups.sh +38 -0
- package/skill/archive-old-logs.sh +40 -0
- package/skill/audit-dm-staleness.sh +42 -0
- package/skill/audit-linkedin.sh +14 -0
- package/skill/audit-moltbook.sh +4 -0
- package/skill/audit-reddit-resurrect.sh +67 -0
- package/skill/audit-reddit.sh +4 -0
- package/skill/audit-twitter.sh +4 -0
- package/skill/audit.sh +287 -0
- package/skill/backfill-twitter-attempts-topic.sh +19 -0
- package/skill/backfill-twitter-ghost-posts.sh +24 -0
- package/skill/check-external-pool-depth.sh +7 -0
- package/skill/check-web-chats.sh +203 -0
- package/skill/dm-outreach-linkedin.sh +250 -0
- package/skill/dm-outreach-reddit.sh +274 -0
- package/skill/dm-outreach-twitter.sh +265 -0
- package/skill/engage-dm-replies-linkedin.sh +4 -0
- package/skill/engage-dm-replies-reddit.sh +4 -0
- package/skill/engage-dm-replies-twitter.sh +4 -0
- package/skill/engage-dm-replies.sh +1597 -0
- package/skill/engage-linkedin.sh +581 -0
- package/skill/engage-moltbook.sh +36 -0
- package/skill/engage-reddit.sh +146 -0
- package/skill/engage-twitter.sh +467 -0
- package/skill/github-engage.sh +176 -0
- package/skill/ingest-web-chat-replies.sh +38 -0
- package/skill/invent-supply-test.sh +100 -0
- package/skill/invent-topics.sh +50 -0
- package/skill/lib/linkedin-backend.sh +364 -0
- package/skill/lib/platform.sh +48 -0
- package/skill/lib/reddit-backend.sh +234 -0
- package/skill/lib/twitter-backend.sh +314 -0
- package/skill/link-edit-github.sh +136 -0
- package/skill/link-edit-moltbook.sh +117 -0
- package/skill/link-edit-reddit.sh +201 -0
- package/skill/linkedin-presence.sh +182 -0
- package/skill/linkedin-recovery.sh +282 -0
- package/skill/lock.sh +647 -0
- package/skill/memory-snapshot.sh +39 -0
- package/skill/precompute-stats.sh +35 -0
- package/skill/prewarm-funnel.sh +104 -0
- package/skill/refresh-instagram-tokens.sh +57 -0
- package/skill/refresh-twitter-following.sh +52 -0
- package/skill/reply-risk-digest.sh +31 -0
- package/skill/run-cycle-update-guard.sh +44 -0
- package/skill/run-draft-and-publish.sh +123 -0
- package/skill/run-generate-daily-style.sh +50 -0
- package/skill/run-github-launchd.sh +62 -0
- package/skill/run-github.sh +102 -0
- package/skill/run-instagram-daily.sh +149 -0
- package/skill/run-instagram-render.sh +875 -0
- package/skill/run-linkedin-launchd.sh +81 -0
- package/skill/run-linkedin-unipile.sh +130 -0
- package/skill/run-linkedin.sh +1593 -0
- package/skill/run-moltbook-launchd.sh +61 -0
- package/skill/run-moltbook.sh +38 -0
- package/skill/run-overlay-watch.sh +100 -0
- package/skill/run-reddit-search-launchd.sh +64 -0
- package/skill/run-reddit-search.sh +505 -0
- package/skill/run-reddit-threads-double.sh +32 -0
- package/skill/run-reddit-threads.sh +847 -0
- package/skill/run-scan-moltbook-replies.sh +57 -0
- package/skill/run-twitter-cycle-launchd.sh +63 -0
- package/skill/run-twitter-cycle-singleton.sh +62 -0
- package/skill/run-twitter-cycle.sh +2408 -0
- package/skill/run-twitter-threads.sh +592 -0
- package/skill/scan-instagram-replies.sh +61 -0
- package/skill/scan-twitter-followups.sh +57 -0
- package/skill/social-autoposter-update.sh +66 -0
- package/skill/stats-instagram.sh +72 -0
- package/skill/stats-linkedin.sh +271 -0
- package/skill/stats-moltbook.sh +4 -0
- package/skill/stats-reddit.sh +4 -0
- package/skill/stats-twitter.sh +4 -0
- package/skill/stats.sh +521 -0
- package/skill/strike-alert.sh +18 -0
- package/skill/styles.sh +87 -0
- package/skill/sweep-link-clicks.sh +40 -0
- package/skill/topics.sh +51 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""Tee the menu bar's stderr to the S4L Cloud Run log relay.
|
|
2
|
+
|
|
3
|
+
The .mcpb server already streams the verbatim stdout/stderr of every pipeline
|
|
4
|
+
subprocess to POST /api/v1/installations/logs (mcp/src/telemetry.ts), where the
|
|
5
|
+
Cloud Run relay console.log()s each line into Cloud Logging. The menu bar is a
|
|
6
|
+
separate launchd agent, so its stderr (including every [s4l-card] surface
|
|
7
|
+
lifecycle line) only ever reached the local menubar.err.log; in the 2026-07-02
|
|
8
|
+
unseen-card incident that meant zero central signal. This module closes the
|
|
9
|
+
gap: sys.stderr is wrapped so every line still lands in the local log file AND
|
|
10
|
+
ships to the same relay endpoint under the same X-Installation identity, with
|
|
11
|
+
context "menubar" so Log Explorer can split it from pipeline output.
|
|
12
|
+
|
|
13
|
+
Mirrors telemetry.ts semantics: batch flushes on a short cadence, blank and
|
|
14
|
+
base64-blob noise filtering (the X-Installation echo loop guard), bounded
|
|
15
|
+
buffer with drop-oldest overflow, and strictly best-effort: nothing in here may
|
|
16
|
+
ever raise into the menu bar or write to stderr itself (that would loop).
|
|
17
|
+
Disable with S4L_LOG_STREAM=0, point elsewhere with AUTOPOSTER_LOG_BASE.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import os
|
|
22
|
+
import re
|
|
23
|
+
import sys
|
|
24
|
+
import threading
|
|
25
|
+
import time
|
|
26
|
+
import urllib.request
|
|
27
|
+
|
|
28
|
+
LOG_BASE = (os.environ.get("AUTOPOSTER_LOG_BASE") or "https://app.s4l.ai").rstrip("/")
|
|
29
|
+
ENABLED = os.environ.get("S4L_LOG_STREAM", "1") != "0"
|
|
30
|
+
MAX_LINE_LEN = 8192 # relay cap
|
|
31
|
+
MAX_BUFFER = 1000 # drop oldest beyond this
|
|
32
|
+
MAX_PER_POST = 200 # relay accepts 1-200 lines per request
|
|
33
|
+
FLUSH_SECONDS = 3.0
|
|
34
|
+
|
|
35
|
+
# Lines that are nothing but a long base64 run are the identity-header echo
|
|
36
|
+
# that once flooded Cloud Logging via the pipeline lane; same guard here.
|
|
37
|
+
_BASE64_BLOB_RE = re.compile(r"^[A-Za-z0-9+/=_-]{120,}$")
|
|
38
|
+
|
|
39
|
+
_buf = []
|
|
40
|
+
_lock = threading.Lock()
|
|
41
|
+
_header = None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _install_header():
|
|
45
|
+
global _header
|
|
46
|
+
if _header:
|
|
47
|
+
return _header
|
|
48
|
+
# Lane 1: the shared helper, present in current repos.
|
|
49
|
+
try:
|
|
50
|
+
# scripts/ is on sys.path (S4L_REPO_DIR insertion at menubar boot).
|
|
51
|
+
from http_api import get_identity_header
|
|
52
|
+
|
|
53
|
+
_header = (get_identity_header() or "").strip() or None
|
|
54
|
+
except Exception:
|
|
55
|
+
_header = None
|
|
56
|
+
if _header:
|
|
57
|
+
return _header
|
|
58
|
+
# Lane 2: older deployed repos lack get_identity_header; mint the header
|
|
59
|
+
# the way telemetry.ts does, by shelling scripts/identity.py.
|
|
60
|
+
try:
|
|
61
|
+
import subprocess
|
|
62
|
+
|
|
63
|
+
script = os.path.join(
|
|
64
|
+
os.environ.get("S4L_REPO_DIR") or "", "scripts", "identity.py"
|
|
65
|
+
)
|
|
66
|
+
if os.path.isfile(script):
|
|
67
|
+
out = subprocess.run(
|
|
68
|
+
[sys.executable, script, "header"],
|
|
69
|
+
capture_output=True,
|
|
70
|
+
text=True,
|
|
71
|
+
timeout=10,
|
|
72
|
+
)
|
|
73
|
+
if out.returncode == 0:
|
|
74
|
+
_header = (out.stdout or "").strip() or None
|
|
75
|
+
except Exception:
|
|
76
|
+
_header = None
|
|
77
|
+
return _header
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _now_iso():
|
|
81
|
+
try:
|
|
82
|
+
import datetime
|
|
83
|
+
|
|
84
|
+
return datetime.datetime.now(datetime.timezone.utc).isoformat()
|
|
85
|
+
except Exception:
|
|
86
|
+
return ""
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class _Tee:
|
|
90
|
+
"""File-like wrapper: pass every write through to the real stderr (launchd's
|
|
91
|
+
menubar.err.log redirect), and buffer non-noise lines for the relay."""
|
|
92
|
+
|
|
93
|
+
def __init__(self, real):
|
|
94
|
+
self._real = real
|
|
95
|
+
|
|
96
|
+
def write(self, s):
|
|
97
|
+
try:
|
|
98
|
+
n = self._real.write(s)
|
|
99
|
+
except Exception:
|
|
100
|
+
n = len(s) if isinstance(s, str) else 0
|
|
101
|
+
try:
|
|
102
|
+
for ln in str(s).splitlines():
|
|
103
|
+
t = ln.strip()
|
|
104
|
+
if not t or _BASE64_BLOB_RE.match(t):
|
|
105
|
+
continue
|
|
106
|
+
with _lock:
|
|
107
|
+
_buf.append(
|
|
108
|
+
{
|
|
109
|
+
"ts": _now_iso(),
|
|
110
|
+
"stream": "stderr",
|
|
111
|
+
"line": ln[:MAX_LINE_LEN],
|
|
112
|
+
"context": "menubar",
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
if len(_buf) > MAX_BUFFER:
|
|
116
|
+
del _buf[: len(_buf) - MAX_BUFFER]
|
|
117
|
+
except Exception:
|
|
118
|
+
pass
|
|
119
|
+
return n
|
|
120
|
+
|
|
121
|
+
def flush(self):
|
|
122
|
+
try:
|
|
123
|
+
self._real.flush()
|
|
124
|
+
except Exception:
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
def __getattr__(self, name):
|
|
128
|
+
return getattr(self._real, name)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _flush_once():
|
|
132
|
+
header = _install_header()
|
|
133
|
+
if not header:
|
|
134
|
+
return # identity not mintable yet; keep buffering
|
|
135
|
+
while True:
|
|
136
|
+
with _lock:
|
|
137
|
+
if not _buf:
|
|
138
|
+
return
|
|
139
|
+
batch = _buf[:MAX_PER_POST]
|
|
140
|
+
del _buf[:MAX_PER_POST]
|
|
141
|
+
try:
|
|
142
|
+
req = urllib.request.Request(
|
|
143
|
+
LOG_BASE + "/api/v1/installations/logs",
|
|
144
|
+
data=json.dumps({"lines": batch}).encode("utf-8"),
|
|
145
|
+
headers={
|
|
146
|
+
"X-Installation": header,
|
|
147
|
+
"Content-Type": "application/json",
|
|
148
|
+
},
|
|
149
|
+
method="POST",
|
|
150
|
+
)
|
|
151
|
+
urllib.request.urlopen(req, timeout=15).read()
|
|
152
|
+
except Exception:
|
|
153
|
+
# Network blip or relay down: drop this batch (a persistent failure
|
|
154
|
+
# must not grow the buffer unbounded) and stop draining; the cadence
|
|
155
|
+
# loop retries with newer lines. Never log from in here.
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _loop():
|
|
160
|
+
while True:
|
|
161
|
+
time.sleep(FLUSH_SECONDS)
|
|
162
|
+
try:
|
|
163
|
+
_flush_once()
|
|
164
|
+
except Exception:
|
|
165
|
+
pass
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def install():
|
|
169
|
+
"""Wrap sys.stderr with the relay tee and start the background flusher.
|
|
170
|
+
Call once at menubar boot, after the S4L_REPO_DIR sys.path insertion."""
|
|
171
|
+
if not ENABLED:
|
|
172
|
+
return
|
|
173
|
+
try:
|
|
174
|
+
if isinstance(sys.stderr, _Tee):
|
|
175
|
+
return
|
|
176
|
+
sys.stderr = _Tee(sys.stderr)
|
|
177
|
+
threading.Thread(target=_loop, daemon=True, name="s4l-log-relay").start()
|
|
178
|
+
except Exception:
|
|
179
|
+
pass
|