@m13v/s4l 1.6.197-rc.7
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 +1314 -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 +497 -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
package/mcp/dist/repo.js
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
// Repo + process helpers for the social-autoposter MCP wrapper.
|
|
2
|
+
// This MCP is a THIN client: it shells out to the existing pipeline scripts in
|
|
3
|
+
// the social-autoposter repo and never reimplements pipeline logic.
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import { resolvePython, resolveRepoDir } from "./runtime.js";
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
// The pipeline repo the wrapper shells out to. Resolved DYNAMICALLY per call (a
|
|
12
|
+
// getter, not a load-time const) because a bare .mcpb double-click materializes
|
|
13
|
+
// the repo AFTER the server boots; the very next shell-out must pick it up
|
|
14
|
+
// without a restart. resolveRepoDir() prefers a real S4L_REPO_DIR clone, then
|
|
15
|
+
// the materialized repo recorded in runtime.json, then a dev fallback.
|
|
16
|
+
export function repoDir() {
|
|
17
|
+
return resolveRepoDir();
|
|
18
|
+
}
|
|
19
|
+
// Python used by the pipeline. Resolved DYNAMICALLY per call (not a load-time
|
|
20
|
+
// const) because the owned uv runtime can be provisioned AFTER the server boots
|
|
21
|
+
// — the first-run installer writes runtime.json and the very next runPython
|
|
22
|
+
// call must pick up the owned interpreter without a server restart.
|
|
23
|
+
// resolvePython() prefers the owned runtime, then S4L_PYTHON, then python3.
|
|
24
|
+
// The locked pipeline script (run-twitter-cycle.sh) writes the draft plan to a
|
|
25
|
+
// HARDCODED /tmp path (`PLAN_FILE="/tmp/twitter_cycle_plan_<batch>.json"`), and the
|
|
26
|
+
// `DRAFT_ONLY_PLAN=` marker the wrapper parses is rooted at /tmp too. We MUST read
|
|
27
|
+
// and write plans from the same directory. os.tmpdir() is NOT /tmp on macOS (it
|
|
28
|
+
// honors $TMPDIR, e.g. /var/folders/.../T, or /tmp/claude-501 inside Claude Code),
|
|
29
|
+
// which silently stranded every draft and made draft_cycle always report
|
|
30
|
+
// "No drafts in batch ...". Default to /tmp to match the script; allow an explicit
|
|
31
|
+
// override for non-standard installs.
|
|
32
|
+
export const TMP_DIR = process.env.S4L_TMP_DIR || process.env.SAPS_TMP_DIR || "/tmp";
|
|
33
|
+
let lineSink = null;
|
|
34
|
+
export function setLineSink(fn) {
|
|
35
|
+
lineSink = fn;
|
|
36
|
+
}
|
|
37
|
+
// Derive a short, stable context label for a spawned command so log lines can
|
|
38
|
+
// be grouped by which script produced them (e.g. "scripts/seed_search_topics.py",
|
|
39
|
+
// "skill/run-twitter-cycle.sh"). Best-effort; never throws.
|
|
40
|
+
function deriveContext(cmd, args) {
|
|
41
|
+
try {
|
|
42
|
+
const base = cmd.split("/").pop() || cmd;
|
|
43
|
+
if (/python/i.test(base) || base === "bash" || base === "sh" || base === "node") {
|
|
44
|
+
const first = args.find((a) => !a.startsWith("-"));
|
|
45
|
+
if (first)
|
|
46
|
+
return first;
|
|
47
|
+
}
|
|
48
|
+
return [base, args[0] ?? ""].join(" ").trim();
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return cmd;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Spawn a process inside the repo, inheriting the repo env (API base + keys
|
|
55
|
+
// come from the install's environment / .env loaded by the scripts themselves).
|
|
56
|
+
//
|
|
57
|
+
// `onLine` (optional) fires once per COMPLETE line as the child emits output,
|
|
58
|
+
// so a long-running script (e.g. run-twitter-cycle.sh, which can churn for
|
|
59
|
+
// minutes) can be followed live instead of going dark until it exits. The full
|
|
60
|
+
// buffered stdout/stderr are still returned unchanged, so existing callers are
|
|
61
|
+
// unaffected. A throwing sink never breaks the run.
|
|
62
|
+
export function run(cmd, args, opts = {}) {
|
|
63
|
+
return new Promise((resolve) => {
|
|
64
|
+
const child = spawn(cmd, args, {
|
|
65
|
+
cwd: opts.cwd || repoDir(),
|
|
66
|
+
env: { ...process.env, ...(opts.env || {}) },
|
|
67
|
+
});
|
|
68
|
+
try {
|
|
69
|
+
opts.onSpawn?.(child);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
/* a spawn observer must never break the run */
|
|
73
|
+
}
|
|
74
|
+
let stdout = "";
|
|
75
|
+
let stderr = "";
|
|
76
|
+
// Per-stream partial-line buffers so onLine fires on whole lines only,
|
|
77
|
+
// regardless of how the OS chunks the pipe reads.
|
|
78
|
+
let outBuf = "";
|
|
79
|
+
let errBuf = "";
|
|
80
|
+
const pump = (chunk, which, buf) => {
|
|
81
|
+
if (!opts.onLine)
|
|
82
|
+
return buf;
|
|
83
|
+
buf += chunk;
|
|
84
|
+
let nl;
|
|
85
|
+
while ((nl = buf.indexOf("\n")) !== -1) {
|
|
86
|
+
const line = buf.slice(0, nl);
|
|
87
|
+
buf = buf.slice(nl + 1);
|
|
88
|
+
try {
|
|
89
|
+
opts.onLine(line, which);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
/* a progress sink must never break the wrapped command */
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return buf;
|
|
96
|
+
};
|
|
97
|
+
// Parallel whole-line splitter that tees to the telemetry sink (if any),
|
|
98
|
+
// kept separate from the onLine pump so neither path can affect the other.
|
|
99
|
+
const logCtx = opts.logContext || deriveContext(cmd, args);
|
|
100
|
+
let sinkOutBuf = "";
|
|
101
|
+
let sinkErrBuf = "";
|
|
102
|
+
const sinkPump = (chunk, which, buf) => {
|
|
103
|
+
const sink = opts.noTee ? null : lineSink;
|
|
104
|
+
if (!sink)
|
|
105
|
+
return buf;
|
|
106
|
+
buf += chunk;
|
|
107
|
+
let nl;
|
|
108
|
+
while ((nl = buf.indexOf("\n")) !== -1) {
|
|
109
|
+
const line = buf.slice(0, nl);
|
|
110
|
+
buf = buf.slice(nl + 1);
|
|
111
|
+
try {
|
|
112
|
+
sink(line, which, logCtx);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
/* the telemetry sink must never break the wrapped command */
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return buf;
|
|
119
|
+
};
|
|
120
|
+
let timer;
|
|
121
|
+
if (opts.timeoutMs) {
|
|
122
|
+
timer = setTimeout(() => {
|
|
123
|
+
child.kill("SIGTERM");
|
|
124
|
+
}, opts.timeoutMs);
|
|
125
|
+
}
|
|
126
|
+
child.stdout.on("data", (d) => {
|
|
127
|
+
const s = d.toString();
|
|
128
|
+
stdout += s;
|
|
129
|
+
outBuf = pump(s, "stdout", outBuf);
|
|
130
|
+
sinkOutBuf = sinkPump(s, "stdout", sinkOutBuf);
|
|
131
|
+
});
|
|
132
|
+
child.stderr.on("data", (d) => {
|
|
133
|
+
const s = d.toString();
|
|
134
|
+
stderr += s;
|
|
135
|
+
errBuf = pump(s, "stderr", errBuf);
|
|
136
|
+
sinkErrBuf = sinkPump(s, "stderr", sinkErrBuf);
|
|
137
|
+
});
|
|
138
|
+
child.on("close", (code) => {
|
|
139
|
+
if (timer)
|
|
140
|
+
clearTimeout(timer);
|
|
141
|
+
// Flush any trailing partial line (output with no terminating newline).
|
|
142
|
+
if (opts.onLine) {
|
|
143
|
+
if (outBuf)
|
|
144
|
+
try {
|
|
145
|
+
opts.onLine(outBuf, "stdout");
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
/* ignore */
|
|
149
|
+
}
|
|
150
|
+
if (errBuf)
|
|
151
|
+
try {
|
|
152
|
+
opts.onLine(errBuf, "stderr");
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
/* ignore */
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const sink = opts.noTee ? null : lineSink;
|
|
159
|
+
if (sink) {
|
|
160
|
+
if (sinkOutBuf)
|
|
161
|
+
try {
|
|
162
|
+
sink(sinkOutBuf, "stdout", logCtx);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
/* ignore */
|
|
166
|
+
}
|
|
167
|
+
if (sinkErrBuf)
|
|
168
|
+
try {
|
|
169
|
+
sink(sinkErrBuf, "stderr", logCtx);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
/* ignore */
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
resolve({ code: code ?? -1, stdout, stderr });
|
|
176
|
+
});
|
|
177
|
+
child.on("error", (err) => {
|
|
178
|
+
if (timer)
|
|
179
|
+
clearTimeout(timer);
|
|
180
|
+
resolve({ code: -1, stdout, stderr: stderr + String(err) });
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
export function runPython(scriptRelPath, args, opts = {}) {
|
|
185
|
+
return run(resolvePython(), [scriptRelPath, ...args], opts);
|
|
186
|
+
}
|
|
187
|
+
export function planPath(batchId) {
|
|
188
|
+
return path.join(TMP_DIR, `twitter_cycle_plan_${batchId}.json`);
|
|
189
|
+
}
|
|
190
|
+
export function readPlan(batchId) {
|
|
191
|
+
const p = planPath(batchId);
|
|
192
|
+
if (!fs.existsSync(p))
|
|
193
|
+
return null;
|
|
194
|
+
try {
|
|
195
|
+
return JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
export function writePlan(batchId, plan) {
|
|
202
|
+
fs.writeFileSync(planPath(batchId), JSON.stringify(plan, null, 2), "utf-8");
|
|
203
|
+
}
|
|
204
|
+
// Find the newest plan file when no batch id is supplied.
|
|
205
|
+
export function latestBatchId() {
|
|
206
|
+
let files;
|
|
207
|
+
try {
|
|
208
|
+
files = fs.readdirSync(TMP_DIR);
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
const matches = files
|
|
214
|
+
.map((f) => /^twitter_cycle_plan_(.+)\.json$/.exec(f))
|
|
215
|
+
.filter((m) => !!m && !m[1].endsWith("_approved"))
|
|
216
|
+
.map((m) => ({
|
|
217
|
+
batchId: m[1],
|
|
218
|
+
mtime: fs.statSync(path.join(TMP_DIR, m[0])).mtimeMs,
|
|
219
|
+
}))
|
|
220
|
+
.sort((a, b) => b.mtime - a.mtime);
|
|
221
|
+
return matches.length ? matches[0].batchId : null;
|
|
222
|
+
}
|