abtars 0.1.0-alpha.1
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/LICENSE +190 -0
- package/README.md +84 -0
- package/bundle/_registry.generated-M4WY2MMI.js +35 -0
- package/bundle/_registry.generated-M4WY2MMI.js.map +7 -0
- package/bundle/abtars-browser.js +162 -0
- package/bundle/abtars-browser.js.map +7 -0
- package/bundle/abtars-cli.js +1438 -0
- package/bundle/abtars-cli.js.map +7 -0
- package/bundle/abtars-restart.js +12 -0
- package/bundle/abtars-restart.js.map +7 -0
- package/bundle/abtars-rss.js +165 -0
- package/bundle/abtars-rss.js.map +7 -0
- package/bundle/abtars-task.js +258 -0
- package/bundle/abtars-task.js.map +7 -0
- package/bundle/abtars.js +4072 -0
- package/bundle/abtars.js.map +7 -0
- package/bundle/agent-api-rate-limit-OQNFMXTZ.js +38 -0
- package/bundle/agent-api-rate-limit-OQNFMXTZ.js.map +7 -0
- package/bundle/agent-registry-LT4JNQH6.js +18 -0
- package/bundle/agent-registry-LT4JNQH6.js.map +7 -0
- package/bundle/agents/default.md +29 -0
- package/bundle/anthropic-adapter-2APTH3LA.js +40 -0
- package/bundle/anthropic-adapter-2APTH3LA.js.map +7 -0
- package/bundle/bridge-lock-transport-4AC2G5G6.js +39 -0
- package/bundle/bridge-lock-transport-4AC2G5G6.js.map +7 -0
- package/bundle/browse-delivery-JXBY36GK.js +17 -0
- package/bundle/browse-delivery-JXBY36GK.js.map +7 -0
- package/bundle/browser-ELNDVPLC.js +18 -0
- package/bundle/browser-ELNDVPLC.js.map +7 -0
- package/bundle/capability-CIL3G4FI.js +17 -0
- package/bundle/capability-CIL3G4FI.js.map +7 -0
- package/bundle/chunk-265TPOPC.js +289 -0
- package/bundle/chunk-265TPOPC.js.map +7 -0
- package/bundle/chunk-2UENBO6M.js +223 -0
- package/bundle/chunk-2UENBO6M.js.map +7 -0
- package/bundle/chunk-2UPU3OW6.js +67 -0
- package/bundle/chunk-2UPU3OW6.js.map +7 -0
- package/bundle/chunk-2XU2X4OI.js +125 -0
- package/bundle/chunk-2XU2X4OI.js.map +7 -0
- package/bundle/chunk-3B7BBE4F.js +758 -0
- package/bundle/chunk-3B7BBE4F.js.map +7 -0
- package/bundle/chunk-3E545J66.js +69 -0
- package/bundle/chunk-3E545J66.js.map +7 -0
- package/bundle/chunk-5R2ANXQ7.js +510 -0
- package/bundle/chunk-5R2ANXQ7.js.map +7 -0
- package/bundle/chunk-6CPN4IGS.js +507 -0
- package/bundle/chunk-6CPN4IGS.js.map +7 -0
- package/bundle/chunk-6NR3OHEW.js +88 -0
- package/bundle/chunk-6NR3OHEW.js.map +7 -0
- package/bundle/chunk-6SETMHNN.js +206 -0
- package/bundle/chunk-6SETMHNN.js.map +7 -0
- package/bundle/chunk-6UCRKRWR.js +644 -0
- package/bundle/chunk-6UCRKRWR.js.map +7 -0
- package/bundle/chunk-AR6GO6YC.js +83 -0
- package/bundle/chunk-AR6GO6YC.js.map +7 -0
- package/bundle/chunk-AZJIODTQ.js +54 -0
- package/bundle/chunk-AZJIODTQ.js.map +7 -0
- package/bundle/chunk-BHMZ4RCC.js +3706 -0
- package/bundle/chunk-BHMZ4RCC.js.map +7 -0
- package/bundle/chunk-BQ2L4GMG.js +9175 -0
- package/bundle/chunk-BQ2L4GMG.js.map +7 -0
- package/bundle/chunk-BSSBCSCL.js +159 -0
- package/bundle/chunk-BSSBCSCL.js.map +7 -0
- package/bundle/chunk-BUUVFUPO.js +157 -0
- package/bundle/chunk-BUUVFUPO.js.map +7 -0
- package/bundle/chunk-CEVRHKJY.js +131 -0
- package/bundle/chunk-CEVRHKJY.js.map +7 -0
- package/bundle/chunk-CWOHNFUV.js +39 -0
- package/bundle/chunk-CWOHNFUV.js.map +7 -0
- package/bundle/chunk-D2DCBO6M.js +228 -0
- package/bundle/chunk-D2DCBO6M.js.map +7 -0
- package/bundle/chunk-FMWKEPM7.js +31 -0
- package/bundle/chunk-FMWKEPM7.js.map +7 -0
- package/bundle/chunk-GRNENTPA.js +145 -0
- package/bundle/chunk-GRNENTPA.js.map +7 -0
- package/bundle/chunk-GST5T3WZ.js +93 -0
- package/bundle/chunk-GST5T3WZ.js.map +7 -0
- package/bundle/chunk-GUQVJC3U.js +299 -0
- package/bundle/chunk-GUQVJC3U.js.map +7 -0
- package/bundle/chunk-HX7Y7EYP.js +3659 -0
- package/bundle/chunk-HX7Y7EYP.js.map +7 -0
- package/bundle/chunk-JCJS4ZIB.js +296 -0
- package/bundle/chunk-JCJS4ZIB.js.map +7 -0
- package/bundle/chunk-JW6RU47G.js +184 -0
- package/bundle/chunk-JW6RU47G.js.map +7 -0
- package/bundle/chunk-LSPKJQCI.js +24 -0
- package/bundle/chunk-LSPKJQCI.js.map +7 -0
- package/bundle/chunk-M6VBAPNT.js +16 -0
- package/bundle/chunk-M6VBAPNT.js.map +7 -0
- package/bundle/chunk-MPX525QO.js +129 -0
- package/bundle/chunk-MPX525QO.js.map +7 -0
- package/bundle/chunk-MW6WDLU7.js +130 -0
- package/bundle/chunk-MW6WDLU7.js.map +7 -0
- package/bundle/chunk-NT3OBORC.js +215 -0
- package/bundle/chunk-NT3OBORC.js.map +7 -0
- package/bundle/chunk-NWDBD4PA.js +50 -0
- package/bundle/chunk-NWDBD4PA.js.map +7 -0
- package/bundle/chunk-OP7BTAWY.js +29 -0
- package/bundle/chunk-OP7BTAWY.js.map +7 -0
- package/bundle/chunk-PLCY3GFH.js +77 -0
- package/bundle/chunk-PLCY3GFH.js.map +7 -0
- package/bundle/chunk-PNEDC45Y.js +97 -0
- package/bundle/chunk-PNEDC45Y.js.map +7 -0
- package/bundle/chunk-QBGBT5QS.js +81 -0
- package/bundle/chunk-QBGBT5QS.js.map +7 -0
- package/bundle/chunk-RVE2N7FA.js +70 -0
- package/bundle/chunk-RVE2N7FA.js.map +7 -0
- package/bundle/chunk-TZHIDLDS.js +71910 -0
- package/bundle/chunk-TZHIDLDS.js.map +7 -0
- package/bundle/chunk-UCQ2WC3B.js +126 -0
- package/bundle/chunk-UCQ2WC3B.js.map +7 -0
- package/bundle/chunk-UHRP745J.js +214 -0
- package/bundle/chunk-UHRP745J.js.map +7 -0
- package/bundle/chunk-V76TVMCM.js +58 -0
- package/bundle/chunk-V76TVMCM.js.map +7 -0
- package/bundle/chunk-VVEDVGCR.js +981 -0
- package/bundle/chunk-VVEDVGCR.js.map +7 -0
- package/bundle/chunk-W6FAL35D.js +102 -0
- package/bundle/chunk-W6FAL35D.js.map +7 -0
- package/bundle/chunk-X6TERNVJ.js +15902 -0
- package/bundle/chunk-X6TERNVJ.js.map +7 -0
- package/bundle/chunk-X76UX47U.js +47 -0
- package/bundle/chunk-X76UX47U.js.map +7 -0
- package/bundle/chunk-XREWVCUO.js +518 -0
- package/bundle/chunk-XREWVCUO.js.map +7 -0
- package/bundle/chunk-Y6XAEX2Q.js +408 -0
- package/bundle/chunk-Y6XAEX2Q.js.map +7 -0
- package/bundle/chunk-YOCTDKKL.js +28 -0
- package/bundle/chunk-YOCTDKKL.js.map +7 -0
- package/bundle/chunk-ZXPXCDA6.js +160 -0
- package/bundle/chunk-ZXPXCDA6.js.map +7 -0
- package/bundle/commands-BHVUOU3V.js +31 -0
- package/bundle/commands-BHVUOU3V.js.map +7 -0
- package/bundle/completion-buffer-P253ONKF.js +13 -0
- package/bundle/completion-buffer-P253ONKF.js.map +7 -0
- package/bundle/config-RGSDAPZN.js +19 -0
- package/bundle/config-RGSDAPZN.js.map +7 -0
- package/bundle/config-show-ERTATR6E.js +40 -0
- package/bundle/config-show-ERTATR6E.js.map +7 -0
- package/bundle/context-HCEGZNDC.js +72 -0
- package/bundle/context-HCEGZNDC.js.map +7 -0
- package/bundle/delegation-tools-GYTS2D6A.js +27 -0
- package/bundle/delegation-tools-GYTS2D6A.js.map +7 -0
- package/bundle/deploy-lib-import-32ZFKHWP.js +49 -0
- package/bundle/deploy-lib-import-32ZFKHWP.js.map +7 -0
- package/bundle/digital-signature-OFCGSHWO.js +13 -0
- package/bundle/digital-signature-OFCGSHWO.js.map +7 -0
- package/bundle/direct-api-transport-YR7SXXNN.js +860 -0
- package/bundle/direct-api-transport-YR7SXXNN.js.map +7 -0
- package/bundle/discord-adapter-YYWVMPPU.js +584 -0
- package/bundle/discord-adapter-YYWVMPPU.js.map +7 -0
- package/bundle/dist-MTMKARCP.js +1969 -0
- package/bundle/dist-MTMKARCP.js.map +7 -0
- package/bundle/dns-wakeup-27M7D2MR.js +107 -0
- package/bundle/dns-wakeup-27M7D2MR.js.map +7 -0
- package/bundle/doctor-QNUSDY73.js +248 -0
- package/bundle/doctor-QNUSDY73.js.map +7 -0
- package/bundle/ensure-invariants-NMXNS476.js +49 -0
- package/bundle/ensure-invariants-NMXNS476.js.map +7 -0
- package/bundle/env-schema-2KBHBDGN.js +19 -0
- package/bundle/env-schema-2KBHBDGN.js.map +7 -0
- package/bundle/esm-DDP6NCZG.js +100663 -0
- package/bundle/esm-DDP6NCZG.js.map +7 -0
- package/bundle/fallback-policy-L4QV2PEJ.js +46 -0
- package/bundle/fallback-policy-L4QV2PEJ.js.map +7 -0
- package/bundle/health-check-SPA7NT6N.js +56 -0
- package/bundle/health-check-SPA7NT6N.js.map +7 -0
- package/bundle/hook-system-6Q5YTR53.js +17 -0
- package/bundle/hook-system-6Q5YTR53.js.map +7 -0
- package/bundle/hotskills-K7BM4YLB.js +12 -0
- package/bundle/hotskills-K7BM4YLB.js.map +7 -0
- package/bundle/install-6HRZVKUM.js +15 -0
- package/bundle/install-6HRZVKUM.js.map +7 -0
- package/bundle/install-log-IAPHYKD4.js +28 -0
- package/bundle/install-log-IAPHYKD4.js.map +7 -0
- package/bundle/install-manifest-SPQRUNXL.js +102 -0
- package/bundle/install-manifest-SPQRUNXL.js.map +7 -0
- package/bundle/install-validate-PVLZXYLQ.js +53 -0
- package/bundle/install-validate-PVLZXYLQ.js.map +7 -0
- package/bundle/irc-adapter-OI5UZSQF.js +293 -0
- package/bundle/irc-adapter-OI5UZSQF.js.map +7 -0
- package/bundle/irc-config-55YO6EGB.js +88 -0
- package/bundle/irc-config-55YO6EGB.js.map +7 -0
- package/bundle/logs-ZNYXX5PA.js +19 -0
- package/bundle/logs-ZNYXX5PA.js.map +7 -0
- package/bundle/media-utils-XNNDTYFI.js +4662 -0
- package/bundle/media-utils-XNNDTYFI.js.map +7 -0
- package/bundle/message-pipeline-LLH5SYMO.js +33 -0
- package/bundle/message-pipeline-LLH5SYMO.js.map +7 -0
- package/bundle/meta.json +41304 -0
- package/bundle/model-health-registry-35LQNVQR.js +11 -0
- package/bundle/model-health-registry-35LQNVQR.js.map +7 -0
- package/bundle/notification-Y5S5MMLV.js +13 -0
- package/bundle/notification-Y5S5MMLV.js.map +7 -0
- package/bundle/openrouter-credits-EDY7ETAU.js +32 -0
- package/bundle/openrouter-credits-EDY7ETAU.js.map +7 -0
- package/bundle/passwd-RRFV4CC5.js +133 -0
- package/bundle/passwd-RRFV4CC5.js.map +7 -0
- package/bundle/paths-G33RZWZ7.js +17 -0
- package/bundle/paths-G33RZWZ7.js.map +7 -0
- package/bundle/peer-client-52XYMNI7.js +156 -0
- package/bundle/peer-client-52XYMNI7.js.map +7 -0
- package/bundle/peer-config-VK6EDLN5.js +16 -0
- package/bundle/peer-config-VK6EDLN5.js.map +7 -0
- package/bundle/peer-sessions-EAXTNQ36.js +49 -0
- package/bundle/peer-sessions-EAXTNQ36.js.map +7 -0
- package/bundle/pending-callback-RIMQZ7FJ.js +40 -0
- package/bundle/pending-callback-RIMQZ7FJ.js.map +7 -0
- package/bundle/phase-transport-KYERDL2O.js +22 -0
- package/bundle/phase-transport-KYERDL2O.js.map +7 -0
- package/bundle/public/css/dashboard.css +542 -0
- package/bundle/public/index.html +180 -0
- package/bundle/public/js/app.js +437 -0
- package/bundle/public/memory-universe.js +384 -0
- package/bundle/responses-adapter-AAQTY3K4.js +30 -0
- package/bundle/responses-adapter-AAQTY3K4.js.map +7 -0
- package/bundle/restore-ZE3SEPSS.js +46 -0
- package/bundle/restore-ZE3SEPSS.js.map +7 -0
- package/bundle/self-healer-utils-DMUUXC47.js +43 -0
- package/bundle/self-healer-utils-DMUUXC47.js.map +7 -0
- package/bundle/skill-stats-LLEXEXLR.js +22 -0
- package/bundle/skill-stats-LLEXEXLR.js.map +7 -0
- package/bundle/sleep-OYIUOVQD.js +19 -0
- package/bundle/sleep-OYIUOVQD.js.map +7 -0
- package/bundle/soul-loader-54WCVNLJ.js +16 -0
- package/bundle/soul-loader-54WCVNLJ.js.map +7 -0
- package/bundle/src-JL4PVO23.js +8 -0
- package/bundle/src-JL4PVO23.js.map +7 -0
- package/bundle/sse-parser-anthropic-P7CE2MH2.js +72 -0
- package/bundle/sse-parser-anthropic-P7CE2MH2.js.map +7 -0
- package/bundle/sse-parser-responses-EQQA5FWN.js +63 -0
- package/bundle/sse-parser-responses-EQQA5FWN.js.map +7 -0
- package/bundle/ssrf-guard-FZCBYIVW.js +64 -0
- package/bundle/ssrf-guard-FZCBYIVW.js.map +7 -0
- package/bundle/start-FH3GRMJ4.js +35 -0
- package/bundle/start-FH3GRMJ4.js.map +7 -0
- package/bundle/stream-single-WSG4D53C.js +33 -0
- package/bundle/stream-single-WSG4D53C.js.map +7 -0
- package/bundle/stt-2UH3RITX.js +14 -0
- package/bundle/stt-2UH3RITX.js.map +7 -0
- package/bundle/subagent-runtime-LE2ZXH3G.js +12 -0
- package/bundle/subagent-runtime-LE2ZXH3G.js.map +7 -0
- package/bundle/system-message-T5R3EYYN.js +30 -0
- package/bundle/system-message-T5R3EYYN.js.map +7 -0
- package/bundle/system-status-KQ6KHFJ6.js +189 -0
- package/bundle/system-status-KQ6KHFJ6.js.map +7 -0
- package/bundle/task-store-K7CQDEPI.js +22 -0
- package/bundle/task-store-K7CQDEPI.js.map +7 -0
- package/bundle/telegram-adapter-2V3XUMT5.js +1060 -0
- package/bundle/telegram-adapter-2V3XUMT5.js.map +7 -0
- package/bundle/tool-registry-MU3OX4UI.js +38 -0
- package/bundle/tool-registry-MU3OX4UI.js.map +7 -0
- package/bundle/tool-sandbox-VYOK4ZOA.js +20 -0
- package/bundle/tool-sandbox-VYOK4ZOA.js.map +7 -0
- package/bundle/transport-config-YLXU33RO.js +57 -0
- package/bundle/transport-config-YLXU33RO.js.map +7 -0
- package/bundle/update-QCW5LXRN.js +13 -0
- package/bundle/update-QCW5LXRN.js.map +7 -0
- package/bundle/update-check-27KZSAP6.js +12 -0
- package/bundle/update-check-27KZSAP6.js.map +7 -0
- package/bundle/usage-tracker-OVVEVMOY.js +17 -0
- package/bundle/usage-tracker-OVVEVMOY.js.map +7 -0
- package/bundle/user-registry-D4SD73UV.js +16 -0
- package/bundle/user-registry-D4SD73UV.js.map +7 -0
- package/core/professor.json +14 -0
- package/core/prompts/browsing_prompt.md +39 -0
- package/core/prompts/compaction.md +32 -0
- package/core/skills/memory/classification/SKILL.md +37 -0
- package/core/skills/memory/memory-anomalies/SKILL.md +39 -0
- package/core/skills/memory/memory-search/SKILL.md +48 -0
- package/core/skills/memory/topic-save/SKILL.md +44 -0
- package/core/skills/ops/cron/SKILL.md +51 -0
- package/core/skills/ops/gdrive-backup/SKILL.md +15 -0
- package/core/skills/ops/session-start/SKILL.md +11 -0
- package/core/skills/ops/skill-authoring/SKILL.md +54 -0
- package/core/skills/ops/system-health/SKILL.md +104 -0
- package/core/skills/ops/troubleshooting/SKILL.md +48 -0
- package/core/skills/ops/trust-gating/SKILL.md +30 -0
- package/core/skills/tools/a2a-communication/SKILL.md +68 -0
- package/core/skills/tools/browse-delegate/SKILL.md +27 -0
- package/core/skills/tools/browser/SKILL.md +36 -0
- package/core/skills/tools/clawhub/SKILL.md +44 -0
- package/core/skills/tools/delegation/SKILL.md +48 -0
- package/core/skills/tools/fxtwitter/SKILL.md +52 -0
- package/core/skills/tools/gmail/SKILL.md +44 -0
- package/core/skills/tools/irc-chat/SKILL.md +84 -0
- package/core/skills/tools/linear/SKILL.md +90 -0
- package/core/skills/tools/mcporter/SKILL.md +46 -0
- package/core/skills/tools/model-scout/SKILL.md +132 -0
- package/core/skills/tools/model-scout/scout-add-model.py +67 -0
- package/core/skills/tools/model-scout/scout-ollama.py +116 -0
- package/core/skills/tools/model-scout/scout-openrouter.py +85 -0
- package/core/skills/tools/nlm/SKILL.md +40 -0
- package/core/skills/tools/todo/SKILL.md +30 -0
- package/core/skills/tools/twitterX/SKILL.md +52 -0
- package/core/skills/tools/twitterX/scripts/abtars-tweet.js +532 -0
- package/core/skills/tools/twitterX/scripts/package.json +1 -0
- package/core/skills/tools/web-fetch/SKILL.md +29 -0
- package/package.json +59 -0
- package/scripts/abtars-daemon.service +23 -0
- package/scripts/abtars-fetch.sh +42 -0
- package/scripts/abtars-watchdog.service +13 -0
- package/scripts/abtars.sh +14 -0
- package/scripts/abtars@.service +21 -0
- package/scripts/browser-patchright.sh +79 -0
- package/scripts/com.abtars.daemon.plist +24 -0
- package/scripts/com.abtars.watchdog.plist +27 -0
- package/scripts/daily-backup.sh +62 -0
- package/scripts/doctor.sh +553 -0
- package/scripts/hooks/audit-logger.sh +22 -0
- package/scripts/upgrade-deps.sh +64 -0
- package/scripts/watchdog.sh +309 -0
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
function abtarsHome() {
|
|
5
|
+
return process.env.ABTARS_HOME ?? join(homedir(), ".abtars");
|
|
6
|
+
}
|
|
7
|
+
function reportsDir(cat) {
|
|
8
|
+
return join(abtarsHome(), "reports", cat);
|
|
9
|
+
}
|
|
10
|
+
function localDate() {
|
|
11
|
+
const d = /* @__PURE__ */ new Date();
|
|
12
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
13
|
+
}
|
|
14
|
+
function logAndSwallow(_tag, _ctx, _err) {
|
|
15
|
+
return void 0;
|
|
16
|
+
}
|
|
17
|
+
import { join, basename } from "node:path";
|
|
18
|
+
const AB_HOME = abtarsHome();
|
|
19
|
+
const TWITTER_DIR = join(AB_HOME, "workspace", "twitterX");
|
|
20
|
+
const COOKIE_PATH = join(AB_HOME, "secret", "cookies", "x-cookies.json");
|
|
21
|
+
const BASE_FOLLOWS = join(TWITTER_DIR, process.env["TWEET_BASE_FOLLOWS_FILE"] ?? "base.follows.json");
|
|
22
|
+
const AGENT_FOLLOWS = join(TWITTER_DIR, process.env["TWEET_FOLLOWS_FILE"] ?? "agent.follows.json");
|
|
23
|
+
const REPORTS_DIR = reportsDir("x");
|
|
24
|
+
const OUTPUT_DIR = join(TWITTER_DIR, "output");
|
|
25
|
+
async function sendReportToTelegram(filePath, caption) {
|
|
26
|
+
const token = process.env["TELEGRAM_BOT_TOKEN"];
|
|
27
|
+
const chatId = process.env["AGENTBRIDGE_MAIN_CHAT_ID"];
|
|
28
|
+
if (!token || !chatId) return;
|
|
29
|
+
if (!existsSync(filePath)) return;
|
|
30
|
+
const buf = readFileSync(filePath);
|
|
31
|
+
const form = new FormData();
|
|
32
|
+
form.append("chat_id", chatId);
|
|
33
|
+
const blob = new Blob([buf], { type: "text/markdown" });
|
|
34
|
+
form.append("document", blob, basename(filePath));
|
|
35
|
+
form.append("caption", caption.slice(0, 1024));
|
|
36
|
+
const res = await fetch(`https://api.telegram.org/bot${token}/sendDocument`, { method: "POST", body: form });
|
|
37
|
+
if (!res.ok) {
|
|
38
|
+
const text = await res.text().catch(() => "");
|
|
39
|
+
throw new Error(`Telegram sendDocument failed (${res.status}): ${text}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const AI_BIO_KEYWORDS = /\b(ai|ml|llm|machine.?learning|deep.?learning|neural|nlp|computer.?vision|reinforcement|transformer|diffusion|robotics|research|phd|professor|scientist)\b/i;
|
|
43
|
+
function loadApiKey() {
|
|
44
|
+
if (!existsSync(COOKIE_PATH)) return void 0;
|
|
45
|
+
try {
|
|
46
|
+
const raw = readFileSync(COOKIE_PATH, "utf8");
|
|
47
|
+
const parsed = JSON.parse(raw);
|
|
48
|
+
const cookieStr = Object.entries(parsed).map(([k, v]) => `${k}=${v}`).join("; ") + ";";
|
|
49
|
+
return Buffer.from(cookieStr).toString("base64");
|
|
50
|
+
} catch {
|
|
51
|
+
return void 0;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function loadFollows() {
|
|
55
|
+
const handles = /* @__PURE__ */ new Set();
|
|
56
|
+
for (const path of [BASE_FOLLOWS, AGENT_FOLLOWS]) {
|
|
57
|
+
if (!existsSync(path)) continue;
|
|
58
|
+
try {
|
|
59
|
+
const raw = JSON.parse(readFileSync(path, "utf8"));
|
|
60
|
+
if (Array.isArray(raw)) {
|
|
61
|
+
raw.forEach((e) => handles.add(e.handle.replace(/^@/, "").toLowerCase()));
|
|
62
|
+
} else {
|
|
63
|
+
raw.handles?.forEach((h) => handles.add(h.replace(/^@/, "").toLowerCase()));
|
|
64
|
+
raw.entries?.forEach((e) => handles.add(e.handle.replace(/^@/, "").toLowerCase()));
|
|
65
|
+
}
|
|
66
|
+
} catch (err) {
|
|
67
|
+
logAndSwallow("abtars_tweet", "op", err);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return [...handles];
|
|
71
|
+
}
|
|
72
|
+
async function fetchTweet(url) {
|
|
73
|
+
const match = url.match(/(?:twitter\.com|x\.com)\/(\w+)\/status\/(\d+)/);
|
|
74
|
+
if (!match) {
|
|
75
|
+
console.error("Invalid tweet URL. Expected: https://x.com/user/status/123");
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
const [, user, id] = match;
|
|
79
|
+
const res = await fetch(`https://api.fxtwitter.com/${user}/status/${id}`);
|
|
80
|
+
const data = await res.json();
|
|
81
|
+
if (data.code !== 200) {
|
|
82
|
+
console.error(`FxTwitter error: ${data.message}`);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
const t = data.tweet;
|
|
86
|
+
console.log(JSON.stringify({
|
|
87
|
+
id: t.id,
|
|
88
|
+
author: t.author?.name,
|
|
89
|
+
handle: t.author?.screen_name,
|
|
90
|
+
text: t.text,
|
|
91
|
+
likes: t.likes,
|
|
92
|
+
retweets: t.retweets,
|
|
93
|
+
replies: t.replies,
|
|
94
|
+
views: t.views,
|
|
95
|
+
created_at: t.created_at,
|
|
96
|
+
media: t.media
|
|
97
|
+
}, null, 2));
|
|
98
|
+
}
|
|
99
|
+
async function loadRettiwt() {
|
|
100
|
+
try {
|
|
101
|
+
return await import("rettiwt-api");
|
|
102
|
+
} catch {
|
|
103
|
+
console.error("rettiwt-api not installed. Run: npm install rettiwt-api");
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function fetchUser(handle) {
|
|
108
|
+
const { Rettiwt } = await loadRettiwt();
|
|
109
|
+
const r = new Rettiwt();
|
|
110
|
+
const d = await r.user.details(handle.replace(/^@/, ""));
|
|
111
|
+
if (!d) {
|
|
112
|
+
console.error("User not found");
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
console.log(JSON.stringify(d.toJSON(), null, 2));
|
|
116
|
+
}
|
|
117
|
+
const GQL_USER_TWEETS = "https://x.com/i/api/graphql/E3opETHurmVJflFsUBVuUQ/UserTweets";
|
|
118
|
+
async function fetchTimeline(handle, count) {
|
|
119
|
+
const { Rettiwt } = await loadRettiwt();
|
|
120
|
+
const r = new Rettiwt();
|
|
121
|
+
const clean = handle.replace(/^@/, "");
|
|
122
|
+
const user = await r.user.details(clean);
|
|
123
|
+
if (!user) throw new Error(`User @${clean} not found`);
|
|
124
|
+
const auth = loadCookieHeader();
|
|
125
|
+
if (auth) {
|
|
126
|
+
try {
|
|
127
|
+
return await fetchTimelineGql(user.id, user.fullName ?? clean, clean, count);
|
|
128
|
+
} catch (err) {
|
|
129
|
+
logAndSwallow("abtars_tweet", "op", err);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const data = await r.user.timeline(user.id, count);
|
|
133
|
+
return data.list.map((t) => {
|
|
134
|
+
const j = t.toJSON();
|
|
135
|
+
const likes = j.likeCount ?? 0;
|
|
136
|
+
const retweets = j.retweetCount ?? 0;
|
|
137
|
+
const views = j.viewCount ?? 0;
|
|
138
|
+
return {
|
|
139
|
+
id: j.id,
|
|
140
|
+
text: j.fullText ?? "",
|
|
141
|
+
author: user.fullName ?? clean,
|
|
142
|
+
handle: clean,
|
|
143
|
+
likes,
|
|
144
|
+
retweets,
|
|
145
|
+
views,
|
|
146
|
+
createdAt: j.createdAt,
|
|
147
|
+
score: likes + retweets * 3 + (views ? views / 1e3 : 0)
|
|
148
|
+
};
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
async function fetchTimelineGql(userId, authorName, handle, count) {
|
|
152
|
+
const data = await twitterGql(GQL_USER_TWEETS, {
|
|
153
|
+
userId,
|
|
154
|
+
count,
|
|
155
|
+
includePromotedContent: false,
|
|
156
|
+
withQuickPromoteEligibilityTweetFields: true,
|
|
157
|
+
withVoice: true,
|
|
158
|
+
withV2Timeline: true
|
|
159
|
+
});
|
|
160
|
+
const instructions = data?.data?.user?.result?.timeline_v2?.timeline?.instructions ?? [];
|
|
161
|
+
const entries = instructions.find((i) => i.type === "TimelineAddEntries")?.entries ?? [];
|
|
162
|
+
const tweets = [];
|
|
163
|
+
for (const e of entries) {
|
|
164
|
+
const tw = e.content?.itemContent?.tweet_results?.result;
|
|
165
|
+
if (!tw?.legacy) continue;
|
|
166
|
+
const p = parseTweetResult(tw);
|
|
167
|
+
const likes = p.likes, retweets = p.retweets, views = p.views;
|
|
168
|
+
tweets.push({
|
|
169
|
+
id: p.id,
|
|
170
|
+
text: p.text,
|
|
171
|
+
author: p.name || authorName,
|
|
172
|
+
handle: p.handle || handle,
|
|
173
|
+
likes,
|
|
174
|
+
retweets,
|
|
175
|
+
views,
|
|
176
|
+
createdAt: p.createdAt,
|
|
177
|
+
score: likes + retweets * 3 + (views ? views / 1e3 : 0)
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
return tweets;
|
|
181
|
+
}
|
|
182
|
+
async function runFeed(format, count, topN, discover, outputPath) {
|
|
183
|
+
const handles = loadFollows();
|
|
184
|
+
if (handles.length === 0) {
|
|
185
|
+
console.error("No follows found. Create ~/.abtars/workspace/twitterX/base.follows.json or agent.follows.json");
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
console.error(`Fetching timelines for ${handles.length} handles...`);
|
|
189
|
+
const allTweets = [];
|
|
190
|
+
for (const h of handles) {
|
|
191
|
+
try {
|
|
192
|
+
console.error(` @${h}...`);
|
|
193
|
+
const tweets = await fetchTimeline(h, count);
|
|
194
|
+
const cutoff = Date.now() - 24 * 60 * 60 * 1e3;
|
|
195
|
+
const recent = tweets.filter((t) => new Date(t.createdAt).getTime() > cutoff);
|
|
196
|
+
allTweets.push(...recent);
|
|
197
|
+
} catch (e) {
|
|
198
|
+
console.error(` \u26A0 @${h} failed: ${e.message}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
allTweets.sort((a, b) => b.score - a.score);
|
|
202
|
+
const top = allTweets.slice(0, topN);
|
|
203
|
+
let candidates = [];
|
|
204
|
+
if (discover && top.length > 0) {
|
|
205
|
+
candidates = await runDiscover(top.slice(0, 5), handles);
|
|
206
|
+
}
|
|
207
|
+
const date = localDate();
|
|
208
|
+
const outFile = outputPath ?? join(OUTPUT_DIR, `tweets-${date}.json`);
|
|
209
|
+
mkdirSync(join(outFile, ".."), { recursive: true });
|
|
210
|
+
const payload = { date, source: "abtars-tweet", totalCollected: allTweets.length, tweets: top, discover: candidates };
|
|
211
|
+
writeFileSync(outFile, JSON.stringify(payload, null, 2), "utf8");
|
|
212
|
+
console.error(`\u{1F4C4} ${top.length} tweets written to ${outFile}`);
|
|
213
|
+
if (format === "md") {
|
|
214
|
+
const md = renderNewsletter(top, candidates, date);
|
|
215
|
+
mkdirSync(REPORTS_DIR, { recursive: true });
|
|
216
|
+
const reportPath = join(REPORTS_DIR, `AI-Daily-${date}.md`);
|
|
217
|
+
writeFileSync(reportPath, md, "utf8");
|
|
218
|
+
console.error(`\u{1F4F0} Newsletter written to ${reportPath}`);
|
|
219
|
+
await sendReportToTelegram(reportPath, `AI Daily ${date}`).catch((err) => {
|
|
220
|
+
console.error(`\u26A0 Telegram send failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const BEARER = "AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA";
|
|
225
|
+
const GQL_TWEET_DETAIL = "https://x.com/i/api/graphql/97JF30KziU00483E_8elBA/TweetDetail";
|
|
226
|
+
const UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
|
|
227
|
+
const GQL_FEATURES = {
|
|
228
|
+
rweb_tipjar_consumption_enabled: true,
|
|
229
|
+
responsive_web_graphql_exclude_directive_enabled: true,
|
|
230
|
+
verified_phone_label_enabled: false,
|
|
231
|
+
creator_subscriptions_tweet_preview_api_enabled: true,
|
|
232
|
+
responsive_web_graphql_timeline_navigation_enabled: true,
|
|
233
|
+
responsive_web_graphql_skip_user_profile_image_extensions_enabled: false,
|
|
234
|
+
communities_web_enable_tweet_community_results_fetch: true,
|
|
235
|
+
c9s_tweet_anatomy_moderator_badge_enabled: true,
|
|
236
|
+
articles_preview_enabled: true,
|
|
237
|
+
responsive_web_edit_tweet_api_enabled: true,
|
|
238
|
+
graphql_is_translatable_rweb_tweet_is_translatable_enabled: true,
|
|
239
|
+
view_counts_everywhere_api_enabled: true,
|
|
240
|
+
longform_notetweets_consumption_enabled: true,
|
|
241
|
+
responsive_web_twitter_article_tweet_consumption_enabled: true,
|
|
242
|
+
tweet_awards_web_tipping_enabled: false,
|
|
243
|
+
creator_subscriptions_quote_tweet_preview_enabled: false,
|
|
244
|
+
freedom_of_speech_not_reach_fetch_enabled: true,
|
|
245
|
+
standardized_nudges_misinfo: true,
|
|
246
|
+
tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled: true,
|
|
247
|
+
rweb_video_timestamps_enabled: true,
|
|
248
|
+
longform_notetweets_rich_text_read_enabled: true,
|
|
249
|
+
longform_notetweets_inline_media_enabled: true,
|
|
250
|
+
responsive_web_enhance_cards_enabled: false
|
|
251
|
+
};
|
|
252
|
+
function loadCookieHeader() {
|
|
253
|
+
if (!existsSync(COOKIE_PATH)) return void 0;
|
|
254
|
+
try {
|
|
255
|
+
const parsed = JSON.parse(readFileSync(COOKIE_PATH, "utf8"));
|
|
256
|
+
const cookie = Object.entries(parsed).map(([k, v]) => `${k}=${v}`).join("; ");
|
|
257
|
+
return { cookie, csrf: parsed.ct0 };
|
|
258
|
+
} catch {
|
|
259
|
+
return void 0;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
async function twitterGql(url, variables) {
|
|
263
|
+
const auth = loadCookieHeader();
|
|
264
|
+
if (!auth) throw new Error("User auth required. Refresh cookies in ~/.abtars/secret/cookies/x-cookies.json");
|
|
265
|
+
const params = new URLSearchParams({
|
|
266
|
+
variables: JSON.stringify(variables),
|
|
267
|
+
features: JSON.stringify(GQL_FEATURES)
|
|
268
|
+
});
|
|
269
|
+
const res = await fetch(`${url}?${params}`, {
|
|
270
|
+
headers: {
|
|
271
|
+
authorization: `Bearer ${BEARER}`,
|
|
272
|
+
"x-csrf-token": auth.csrf,
|
|
273
|
+
cookie: auth.cookie,
|
|
274
|
+
"user-agent": UA
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
if (!res.ok) {
|
|
278
|
+
if (res.status === 403) throw new Error("403 \u2014 cookies may be expired. Refresh in ~/.abtars/secret/cookies/x-cookies.json");
|
|
279
|
+
throw new Error(`Twitter API ${res.status}: ${await res.text().catch(() => "")}`);
|
|
280
|
+
}
|
|
281
|
+
return res.json();
|
|
282
|
+
}
|
|
283
|
+
function extractTweetsFromTimeline(data) {
|
|
284
|
+
const entries = [];
|
|
285
|
+
const instructions = data?.data?.tweetResult?.result?.timeline?.instructions ?? data?.data?.threaded_conversation_with_injections_v2?.instructions ?? data?.data?.search_by_raw_query?.search_timeline?.timeline?.instructions ?? [];
|
|
286
|
+
for (const inst of instructions) {
|
|
287
|
+
for (const entry of inst.entries ?? []) {
|
|
288
|
+
const tweet = entry.content?.itemContent?.tweet_results?.result ?? entry.content?.items?.[0]?.item?.itemContent?.tweet_results?.result;
|
|
289
|
+
if (tweet?.legacy) entries.push(tweet);
|
|
290
|
+
for (const item of entry.content?.items ?? []) {
|
|
291
|
+
const t = item.item?.itemContent?.tweet_results?.result;
|
|
292
|
+
if (t?.legacy) entries.push(t);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return entries;
|
|
297
|
+
}
|
|
298
|
+
function parseTweetResult(t) {
|
|
299
|
+
const legacy = t.legacy ?? {};
|
|
300
|
+
const userResult = t.core?.user_results?.result ?? {};
|
|
301
|
+
const userCore = userResult.core ?? {};
|
|
302
|
+
const userLegacy = userResult.legacy ?? {};
|
|
303
|
+
return {
|
|
304
|
+
id: legacy.id_str ?? t.rest_id ?? "",
|
|
305
|
+
handle: userCore.screen_name ?? userLegacy.screen_name ?? "",
|
|
306
|
+
name: userCore.name ?? userLegacy.name ?? "",
|
|
307
|
+
text: legacy.full_text ?? "",
|
|
308
|
+
likes: legacy.favorite_count ?? 0,
|
|
309
|
+
retweets: legacy.retweet_count ?? 0,
|
|
310
|
+
views: parseInt(t.views?.count ?? "0", 10),
|
|
311
|
+
createdAt: legacy.created_at ?? ""
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
async function fetchReplies(tweetId, minLikes) {
|
|
315
|
+
const data = await twitterGql(GQL_TWEET_DETAIL, {
|
|
316
|
+
focalTweetId: tweetId,
|
|
317
|
+
with_rux_injections: false,
|
|
318
|
+
rankingMode: "Relevance",
|
|
319
|
+
includePromotedContent: false,
|
|
320
|
+
withCommunity: true,
|
|
321
|
+
withQuickPromoteEligibilityTweetFields: true,
|
|
322
|
+
withBirdwatchNotes: true,
|
|
323
|
+
withVoice: true
|
|
324
|
+
});
|
|
325
|
+
const all = extractTweetsFromTimeline(data);
|
|
326
|
+
const seen = /* @__PURE__ */ new Set();
|
|
327
|
+
const replies = all.map(parseTweetResult).filter((t) => {
|
|
328
|
+
if (t.id === tweetId || seen.has(t.id) || t.likes < minLikes) return false;
|
|
329
|
+
seen.add(t.id);
|
|
330
|
+
return true;
|
|
331
|
+
}).sort((a, b) => b.likes - a.likes);
|
|
332
|
+
console.log(JSON.stringify(replies, null, 2));
|
|
333
|
+
}
|
|
334
|
+
async function searchTweets(_query, _count) {
|
|
335
|
+
console.error("\u26A0 Direct X search is restricted. Use --timeline per handle or web search for discovery.");
|
|
336
|
+
console.error(" Tip: abtars-tweet --replies <tweet-id> works for finding interesting commenters.");
|
|
337
|
+
process.exit(1);
|
|
338
|
+
}
|
|
339
|
+
async function runDiscover(topTweets, knownHandles) {
|
|
340
|
+
const auth = loadCookieHeader();
|
|
341
|
+
if (!auth) {
|
|
342
|
+
console.error(" \u26A0 Skipping discovery \u2014 user auth required. Refresh cookies.");
|
|
343
|
+
return [];
|
|
344
|
+
}
|
|
345
|
+
const { Rettiwt } = await loadRettiwt();
|
|
346
|
+
const guestRettiwt = new Rettiwt();
|
|
347
|
+
const known = new Set(knownHandles.map((h) => h.toLowerCase()));
|
|
348
|
+
const candidates = [];
|
|
349
|
+
const seen = /* @__PURE__ */ new Set();
|
|
350
|
+
console.error(`
|
|
351
|
+
\u{1F50D} Discovering new follows from top ${topTweets.length} tweets...`);
|
|
352
|
+
for (const tweet of topTweets) {
|
|
353
|
+
try {
|
|
354
|
+
console.error(` Checking replies on @${tweet.handle}/${tweet.id}...`);
|
|
355
|
+
const data = await twitterGql(GQL_TWEET_DETAIL, {
|
|
356
|
+
focalTweetId: tweet.id,
|
|
357
|
+
with_rux_injections: false,
|
|
358
|
+
rankingMode: "Relevance",
|
|
359
|
+
includePromotedContent: false,
|
|
360
|
+
withCommunity: true,
|
|
361
|
+
withQuickPromoteEligibilityTweetFields: true,
|
|
362
|
+
withBirdwatchNotes: true,
|
|
363
|
+
withVoice: true
|
|
364
|
+
});
|
|
365
|
+
const all = extractTweetsFromTimeline(data);
|
|
366
|
+
const goodReplies = all.map(parseTweetResult).filter((t) => t.id !== tweet.id && t.likes >= 50).sort((a, b) => b.likes - a.likes).slice(0, 5);
|
|
367
|
+
for (const reply of goodReplies) {
|
|
368
|
+
const rHandle = reply.handle.toLowerCase();
|
|
369
|
+
if (!rHandle || known.has(rHandle) || seen.has(rHandle)) continue;
|
|
370
|
+
seen.add(rHandle);
|
|
371
|
+
try {
|
|
372
|
+
const profile = await guestRettiwt.user.details(rHandle);
|
|
373
|
+
if (!profile) continue;
|
|
374
|
+
const pj = profile.toJSON();
|
|
375
|
+
const bio = pj.description ?? "";
|
|
376
|
+
if (!AI_BIO_KEYWORDS.test(bio)) continue;
|
|
377
|
+
candidates.push({
|
|
378
|
+
handle: rHandle,
|
|
379
|
+
name: pj.fullName ?? rHandle,
|
|
380
|
+
bio,
|
|
381
|
+
followers: pj.followersCount ?? 0,
|
|
382
|
+
foundVia: `reply on @${tweet.handle}'s tweet`,
|
|
383
|
+
replyLikes: reply.likes,
|
|
384
|
+
replyText: reply.text.slice(0, 200)
|
|
385
|
+
});
|
|
386
|
+
} catch (err) {
|
|
387
|
+
logAndSwallow("abtars_tweet", "op", err);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
} catch (e) {
|
|
391
|
+
console.error(` \u26A0 Replies failed for ${tweet.id}: ${e.message}`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
console.error(` Found ${candidates.length} candidates`);
|
|
395
|
+
return candidates.sort((a, b) => b.replyLikes - a.replyLikes);
|
|
396
|
+
}
|
|
397
|
+
function renderNewsletter(tweets, candidates, date) {
|
|
398
|
+
const lines = [`# AI Daily Brief \u2014 ${date}
|
|
399
|
+
`];
|
|
400
|
+
if (tweets.length === 0) {
|
|
401
|
+
lines.push("No tweets found in the last 24 hours from followed accounts.\n");
|
|
402
|
+
return lines.join("\n");
|
|
403
|
+
}
|
|
404
|
+
lines.push("## \u{1F525} Top Tweets (by engagement)\n");
|
|
405
|
+
tweets.forEach((t, i) => {
|
|
406
|
+
const text = t.text.length > 280 ? t.text.slice(0, 277) + "..." : t.text;
|
|
407
|
+
lines.push(`### ${i + 1}. @${t.handle} \u2014 ${t.author}`);
|
|
408
|
+
lines.push(`- **Likes:** ${t.likes} | **Retweets:** ${t.retweets} | **Views:** ${t.views ?? "N/A"}`);
|
|
409
|
+
lines.push(`- ${text.replace(/\n/g, " ")}`);
|
|
410
|
+
lines.push(`- \u{1F517} https://x.com/${t.handle}/status/${t.id}
|
|
411
|
+
`);
|
|
412
|
+
});
|
|
413
|
+
if (candidates.length > 0) {
|
|
414
|
+
lines.push("## \u{1F464} Discover \u2014 New Follows\n");
|
|
415
|
+
candidates.forEach((c) => {
|
|
416
|
+
lines.push(`### @${c.handle} \u2014 ${c.name}`);
|
|
417
|
+
lines.push(`- **Bio:** ${c.bio.slice(0, 200)}`);
|
|
418
|
+
lines.push(`- **Followers:** ${c.followers.toLocaleString()}`);
|
|
419
|
+
lines.push(`- **Found via:** ${c.foundVia}`);
|
|
420
|
+
lines.push(`- **Their reply** (${c.replyLikes} likes): ${c.replyText.replace(/\n/g, " ")}
|
|
421
|
+
`);
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
lines.push("## \u{1F4CA} Signals & Trends\n");
|
|
425
|
+
lines.push("_(To be filled by sleep cycle analysis)_\n");
|
|
426
|
+
lines.push(`---
|
|
427
|
+
*Auto-generated via abtars-tweet. Sources: X (via rettiwt-api).*
|
|
428
|
+
`);
|
|
429
|
+
return lines.join("\n");
|
|
430
|
+
}
|
|
431
|
+
function parseArgs() {
|
|
432
|
+
const args = process.argv.slice(2);
|
|
433
|
+
let command = "feed";
|
|
434
|
+
let target = "";
|
|
435
|
+
let count = 20;
|
|
436
|
+
let topN = 12;
|
|
437
|
+
let format = "md";
|
|
438
|
+
let discover = false;
|
|
439
|
+
let minLikes = 50;
|
|
440
|
+
let output;
|
|
441
|
+
for (let i = 0; i < args.length; i++) {
|
|
442
|
+
switch (args[i]) {
|
|
443
|
+
case "--fetch":
|
|
444
|
+
command = "fetch";
|
|
445
|
+
target = args[++i] ?? "";
|
|
446
|
+
break;
|
|
447
|
+
case "--timeline":
|
|
448
|
+
command = "timeline";
|
|
449
|
+
target = args[++i] ?? "";
|
|
450
|
+
break;
|
|
451
|
+
case "--user":
|
|
452
|
+
command = "user";
|
|
453
|
+
target = args[++i] ?? "";
|
|
454
|
+
break;
|
|
455
|
+
case "--feed":
|
|
456
|
+
command = "feed";
|
|
457
|
+
break;
|
|
458
|
+
case "--replies":
|
|
459
|
+
command = "replies";
|
|
460
|
+
target = args[++i] ?? "";
|
|
461
|
+
break;
|
|
462
|
+
case "--search":
|
|
463
|
+
command = "search";
|
|
464
|
+
target = args[++i] ?? "";
|
|
465
|
+
break;
|
|
466
|
+
case "--discover":
|
|
467
|
+
discover = true;
|
|
468
|
+
break;
|
|
469
|
+
case "--count":
|
|
470
|
+
count = parseInt(args[++i] ?? "20", 10);
|
|
471
|
+
break;
|
|
472
|
+
case "--top":
|
|
473
|
+
topN = parseInt(args[++i] ?? "12", 10);
|
|
474
|
+
break;
|
|
475
|
+
case "--min-likes":
|
|
476
|
+
minLikes = parseInt(args[++i] ?? "50", 10);
|
|
477
|
+
break;
|
|
478
|
+
case "--format":
|
|
479
|
+
format = args[++i] ?? "md";
|
|
480
|
+
break;
|
|
481
|
+
case "--output":
|
|
482
|
+
output = args[++i] ?? "";
|
|
483
|
+
break;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return { command, target, count, topN, format, discover, minLikes, output };
|
|
487
|
+
}
|
|
488
|
+
async function main() {
|
|
489
|
+
if (process.argv.includes("--help")) {
|
|
490
|
+
console.log(`abtars-tweet \u2014 fetch tweets via rettiwt-api + FxTwitter.
|
|
491
|
+
|
|
492
|
+
Usage:
|
|
493
|
+
abtars-tweet --fetch <tweet-url> # single tweet via FxTwitter
|
|
494
|
+
abtars-tweet --timeline <handle> [--count N] # user timeline
|
|
495
|
+
abtars-tweet --feed [--format md] # all followed handles \u2192 ranked output
|
|
496
|
+
abtars-tweet --feed --discover # feed + reply analysis for new follows
|
|
497
|
+
abtars-tweet --replies <tweet-id> # replies on a tweet
|
|
498
|
+
abtars-tweet --search "query" # search X
|
|
499
|
+
abtars-tweet --user <handle> # user profile info`);
|
|
500
|
+
process.exit(0);
|
|
501
|
+
}
|
|
502
|
+
const { command, target, count, topN, format, discover, minLikes, output } = parseArgs();
|
|
503
|
+
switch (command) {
|
|
504
|
+
case "fetch":
|
|
505
|
+
await fetchTweet(target);
|
|
506
|
+
break;
|
|
507
|
+
case "user":
|
|
508
|
+
await fetchUser(target);
|
|
509
|
+
break;
|
|
510
|
+
case "timeline": {
|
|
511
|
+
const tweets = await fetchTimeline(target, count);
|
|
512
|
+
console.log(JSON.stringify(tweets, null, 2));
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
case "replies":
|
|
516
|
+
await fetchReplies(target, minLikes);
|
|
517
|
+
break;
|
|
518
|
+
case "search":
|
|
519
|
+
await searchTweets(target, count);
|
|
520
|
+
break;
|
|
521
|
+
case "feed":
|
|
522
|
+
await runFeed(format, count, topN, discover, output);
|
|
523
|
+
break;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
main().catch((e) => {
|
|
527
|
+
console.error(`Fatal: ${e.message}`);
|
|
528
|
+
process.exit(1);
|
|
529
|
+
});
|
|
530
|
+
export {
|
|
531
|
+
loadApiKey
|
|
532
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "type": "module" }
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: web-fetch
|
|
3
|
+
description: Fetch web page content as markdown using lightpanda (Level 1 browsing)
|
|
4
|
+
user-invocable: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Web Fetch (Level 1)
|
|
8
|
+
|
|
9
|
+
Fast, lightweight page fetching. Returns clean markdown. No browser session, no login.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
abtars-fetch "<url>"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Returns markdown to stdout. Truncated at 50K chars.
|
|
16
|
+
|
|
17
|
+
## When to use
|
|
18
|
+
- Reading documentation, articles, news
|
|
19
|
+
- Checking public data (stock prices, weather, APIs)
|
|
20
|
+
- Any page that works without JavaScript or login
|
|
21
|
+
|
|
22
|
+
## When NOT to use — escalate to Level 2
|
|
23
|
+
- Output is empty or says "enable JavaScript"
|
|
24
|
+
- Page requires login or authentication
|
|
25
|
+
- Multi-page navigation needed (click through, fill forms)
|
|
26
|
+
- Anti-bot protection (Cloudflare, captcha)
|
|
27
|
+
- Screenshots needed
|
|
28
|
+
|
|
29
|
+
For Level 2: use `abtars-browse --task "description" --chat-id <CHAT_ID>`
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "abtars",
|
|
3
|
+
"version": "0.1.0-alpha.1",
|
|
4
|
+
"description": "Standalone agent bridging Telegram to Kiro CLI via tmux/ACP",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "bundle/abtars.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc && rm -rf dist/components/dashboard/public && cp -r src/components/dashboard/public dist/components/dashboard/public && git log -1 --format='{\"hash\":\"%h\",\"date\":\"%ci\"}' > dist/build-info.json",
|
|
9
|
+
"bundle": "rm -rf bundle && cd ../abmind && npm run build && cd ../abtars && node esbuild.config.js && rm -rf bundle/public && cp -r src/components/dashboard/public bundle/public && cp -r agents bundle/agents",
|
|
10
|
+
"dev": "node --import tsx src/main.ts",
|
|
11
|
+
"start": "node dist/main.js",
|
|
12
|
+
"typecheck": "tsc --noEmit",
|
|
13
|
+
"check-imports": "bash -c '! grep -rE \"from \\\"abmind/|from '\\''abmind/\" src --include=*.ts | grep -vE \"abmind/deploy-lib\\\"|abmind/deploy-lib'\\''\"'",
|
|
14
|
+
"test": "vitest --run",
|
|
15
|
+
"test:watch": "vitest"
|
|
16
|
+
},
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=22.0.0"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@agentclientprotocol/sdk": "^0.14.1",
|
|
22
|
+
"@andresaya/edge-tts": "^1.8.0",
|
|
23
|
+
"@clack/prompts": "^1.2.0",
|
|
24
|
+
"abmind": "file:../abmind",
|
|
25
|
+
"cron-parser": "^5.5.0",
|
|
26
|
+
"discord.js": "^14.26.3",
|
|
27
|
+
"dotenv": "^16.6.1",
|
|
28
|
+
"file-type": "^22.0.1",
|
|
29
|
+
"jimp": "1.6.0",
|
|
30
|
+
"patchright": "^1.59.4",
|
|
31
|
+
"pdf-parse": "^2.4.5",
|
|
32
|
+
"ws": "^8.20.0",
|
|
33
|
+
"youtube-transcript": "^1.3.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^22.19.17",
|
|
37
|
+
"@types/pdf-parse": "^1.1.5",
|
|
38
|
+
"@types/ws": "^8.18.1",
|
|
39
|
+
"esbuild": "0.25.4",
|
|
40
|
+
"fast-check": "^4.7.0",
|
|
41
|
+
"tsx": "^4.21.0",
|
|
42
|
+
"typescript": "^5.9.3",
|
|
43
|
+
"vitest": "^4.1.5"
|
|
44
|
+
},
|
|
45
|
+
"bin": {
|
|
46
|
+
"abtars": "bundle/abtars-cli.js",
|
|
47
|
+
"abtars-browser": "bundle/abtars-browser.js",
|
|
48
|
+
"abtars-restart": "bundle/abtars-restart.js"
|
|
49
|
+
},
|
|
50
|
+
"license": "Apache-2.0",
|
|
51
|
+
"files": [
|
|
52
|
+
"bundle/",
|
|
53
|
+
"core/",
|
|
54
|
+
"scripts/",
|
|
55
|
+
"README.md",
|
|
56
|
+
"LICENSE",
|
|
57
|
+
"CHANGELOG.md"
|
|
58
|
+
]
|
|
59
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=Abtars (supervised-daemon)
|
|
3
|
+
After=network-online.target
|
|
4
|
+
Wants=network-online.target
|
|
5
|
+
|
|
6
|
+
[Service]
|
|
7
|
+
Type=simple
|
|
8
|
+
User={{USER}}
|
|
9
|
+
Group={{USER}}
|
|
10
|
+
WorkingDirectory=/home/{{USER}}/.abtars
|
|
11
|
+
ExecStart=/home/{{USER}}/.abtars/watchdog.sh
|
|
12
|
+
Restart=always
|
|
13
|
+
RestartSec=5
|
|
14
|
+
Environment=NODE_ENV=production
|
|
15
|
+
Environment=ABTARS_HOME=/home/{{USER}}/.abtars
|
|
16
|
+
Environment=ABMIND_HOME=/home/{{USER}}/.abmind
|
|
17
|
+
Environment=SUPERVISION=systemd
|
|
18
|
+
StandardOutput=journal
|
|
19
|
+
StandardError=journal
|
|
20
|
+
SyslogIdentifier=abtars
|
|
21
|
+
|
|
22
|
+
[Install]
|
|
23
|
+
WantedBy=multi-user.target
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# abtars-fetch — fetch a URL and return clean markdown.
|
|
3
|
+
# Usage: abtars-fetch <url>
|
|
4
|
+
# Returns truncated markdown to stdout. Exits 1 on error.
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
MAX_CHARS=50000
|
|
8
|
+
|
|
9
|
+
if [[ $# -lt 1 ]]; then
|
|
10
|
+
echo "Usage: abtars-fetch <url>" >&2
|
|
11
|
+
exit 1
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
URL="$1"
|
|
15
|
+
|
|
16
|
+
if ! command -v lightpanda &>/dev/null; then
|
|
17
|
+
echo "ERROR: lightpanda not installed. Use Level 2 browse (abtars-browse) instead." >&2
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
OUTPUT=$(lightpanda fetch \
|
|
22
|
+
--dump markdown \
|
|
23
|
+
--strip-mode full \
|
|
24
|
+
--http-connect-timeout 10000 \
|
|
25
|
+
--http-timeout 15000 \
|
|
26
|
+
--wait-ms 10000 \
|
|
27
|
+
--block-private-networks \
|
|
28
|
+
"$URL" 2>/dev/null)
|
|
29
|
+
|
|
30
|
+
if [[ -z "$OUTPUT" ]]; then
|
|
31
|
+
echo "ERROR: Empty response. Page may require JavaScript or login. Use Level 2 browse (abtars-browse) instead." >&2
|
|
32
|
+
exit 1
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# Truncate to prevent context window blowout
|
|
36
|
+
if [[ ${#OUTPUT} -gt $MAX_CHARS ]]; then
|
|
37
|
+
echo "${OUTPUT:0:$MAX_CHARS}"
|
|
38
|
+
echo ""
|
|
39
|
+
echo "[Content truncated at ${MAX_CHARS} characters]"
|
|
40
|
+
else
|
|
41
|
+
echo "$OUTPUT"
|
|
42
|
+
fi
|