social-autoposter 1.5.1 → 1.6.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/package.json +1 -1
- package/scripts/github_tools.py +5 -3
- package/scripts/log_claude_session.py +128 -69
- package/scripts/reddit_tools.py +7 -0
package/package.json
CHANGED
package/scripts/github_tools.py
CHANGED
|
@@ -17,6 +17,7 @@ import time
|
|
|
17
17
|
|
|
18
18
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
19
19
|
import db as dbmod
|
|
20
|
+
from version import read_version as read_autoposter_version
|
|
20
21
|
|
|
21
22
|
CONFIG_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "config.json")
|
|
22
23
|
|
|
@@ -391,9 +392,9 @@ def cmd_log_post(args):
|
|
|
391
392
|
thread_title, thread_content, our_url, our_content, our_account,
|
|
392
393
|
source_summary, project_name, status, posted_at, feedback_report_used,
|
|
393
394
|
engagement_style, search_topic, language, claude_session_id,
|
|
394
|
-
generation_trace, link_source)
|
|
395
|
+
generation_trace, link_source, autoposter_version)
|
|
395
396
|
VALUES ('github', %s, %s, %s, %s, '', %s, %s, %s, '', %s, 'active', NOW(), TRUE,
|
|
396
|
-
%s, %s, %s, %s::uuid, %s::jsonb, %s) RETURNING id""",
|
|
397
|
+
%s, %s, %s, %s::uuid, %s::jsonb, %s, %s) RETURNING id""",
|
|
397
398
|
[args.thread_url, args.thread_author, args.thread_author, args.thread_title,
|
|
398
399
|
args.our_url, args.our_text, args.account, args.project,
|
|
399
400
|
getattr(args, "engagement_style", None),
|
|
@@ -401,7 +402,8 @@ def cmd_log_post(args):
|
|
|
401
402
|
(getattr(args, "language", None) or "en"),
|
|
402
403
|
session_id,
|
|
403
404
|
generation_trace_json,
|
|
404
|
-
getattr(args, "link_source", None)
|
|
405
|
+
getattr(args, "link_source", None),
|
|
406
|
+
read_autoposter_version()],
|
|
405
407
|
)
|
|
406
408
|
row = cur.fetchone()
|
|
407
409
|
new_id = row[0] if row else None
|
|
@@ -23,7 +23,10 @@ import sys
|
|
|
23
23
|
from datetime import datetime
|
|
24
24
|
|
|
25
25
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
26
|
-
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _use_legacy_neon() -> bool:
|
|
29
|
+
return os.environ.get("SOCIAL_AUTOPOSTER_LEGACY_NEON") == "1"
|
|
27
30
|
|
|
28
31
|
PROJECTS_ROOT = os.path.expanduser("~/.claude/projects")
|
|
29
32
|
|
|
@@ -483,6 +486,124 @@ def parse_transcript(path: str):
|
|
|
483
486
|
}
|
|
484
487
|
|
|
485
488
|
|
|
489
|
+
_BACKFILL_TABLES = (
|
|
490
|
+
"posts", "replies", "dms", "dm_messages",
|
|
491
|
+
"seo_escalations", "seo_keywords", "seo_page_improvements", "gsc_queries",
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def _persist_via_api(args, parsed, started, ended, duration_ms, orch_cost, cycle_id):
|
|
496
|
+
"""Upsert claude_sessions row + backfill model column via HTTP routes.
|
|
497
|
+
|
|
498
|
+
Two calls:
|
|
499
|
+
POST /api/v1/claude-sessions -> upsert by session_id
|
|
500
|
+
POST /api/v1/claude-sessions/backfill-model -> stamp model on activity rows
|
|
501
|
+
"""
|
|
502
|
+
from http_api import api_post
|
|
503
|
+
api_post(
|
|
504
|
+
"/api/v1/claude-sessions",
|
|
505
|
+
{
|
|
506
|
+
"session_id": args.session_id,
|
|
507
|
+
"script": args.script,
|
|
508
|
+
"started_at": started,
|
|
509
|
+
"ended_at": ended,
|
|
510
|
+
"duration_ms": duration_ms,
|
|
511
|
+
"total_cost_usd": round(parsed["total_cost_usd"], 6),
|
|
512
|
+
"orchestrator_cost_usd": orch_cost,
|
|
513
|
+
"input_tokens": parsed["totals"]["input"],
|
|
514
|
+
"output_tokens": parsed["totals"]["output"],
|
|
515
|
+
"cache_read_tokens": parsed["totals"]["cache_read"],
|
|
516
|
+
"cache_creation_tokens": parsed["totals"]["cache_creation"],
|
|
517
|
+
"model_breakdown": parsed["by_model"],
|
|
518
|
+
"model": parsed["primary_model"],
|
|
519
|
+
"cycle_id": cycle_id,
|
|
520
|
+
"task_call_count": parsed.get("task_call_count", 0),
|
|
521
|
+
"subagent_count": parsed.get("subagent_count", 0),
|
|
522
|
+
"subagent_cost_usd": parsed.get("subagent_cost_usd", 0.0),
|
|
523
|
+
"subagent_breakdown": parsed.get("subagent_breakdown") or None,
|
|
524
|
+
},
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
resp = api_post(
|
|
528
|
+
"/api/v1/claude-sessions/backfill-model",
|
|
529
|
+
{
|
|
530
|
+
"session_id": args.session_id,
|
|
531
|
+
"model": parsed["primary_model"],
|
|
532
|
+
"tables": list(_BACKFILL_TABLES),
|
|
533
|
+
},
|
|
534
|
+
)
|
|
535
|
+
data = (resp or {}).get("data") or {}
|
|
536
|
+
backfill_counts = data.get("backfilled") or {}
|
|
537
|
+
for t in _BACKFILL_TABLES:
|
|
538
|
+
backfill_counts.setdefault(t, 0)
|
|
539
|
+
return backfill_counts
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
def _persist_via_neon(args, parsed, started, ended, duration_ms, orch_cost, cycle_id):
|
|
543
|
+
"""Legacy path: direct psycopg2 to Neon. Gated by SOCIAL_AUTOPOSTER_LEGACY_NEON=1."""
|
|
544
|
+
import db as dbmod
|
|
545
|
+
dbmod.load_env()
|
|
546
|
+
conn = dbmod.get_conn()
|
|
547
|
+
subagent_breakdown_json = (
|
|
548
|
+
json.dumps(parsed["subagent_breakdown"]) if parsed.get("subagent_breakdown") else None
|
|
549
|
+
)
|
|
550
|
+
conn.execute(
|
|
551
|
+
"""INSERT INTO claude_sessions (
|
|
552
|
+
session_id, script, started_at, ended_at, duration_ms,
|
|
553
|
+
total_cost_usd, orchestrator_cost_usd,
|
|
554
|
+
input_tokens, output_tokens,
|
|
555
|
+
cache_read_tokens, cache_creation_tokens, model_breakdown, model,
|
|
556
|
+
cycle_id,
|
|
557
|
+
task_call_count, subagent_count, subagent_cost_usd, subagent_breakdown
|
|
558
|
+
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s::jsonb, %s, %s,
|
|
559
|
+
%s, %s, %s, %s::jsonb)
|
|
560
|
+
ON CONFLICT (session_id) DO UPDATE SET
|
|
561
|
+
ended_at = EXCLUDED.ended_at,
|
|
562
|
+
duration_ms = EXCLUDED.duration_ms,
|
|
563
|
+
total_cost_usd = EXCLUDED.total_cost_usd,
|
|
564
|
+
orchestrator_cost_usd = COALESCE(EXCLUDED.orchestrator_cost_usd,
|
|
565
|
+
claude_sessions.orchestrator_cost_usd),
|
|
566
|
+
input_tokens = EXCLUDED.input_tokens,
|
|
567
|
+
output_tokens = EXCLUDED.output_tokens,
|
|
568
|
+
cache_read_tokens = EXCLUDED.cache_read_tokens,
|
|
569
|
+
cache_creation_tokens = EXCLUDED.cache_creation_tokens,
|
|
570
|
+
model_breakdown = EXCLUDED.model_breakdown,
|
|
571
|
+
model = EXCLUDED.model,
|
|
572
|
+
cycle_id = COALESCE(EXCLUDED.cycle_id, claude_sessions.cycle_id),
|
|
573
|
+
task_call_count = EXCLUDED.task_call_count,
|
|
574
|
+
subagent_count = EXCLUDED.subagent_count,
|
|
575
|
+
subagent_cost_usd = EXCLUDED.subagent_cost_usd,
|
|
576
|
+
subagent_breakdown = EXCLUDED.subagent_breakdown
|
|
577
|
+
""",
|
|
578
|
+
[
|
|
579
|
+
args.session_id, args.script, started, ended, duration_ms,
|
|
580
|
+
round(parsed["total_cost_usd"], 6),
|
|
581
|
+
orch_cost,
|
|
582
|
+
parsed["totals"]["input"], parsed["totals"]["output"],
|
|
583
|
+
parsed["totals"]["cache_read"], parsed["totals"]["cache_creation"],
|
|
584
|
+
json.dumps(parsed["by_model"]),
|
|
585
|
+
parsed["primary_model"],
|
|
586
|
+
cycle_id,
|
|
587
|
+
parsed.get("task_call_count", 0),
|
|
588
|
+
parsed.get("subagent_count", 0),
|
|
589
|
+
parsed.get("subagent_cost_usd", 0.0),
|
|
590
|
+
subagent_breakdown_json,
|
|
591
|
+
],
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
backfill_counts = {}
|
|
595
|
+
for table in _BACKFILL_TABLES:
|
|
596
|
+
cur = conn.execute(
|
|
597
|
+
f"UPDATE {table} SET model = %s "
|
|
598
|
+
f"WHERE claude_session_id = %s AND model IS NULL",
|
|
599
|
+
[parsed["primary_model"], args.session_id],
|
|
600
|
+
)
|
|
601
|
+
backfill_counts[table] = cur.rowcount
|
|
602
|
+
conn.commit()
|
|
603
|
+
conn.close()
|
|
604
|
+
return backfill_counts
|
|
605
|
+
|
|
606
|
+
|
|
486
607
|
def main():
|
|
487
608
|
parser = argparse.ArgumentParser()
|
|
488
609
|
parser.add_argument("--session-id", required=True)
|
|
@@ -558,74 +679,12 @@ def main():
|
|
|
558
679
|
else None
|
|
559
680
|
)
|
|
560
681
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
"""INSERT INTO claude_sessions (
|
|
568
|
-
session_id, script, started_at, ended_at, duration_ms,
|
|
569
|
-
total_cost_usd, orchestrator_cost_usd,
|
|
570
|
-
input_tokens, output_tokens,
|
|
571
|
-
cache_read_tokens, cache_creation_tokens, model_breakdown, model,
|
|
572
|
-
cycle_id,
|
|
573
|
-
task_call_count, subagent_count, subagent_cost_usd, subagent_breakdown
|
|
574
|
-
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s::jsonb, %s, %s,
|
|
575
|
-
%s, %s, %s, %s::jsonb)
|
|
576
|
-
ON CONFLICT (session_id) DO UPDATE SET
|
|
577
|
-
ended_at = EXCLUDED.ended_at,
|
|
578
|
-
duration_ms = EXCLUDED.duration_ms,
|
|
579
|
-
total_cost_usd = EXCLUDED.total_cost_usd,
|
|
580
|
-
orchestrator_cost_usd = COALESCE(EXCLUDED.orchestrator_cost_usd,
|
|
581
|
-
claude_sessions.orchestrator_cost_usd),
|
|
582
|
-
input_tokens = EXCLUDED.input_tokens,
|
|
583
|
-
output_tokens = EXCLUDED.output_tokens,
|
|
584
|
-
cache_read_tokens = EXCLUDED.cache_read_tokens,
|
|
585
|
-
cache_creation_tokens = EXCLUDED.cache_creation_tokens,
|
|
586
|
-
model_breakdown = EXCLUDED.model_breakdown,
|
|
587
|
-
model = EXCLUDED.model,
|
|
588
|
-
cycle_id = COALESCE(EXCLUDED.cycle_id, claude_sessions.cycle_id),
|
|
589
|
-
task_call_count = EXCLUDED.task_call_count,
|
|
590
|
-
subagent_count = EXCLUDED.subagent_count,
|
|
591
|
-
subagent_cost_usd = EXCLUDED.subagent_cost_usd,
|
|
592
|
-
subagent_breakdown = EXCLUDED.subagent_breakdown
|
|
593
|
-
""",
|
|
594
|
-
[
|
|
595
|
-
args.session_id, args.script, started, ended, duration_ms,
|
|
596
|
-
round(parsed["total_cost_usd"], 6),
|
|
597
|
-
orch_cost,
|
|
598
|
-
parsed["totals"]["input"], parsed["totals"]["output"],
|
|
599
|
-
parsed["totals"]["cache_read"], parsed["totals"]["cache_creation"],
|
|
600
|
-
json.dumps(parsed["by_model"]),
|
|
601
|
-
parsed["primary_model"],
|
|
602
|
-
cycle_id,
|
|
603
|
-
parsed.get("task_call_count", 0),
|
|
604
|
-
parsed.get("subagent_count", 0),
|
|
605
|
-
parsed.get("subagent_cost_usd", 0.0),
|
|
606
|
-
subagent_breakdown_json,
|
|
607
|
-
],
|
|
608
|
-
)
|
|
609
|
-
|
|
610
|
-
# Backfill dominant model onto any activity rows stamped with this session.
|
|
611
|
-
# Only overwrites rows where model IS NULL so re-runs of log_claude_session
|
|
612
|
-
# against the same session_id stay idempotent. Covers social tables
|
|
613
|
-
# (posts/replies/dms/dm_messages) plus SEO pipeline tables that stamp
|
|
614
|
-
# claude_session_id (seo_escalations, seo_keywords, seo_page_improvements,
|
|
615
|
-
# gsc_queries).
|
|
616
|
-
backfill_counts = {}
|
|
617
|
-
for table in (
|
|
618
|
-
"posts", "replies", "dms", "dm_messages",
|
|
619
|
-
"seo_escalations", "seo_keywords", "seo_page_improvements", "gsc_queries",
|
|
620
|
-
):
|
|
621
|
-
cur = conn.execute(
|
|
622
|
-
f"UPDATE {table} SET model = %s "
|
|
623
|
-
f"WHERE claude_session_id = %s AND model IS NULL",
|
|
624
|
-
[parsed["primary_model"], args.session_id],
|
|
625
|
-
)
|
|
626
|
-
backfill_counts[table] = cur.rowcount
|
|
627
|
-
conn.commit()
|
|
628
|
-
conn.close()
|
|
682
|
+
if _use_legacy_neon():
|
|
683
|
+
backfill_counts = _persist_via_neon(args, parsed, started, ended, duration_ms,
|
|
684
|
+
orch_cost, cycle_id)
|
|
685
|
+
else:
|
|
686
|
+
backfill_counts = _persist_via_api(args, parsed, started, ended, duration_ms,
|
|
687
|
+
orch_cost, cycle_id)
|
|
629
688
|
|
|
630
689
|
print(json.dumps({
|
|
631
690
|
"logged": True,
|
package/scripts/reddit_tools.py
CHANGED
|
@@ -20,6 +20,7 @@ from datetime import datetime, timezone
|
|
|
20
20
|
|
|
21
21
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
22
22
|
from http_api import api_get, api_post
|
|
23
|
+
from version import read_version as read_autoposter_version
|
|
23
24
|
|
|
24
25
|
USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)"
|
|
25
26
|
|
|
@@ -776,6 +777,12 @@ def cmd_log_post(args):
|
|
|
776
777
|
# post_reddit.py based on which URL Claude baked into the reply text.
|
|
777
778
|
if getattr(args, "link_source", None):
|
|
778
779
|
body["link_source"] = args.link_source
|
|
780
|
+
# autoposter_version: social-autoposter package.json version at the moment
|
|
781
|
+
# we posted. Powers per-release attribution: "did 1.5.0 outperform 1.4.x
|
|
782
|
+
# on Reddit?". None when package.json + env are both missing.
|
|
783
|
+
autoposter_version = read_autoposter_version()
|
|
784
|
+
if autoposter_version:
|
|
785
|
+
body["autoposter_version"] = autoposter_version
|
|
779
786
|
resp = api_post("/api/v1/posts", body, ok_on_conflict=True)
|
|
780
787
|
err = resp.get("error") if isinstance(resp, dict) else None
|
|
781
788
|
if err:
|