@harbinger-ai/harbinger 0.1.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/LICENSE +21 -0
- package/README.md +406 -0
- package/agents/README.md +76 -0
- package/agents/_template/CONFIG.yaml +7 -0
- package/agents/_template/HEARTBEAT.md +59 -0
- package/agents/_template/IDENTITY.md +4 -0
- package/agents/_template/SKILLS.md +1 -0
- package/agents/_template/SOUL.md +25 -0
- package/agents/_template/TOOLS.md +3 -0
- package/agents/binary-reverser/CONFIG.yaml +21 -0
- package/agents/binary-reverser/HEARTBEAT.md +65 -0
- package/agents/binary-reverser/IDENTITY.md +1 -0
- package/agents/binary-reverser/SKILLS.md +1 -0
- package/agents/binary-reverser/SOUL.md +23 -0
- package/agents/binary-reverser/TOOLS.md +99 -0
- package/agents/browser-agent/CONFIG.yaml +20 -0
- package/agents/browser-agent/HEARTBEAT.md +79 -0
- package/agents/browser-agent/IDENTITY.md +5 -0
- package/agents/browser-agent/SKILLS.md +86 -0
- package/agents/browser-agent/SOUL.md +23 -0
- package/agents/browser-agent/TOOLS.md +186 -0
- package/agents/cloud-infiltrator/CONFIG.yaml +22 -0
- package/agents/cloud-infiltrator/HEARTBEAT.md +78 -0
- package/agents/cloud-infiltrator/IDENTITY.md +1 -0
- package/agents/cloud-infiltrator/SKILLS.md +1 -0
- package/agents/cloud-infiltrator/SOUL.md +23 -0
- package/agents/cloud-infiltrator/TOOLS.md +68 -0
- package/agents/coding-assistant/CONFIG.yaml +22 -0
- package/agents/coding-assistant/HEARTBEAT.md +57 -0
- package/agents/coding-assistant/IDENTITY.md +5 -0
- package/agents/coding-assistant/SKILLS.md +69 -0
- package/agents/coding-assistant/SOUL.md +60 -0
- package/agents/coding-assistant/TOOLS.md +168 -0
- package/agents/learning-agent/CONFIG.yaml +21 -0
- package/agents/learning-agent/HEARTBEAT.md +63 -0
- package/agents/learning-agent/IDENTITY.md +5 -0
- package/agents/learning-agent/SKILLS.md +86 -0
- package/agents/learning-agent/SOUL.md +77 -0
- package/agents/learning-agent/TOOLS.md +145 -0
- package/agents/maintainer/CONFIG.yaml +31 -0
- package/agents/maintainer/HEARTBEAT.md +28 -0
- package/agents/maintainer/IDENTITY.md +33 -0
- package/agents/maintainer/SKILLS.md +24 -0
- package/agents/maintainer/SOUL.md +61 -0
- package/agents/maintainer/TOOLS.md +29 -0
- package/agents/maintainer/lib/engine.js +279 -0
- package/agents/maintainer/lib/safe-fixer.js +183 -0
- package/agents/morning-brief/CONFIG.yaml +22 -0
- package/agents/morning-brief/HEARTBEAT.md +60 -0
- package/agents/morning-brief/IDENTITY.md +5 -0
- package/agents/morning-brief/SKILLS.md +56 -0
- package/agents/morning-brief/SOUL.md +64 -0
- package/agents/morning-brief/TOOLS.md +112 -0
- package/agents/osint-detective/CONFIG.yaml +24 -0
- package/agents/osint-detective/HEARTBEAT.md +66 -0
- package/agents/osint-detective/IDENTITY.md +1 -0
- package/agents/osint-detective/SKILLS.md +1 -0
- package/agents/osint-detective/SOUL.md +23 -0
- package/agents/osint-detective/TOOLS.md +81 -0
- package/agents/recon-scout/CONFIG.yaml +22 -0
- package/agents/recon-scout/HEARTBEAT.md +79 -0
- package/agents/recon-scout/IDENTITY.md +1 -0
- package/agents/recon-scout/SKILLS.md +1 -0
- package/agents/recon-scout/SOUL.md +23 -0
- package/agents/recon-scout/TOOLS.md +93 -0
- package/agents/report-writer/CONFIG.yaml +21 -0
- package/agents/report-writer/HEARTBEAT.md +63 -0
- package/agents/report-writer/IDENTITY.md +1 -0
- package/agents/report-writer/SKILLS.md +1 -0
- package/agents/report-writer/SOUL.md +23 -0
- package/agents/report-writer/TOOLS.md +69 -0
- package/agents/shared/README.md +13 -0
- package/agents/web-hacker/CONFIG.yaml +24 -0
- package/agents/web-hacker/HEARTBEAT.md +78 -0
- package/agents/web-hacker/IDENTITY.md +1 -0
- package/agents/web-hacker/SKILLS.md +1 -0
- package/agents/web-hacker/SOUL.md +23 -0
- package/agents/web-hacker/TOOLS.md +86 -0
- package/api/CLAUDE.md +19 -0
- package/api/index.js +274 -0
- package/bin/cli.js +620 -0
- package/bin/local.sh +31 -0
- package/bin/postinstall.js +63 -0
- package/config/index.js +24 -0
- package/config/instrumentation.js +93 -0
- package/drizzle/0000_initial.sql +52 -0
- package/drizzle/0001_bounty_and_registry.sql +82 -0
- package/drizzle/0002_sync_columns.sql +7 -0
- package/drizzle/0003_graceful_bloodscream.sql +86 -0
- package/drizzle/meta/0000_snapshot.json +321 -0
- package/drizzle/meta/0003_snapshot.json +878 -0
- package/drizzle/meta/_journal.json +34 -0
- package/drizzle/relations.ts +3 -0
- package/drizzle/schema.ts +145 -0
- package/lib/actions.js +47 -0
- package/lib/agents.js +166 -0
- package/lib/ai/agent.js +96 -0
- package/lib/ai/autonomous-engine.js +261 -0
- package/lib/ai/index.js +359 -0
- package/lib/ai/model-router.js +254 -0
- package/lib/ai/model.js +73 -0
- package/lib/ai/tools.js +84 -0
- package/lib/auth/actions.js +28 -0
- package/lib/auth/config.js +27 -0
- package/lib/auth/edge-config.js +27 -0
- package/lib/auth/index.js +27 -0
- package/lib/auth/middleware.js +53 -0
- package/lib/bounty/actions.js +119 -0
- package/lib/bounty/findings.js +64 -0
- package/lib/bounty/programs.js +34 -0
- package/lib/bounty/sync-targets.js +267 -0
- package/lib/bounty/targets.js +33 -0
- package/lib/channels/base.js +56 -0
- package/lib/channels/index.js +15 -0
- package/lib/channels/telegram.js +148 -0
- package/lib/chat/actions.js +288 -0
- package/lib/chat/api.js +135 -0
- package/lib/chat/components/app-sidebar.js +237 -0
- package/lib/chat/components/app-sidebar.jsx +289 -0
- package/lib/chat/components/chat-header.js +27 -0
- package/lib/chat/components/chat-header.jsx +37 -0
- package/lib/chat/components/chat-input.js +230 -0
- package/lib/chat/components/chat-input.jsx +228 -0
- package/lib/chat/components/chat-nav-context.js +11 -0
- package/lib/chat/components/chat-nav-context.jsx +11 -0
- package/lib/chat/components/chat-page.js +81 -0
- package/lib/chat/components/chat-page.jsx +100 -0
- package/lib/chat/components/chat.js +150 -0
- package/lib/chat/components/chat.jsx +182 -0
- package/lib/chat/components/chats-page.js +302 -0
- package/lib/chat/components/chats-page.jsx +330 -0
- package/lib/chat/components/crons-page.js +172 -0
- package/lib/chat/components/crons-page.jsx +244 -0
- package/lib/chat/components/enhanced-tool-call.js +103 -0
- package/lib/chat/components/enhanced-tool-call.jsx +139 -0
- package/lib/chat/components/findings-page.js +175 -0
- package/lib/chat/components/findings-page.jsx +214 -0
- package/lib/chat/components/greeting.js +22 -0
- package/lib/chat/components/greeting.jsx +26 -0
- package/lib/chat/components/icons.js +777 -0
- package/lib/chat/components/icons.jsx +741 -0
- package/lib/chat/components/index.js +26 -0
- package/lib/chat/components/mcp-page.js +260 -0
- package/lib/chat/components/mcp-page.jsx +355 -0
- package/lib/chat/components/message.js +289 -0
- package/lib/chat/components/message.jsx +315 -0
- package/lib/chat/components/messages.js +66 -0
- package/lib/chat/components/messages.jsx +77 -0
- package/lib/chat/components/notifications-page.js +56 -0
- package/lib/chat/components/notifications-page.jsx +87 -0
- package/lib/chat/components/page-layout.js +21 -0
- package/lib/chat/components/page-layout.jsx +28 -0
- package/lib/chat/components/registry-page.js +222 -0
- package/lib/chat/components/registry-page.jsx +255 -0
- package/lib/chat/components/settings-layout.js +40 -0
- package/lib/chat/components/settings-layout.jsx +54 -0
- package/lib/chat/components/settings-secrets-page.js +216 -0
- package/lib/chat/components/settings-secrets-page.jsx +264 -0
- package/lib/chat/components/sidebar-history-item.js +132 -0
- package/lib/chat/components/sidebar-history-item.jsx +113 -0
- package/lib/chat/components/sidebar-history.js +115 -0
- package/lib/chat/components/sidebar-history.jsx +157 -0
- package/lib/chat/components/sidebar-user-nav.js +63 -0
- package/lib/chat/components/sidebar-user-nav.jsx +73 -0
- package/lib/chat/components/status-bar.js +39 -0
- package/lib/chat/components/status-bar.jsx +51 -0
- package/lib/chat/components/swarm-page.js +157 -0
- package/lib/chat/components/swarm-page.jsx +210 -0
- package/lib/chat/components/targets-page.js +376 -0
- package/lib/chat/components/targets-page.jsx +389 -0
- package/lib/chat/components/tool-call.js +86 -0
- package/lib/chat/components/tool-call.jsx +104 -0
- package/lib/chat/components/tool-panel.js +107 -0
- package/lib/chat/components/tool-panel.jsx +145 -0
- package/lib/chat/components/triggers-page.js +153 -0
- package/lib/chat/components/triggers-page.jsx +221 -0
- package/lib/chat/components/ui/confirm-dialog.js +53 -0
- package/lib/chat/components/ui/confirm-dialog.jsx +57 -0
- package/lib/chat/components/ui/dropdown-menu.js +98 -0
- package/lib/chat/components/ui/dropdown-menu.jsx +116 -0
- package/lib/chat/components/ui/rename-dialog.js +74 -0
- package/lib/chat/components/ui/rename-dialog.jsx +72 -0
- package/lib/chat/components/ui/scroll-area.js +13 -0
- package/lib/chat/components/ui/scroll-area.jsx +17 -0
- package/lib/chat/components/ui/separator.js +21 -0
- package/lib/chat/components/ui/separator.jsx +18 -0
- package/lib/chat/components/ui/sheet.js +75 -0
- package/lib/chat/components/ui/sheet.jsx +95 -0
- package/lib/chat/components/ui/sidebar.js +227 -0
- package/lib/chat/components/ui/sidebar.jsx +245 -0
- package/lib/chat/components/ui/tooltip.js +56 -0
- package/lib/chat/components/ui/tooltip.jsx +66 -0
- package/lib/chat/components/upgrade-dialog.js +151 -0
- package/lib/chat/components/upgrade-dialog.jsx +170 -0
- package/lib/chat/utils.js +11 -0
- package/lib/cron.js +246 -0
- package/lib/db/api-keys.js +163 -0
- package/lib/db/chats.js +145 -0
- package/lib/db/index.js +52 -0
- package/lib/db/notifications.js +99 -0
- package/lib/db/schema.js +145 -0
- package/lib/db/update-check.js +96 -0
- package/lib/db/users.js +89 -0
- package/lib/mcp/actions.js +104 -0
- package/lib/mcp/client.js +79 -0
- package/lib/mcp/handler.js +57 -0
- package/lib/mcp/server.js +165 -0
- package/lib/paths.js +46 -0
- package/lib/registry/actions.js +164 -0
- package/lib/registry/catalog.js +137 -0
- package/lib/registry/tools.js +71 -0
- package/lib/tools/create-job.js +99 -0
- package/lib/tools/github.js +217 -0
- package/lib/tools/openai.js +35 -0
- package/lib/tools/telegram.js +292 -0
- package/lib/triggers.js +118 -0
- package/lib/utils/render-md.js +102 -0
- package/package.json +103 -0
- package/setup/lib/auth.mjs +81 -0
- package/setup/lib/env.mjs +21 -0
- package/setup/lib/fs-utils.mjs +20 -0
- package/setup/lib/github.mjs +149 -0
- package/setup/lib/prerequisites.mjs +155 -0
- package/setup/lib/prompts.mjs +267 -0
- package/setup/lib/providers.mjs +48 -0
- package/setup/lib/sync.mjs +125 -0
- package/setup/lib/targets.mjs +45 -0
- package/setup/lib/telegram-verify.mjs +63 -0
- package/setup/lib/telegram.mjs +76 -0
- package/setup/setup-telegram.mjs +264 -0
- package/setup/setup.mjs +842 -0
- package/templates/.dockerignore +5 -0
- package/templates/.env.example +63 -0
- package/templates/.github/workflows/auto-merge.yml +117 -0
- package/templates/.github/workflows/build-image.yml +36 -0
- package/templates/.github/workflows/notify-job-failed.yml +64 -0
- package/templates/.github/workflows/notify-pr-complete.yml +119 -0
- package/templates/.github/workflows/rebuild-event-handler.yml +121 -0
- package/templates/.github/workflows/run-job.yml +89 -0
- package/templates/.github/workflows/upgrade-event-handler.yml +62 -0
- package/templates/.gitignore.template +45 -0
- package/templates/.pi/extensions/env-sanitizer/index.ts +48 -0
- package/templates/.pi/extensions/env-sanitizer/package.json +5 -0
- package/templates/CLAUDE.md +29 -0
- package/templates/CLAUDE.md.template +307 -0
- package/templates/app/api/[...thepopebot]/route.js +1 -0
- package/templates/app/api/auth/[...nextauth]/route.js +1 -0
- package/templates/app/chat/[chatId]/page.js +8 -0
- package/templates/app/chats/page.js +7 -0
- package/templates/app/components/ascii-logo.jsx +10 -0
- package/templates/app/components/login-form.jsx +92 -0
- package/templates/app/components/setup-form.jsx +82 -0
- package/templates/app/components/theme-provider.jsx +11 -0
- package/templates/app/components/theme-toggle.jsx +38 -0
- package/templates/app/components/ui/button.jsx +21 -0
- package/templates/app/components/ui/card.jsx +23 -0
- package/templates/app/components/ui/input.jsx +10 -0
- package/templates/app/components/ui/label.jsx +10 -0
- package/templates/app/crons/page.js +5 -0
- package/templates/app/findings/page.js +7 -0
- package/templates/app/globals.css +90 -0
- package/templates/app/layout.js +19 -0
- package/templates/app/login/page.js +15 -0
- package/templates/app/notifications/page.js +7 -0
- package/templates/app/page.js +7 -0
- package/templates/app/settings/crons/page.js +5 -0
- package/templates/app/settings/layout.js +7 -0
- package/templates/app/settings/mcp/page.js +5 -0
- package/templates/app/settings/page.js +5 -0
- package/templates/app/settings/secrets/page.js +5 -0
- package/templates/app/settings/triggers/page.js +5 -0
- package/templates/app/stream/chat/route.js +1 -0
- package/templates/app/swarm/page.js +7 -0
- package/templates/app/targets/page.js +7 -0
- package/templates/app/toolbox/page.js +7 -0
- package/templates/app/triggers/page.js +5 -0
- package/templates/config/AGENT.md +34 -0
- package/templates/config/CRONS.json +56 -0
- package/templates/config/EVENT_HANDLER.md +224 -0
- package/templates/config/HEARTBEAT.md +3 -0
- package/templates/config/JOB_SUMMARY.md +130 -0
- package/templates/config/MCP_SERVERS.json +1 -0
- package/templates/config/SKILL_BUILDING_GUIDE.md +90 -0
- package/templates/config/SOUL.md +17 -0
- package/templates/config/TRIGGERS.json +58 -0
- package/templates/docker/event-handler/Dockerfile +20 -0
- package/templates/docker/event-handler/ecosystem.config.cjs +8 -0
- package/templates/docker/job-claude-code/Dockerfile +34 -0
- package/templates/docker/job-claude-code/entrypoint.sh +139 -0
- package/templates/docker/job-pi-coding-agent/Dockerfile +44 -0
- package/templates/docker/job-pi-coding-agent/entrypoint.sh +163 -0
- package/templates/docker-compose.yml +63 -0
- package/templates/instrumentation.js +6 -0
- package/templates/middleware.js +1 -0
- package/templates/next.config.mjs +3 -0
- package/templates/postcss.config.mjs +5 -0
- package/templates/skills/LICENSE +21 -0
- package/templates/skills/README.md +119 -0
- package/templates/skills/brave-search/SKILL.md +79 -0
- package/templates/skills/brave-search/content.js +86 -0
- package/templates/skills/brave-search/package-lock.json +621 -0
- package/templates/skills/brave-search/package.json +14 -0
- package/templates/skills/brave-search/search.js +199 -0
- package/templates/skills/browser-tools/SKILL.md +196 -0
- package/templates/skills/browser-tools/browser-content.js +103 -0
- package/templates/skills/browser-tools/browser-cookies.js +35 -0
- package/templates/skills/browser-tools/browser-eval.js +53 -0
- package/templates/skills/browser-tools/browser-hn-scraper.js +108 -0
- package/templates/skills/browser-tools/browser-nav.js +44 -0
- package/templates/skills/browser-tools/browser-pick.js +162 -0
- package/templates/skills/browser-tools/browser-screenshot.js +34 -0
- package/templates/skills/browser-tools/browser-start.js +87 -0
- package/templates/skills/browser-tools/package-lock.json +2556 -0
- package/templates/skills/browser-tools/package.json +19 -0
- package/templates/skills/llm-secrets/SKILL.md +34 -0
- package/templates/skills/llm-secrets/llm-secrets.js +33 -0
- package/templates/skills/modify-self/SKILL.md +12 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
# Extract job ID from branch name (job/uuid -> uuid), fallback to random UUID
|
|
5
|
+
if [[ "$BRANCH" == job/* ]]; then
|
|
6
|
+
JOB_ID="${BRANCH#job/}"
|
|
7
|
+
else
|
|
8
|
+
JOB_ID=$(cat /proc/sys/kernel/random/uuid)
|
|
9
|
+
fi
|
|
10
|
+
echo "Job ID: ${JOB_ID}"
|
|
11
|
+
|
|
12
|
+
# Export SECRETS (JSON) as flat env vars (GH_TOKEN, ANTHROPIC_API_KEY, etc.)
|
|
13
|
+
# These are filtered from LLM's bash subprocess by env-sanitizer extension
|
|
14
|
+
if [ -n "$SECRETS" ]; then
|
|
15
|
+
eval $(echo "$SECRETS" | jq -r 'to_entries | .[] | "export \(.key)=\(.value | @sh)"')
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
# Export LLM_SECRETS (JSON) as flat env vars
|
|
19
|
+
# These are NOT filtered - LLM can access these (browser logins, skill API keys, etc.)
|
|
20
|
+
if [ -n "$LLM_SECRETS" ]; then
|
|
21
|
+
eval $(echo "$LLM_SECRETS" | jq -r 'to_entries | .[] | "export \(.key)=\(.value | @sh)"')
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# Git setup - derive identity from GitHub token
|
|
25
|
+
gh auth setup-git
|
|
26
|
+
GH_USER_JSON=$(gh api user -q '{name: .name, login: .login, email: .email, id: .id}')
|
|
27
|
+
GH_USER_NAME=$(echo "$GH_USER_JSON" | jq -r '.name // .login')
|
|
28
|
+
GH_USER_EMAIL=$(echo "$GH_USER_JSON" | jq -r '.email // "\(.id)+\(.login)@users.noreply.github.com"')
|
|
29
|
+
git config --global user.name "$GH_USER_NAME"
|
|
30
|
+
git config --global user.email "$GH_USER_EMAIL"
|
|
31
|
+
|
|
32
|
+
# Clone branch
|
|
33
|
+
if [ -n "$REPO_URL" ]; then
|
|
34
|
+
git clone --single-branch --branch "$BRANCH" --depth 1 "$REPO_URL" /job
|
|
35
|
+
else
|
|
36
|
+
echo "No REPO_URL provided"
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
cd /job
|
|
40
|
+
|
|
41
|
+
# Create temp directory for agent use (gitignored via tmp/)
|
|
42
|
+
mkdir -p /job/tmp
|
|
43
|
+
|
|
44
|
+
# Install npm deps for active skills (native deps need correct Linux arch)
|
|
45
|
+
for skill_dir in /job/skills/active/*/; do
|
|
46
|
+
if [ -f "${skill_dir}package.json" ]; then
|
|
47
|
+
echo "Installing skill deps: $(basename "$skill_dir")"
|
|
48
|
+
(cd "$skill_dir" && npm install --omit=dev --no-package-lock)
|
|
49
|
+
fi
|
|
50
|
+
done
|
|
51
|
+
|
|
52
|
+
# Start Chrome if available (installed by browser-tools skill via Puppeteer)
|
|
53
|
+
CHROME_PID=""
|
|
54
|
+
CHROME_BIN=$(find /root/.cache/puppeteer -name "chrome" -type f 2>/dev/null | head -1)
|
|
55
|
+
if [ -n "$CHROME_BIN" ]; then
|
|
56
|
+
$CHROME_BIN --headless --no-sandbox --disable-gpu --remote-debugging-port=9222 2>/dev/null &
|
|
57
|
+
CHROME_PID=$!
|
|
58
|
+
sleep 2
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# Setup logs
|
|
62
|
+
LOG_DIR="/job/logs/${JOB_ID}"
|
|
63
|
+
mkdir -p "${LOG_DIR}"
|
|
64
|
+
|
|
65
|
+
# 1. Build system prompt from config MD files
|
|
66
|
+
SYSTEM_FILES=("SOUL.md" "AGENT.md")
|
|
67
|
+
> /job/.pi/SYSTEM.md
|
|
68
|
+
for i in "${!SYSTEM_FILES[@]}"; do
|
|
69
|
+
cat "/job/config/${SYSTEM_FILES[$i]}" >> /job/.pi/SYSTEM.md
|
|
70
|
+
if [ "$i" -lt $((${#SYSTEM_FILES[@]} - 1)) ]; then
|
|
71
|
+
echo -e "\n\n" >> /job/.pi/SYSTEM.md
|
|
72
|
+
fi
|
|
73
|
+
done
|
|
74
|
+
|
|
75
|
+
# Resolve {{datetime}} variable in SYSTEM.md
|
|
76
|
+
sed -i "s/{{datetime}}/$(date -u +"%Y-%m-%dT%H:%M:%SZ")/g" /job/.pi/SYSTEM.md
|
|
77
|
+
|
|
78
|
+
# Read job metadata from job.config.json
|
|
79
|
+
JOB_CONFIG="/job/logs/${JOB_ID}/job.config.json"
|
|
80
|
+
TITLE=$(jq -r '.title // empty' "$JOB_CONFIG")
|
|
81
|
+
JOB_DESCRIPTION=$(jq -r '.job // empty' "$JOB_CONFIG")
|
|
82
|
+
|
|
83
|
+
PROMPT="
|
|
84
|
+
|
|
85
|
+
# Your Job
|
|
86
|
+
|
|
87
|
+
${JOB_DESCRIPTION}"
|
|
88
|
+
|
|
89
|
+
LLM_PROVIDER="${LLM_PROVIDER:-anthropic}"
|
|
90
|
+
|
|
91
|
+
MODEL_FLAGS="--provider $LLM_PROVIDER"
|
|
92
|
+
if [ -n "$LLM_MODEL" ]; then
|
|
93
|
+
MODEL_FLAGS="$MODEL_FLAGS --model $LLM_MODEL"
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# Generate models.json for custom provider (OpenAI-compatible endpoints like Ollama)
|
|
97
|
+
if [ "$LLM_PROVIDER" = "custom" ] && [ -n "$OPENAI_BASE_URL" ]; then
|
|
98
|
+
# If no API key was provided, set a dummy so Pi doesn't send empty auth
|
|
99
|
+
if [ -z "$CUSTOM_API_KEY" ]; then
|
|
100
|
+
export CUSTOM_API_KEY="not-needed"
|
|
101
|
+
fi
|
|
102
|
+
cat > /root/.pi/agent/models.json <<MODELS
|
|
103
|
+
{
|
|
104
|
+
"providers": {
|
|
105
|
+
"custom": {
|
|
106
|
+
"baseUrl": "$OPENAI_BASE_URL",
|
|
107
|
+
"api": "openai-completions",
|
|
108
|
+
"apiKey": "CUSTOM_API_KEY",
|
|
109
|
+
"models": [{ "id": "$LLM_MODEL" }]
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
MODELS
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
# Copy custom models.json to PI's global config if present in repo (overrides generated)
|
|
117
|
+
if [ -f "/job/.pi/agent/models.json" ]; then
|
|
118
|
+
mkdir -p /root/.pi/agent
|
|
119
|
+
cp /job/.pi/agent/models.json /root/.pi/agent/models.json
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
# Run Pi — capture exit code instead of letting set -e kill the script
|
|
123
|
+
set +e
|
|
124
|
+
pi $MODEL_FLAGS -p "$PROMPT" --session-dir "${LOG_DIR}"
|
|
125
|
+
PI_EXIT=$?
|
|
126
|
+
|
|
127
|
+
# 2. Commit based on outcome
|
|
128
|
+
if [ $PI_EXIT -ne 0 ]; then
|
|
129
|
+
# Pi failed — only commit session logs, not partial code changes
|
|
130
|
+
git reset || true
|
|
131
|
+
git add -f "${LOG_DIR}"
|
|
132
|
+
git commit -m "🤖 Agent Job: ${TITLE} (failed)" || true
|
|
133
|
+
else
|
|
134
|
+
# Pi succeeded — commit everything
|
|
135
|
+
git add -A
|
|
136
|
+
git add -f "${LOG_DIR}"
|
|
137
|
+
git commit -m "🤖 Agent Job: ${TITLE}" || true
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
git push origin
|
|
141
|
+
set -e
|
|
142
|
+
|
|
143
|
+
# 3. Merge (pi has memory of job via session)
|
|
144
|
+
#if [ -n "$REPO_URL" ] && [ -f "/job/MERGE_JOB.md" ]; then
|
|
145
|
+
# echo "MERGED"
|
|
146
|
+
# pi -p "$(cat /job/MERGE_JOB.md)" --session-dir "${LOG_DIR}" --continue
|
|
147
|
+
#fi
|
|
148
|
+
|
|
149
|
+
# 5. Create PR (auto-merge handled by GitHub Actions workflow)
|
|
150
|
+
gh pr create --title "🤖 Agent Job: ${TITLE}" --body "${JOB_DESCRIPTION}" --base main || true
|
|
151
|
+
|
|
152
|
+
# Cleanup
|
|
153
|
+
if [ -n "$CHROME_PID" ]; then
|
|
154
|
+
kill $CHROME_PID 2>/dev/null || true
|
|
155
|
+
fi
|
|
156
|
+
|
|
157
|
+
# Re-raise Pi's failure so the workflow reports it
|
|
158
|
+
if [ $PI_EXIT -ne 0 ]; then
|
|
159
|
+
echo "Pi exited with code ${PI_EXIT}"
|
|
160
|
+
exit $PI_EXIT
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
echo "Done. Job ID: ${JOB_ID}"
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
services:
|
|
2
|
+
traefik:
|
|
3
|
+
image: traefik:v3
|
|
4
|
+
command:
|
|
5
|
+
- --providers.docker=true
|
|
6
|
+
- --providers.docker.exposedByDefault=false
|
|
7
|
+
- --entrypoints.web.address=:80
|
|
8
|
+
- --entrypoints.websecure.address=:443
|
|
9
|
+
## Uncomment the following lines to enable TLS via Let's Encrypt
|
|
10
|
+
## (requires LETSENCRYPT_EMAIL in .env):
|
|
11
|
+
# - --entrypoints.web.http.redirections.entrypoint.to=websecure
|
|
12
|
+
# - --entrypoints.web.http.redirections.entrypoint.scheme=https
|
|
13
|
+
# - --certificatesresolvers.letsencrypt.acme.email=${LETSENCRYPT_EMAIL}
|
|
14
|
+
# - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
|
|
15
|
+
# - --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web
|
|
16
|
+
ports:
|
|
17
|
+
- "80:80"
|
|
18
|
+
- "443:443"
|
|
19
|
+
volumes:
|
|
20
|
+
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
21
|
+
- traefik_certs:/letsencrypt
|
|
22
|
+
restart: unless-stopped
|
|
23
|
+
|
|
24
|
+
event-handler:
|
|
25
|
+
container_name: thepopebot-event-handler
|
|
26
|
+
image: ${EVENT_HANDLER_IMAGE_URL:-stephengpope/thepopebot:event-handler-${THEPOPEBOT_VERSION:-latest}}
|
|
27
|
+
volumes:
|
|
28
|
+
- .:/app
|
|
29
|
+
- /app/node_modules
|
|
30
|
+
labels:
|
|
31
|
+
- traefik.enable=true
|
|
32
|
+
# Set APP_HOSTNAME in .env to the domain from APP_URL (e.g., mybot.example.com)
|
|
33
|
+
- traefik.http.routers.event-handler.rule=Host(`${APP_HOSTNAME}`)
|
|
34
|
+
- traefik.http.routers.event-handler.entrypoints=web
|
|
35
|
+
- traefik.http.services.event-handler.loadbalancer.server.port=80
|
|
36
|
+
## Uncomment the following lines to enable TLS via Let's Encrypt:
|
|
37
|
+
# - traefik.http.routers.event-handler.entrypoints=websecure
|
|
38
|
+
# - traefik.http.routers.event-handler.tls.certresolver=letsencrypt
|
|
39
|
+
stop_grace_period: 120s
|
|
40
|
+
healthcheck:
|
|
41
|
+
test: ["CMD", "curl", "-f", "http://localhost:80/api/ping"]
|
|
42
|
+
interval: 10s
|
|
43
|
+
timeout: 3s
|
|
44
|
+
retries: 3
|
|
45
|
+
start_period: 30s
|
|
46
|
+
restart: unless-stopped
|
|
47
|
+
|
|
48
|
+
runner:
|
|
49
|
+
image: myoung34/github-runner:latest
|
|
50
|
+
deploy:
|
|
51
|
+
replicas: ${RUNNER_REPLICAS:-2}
|
|
52
|
+
environment:
|
|
53
|
+
REPO_URL: https://github.com/${GH_OWNER}/${GH_REPO}
|
|
54
|
+
ACCESS_TOKEN: ${GH_TOKEN}
|
|
55
|
+
RUNNER_SCOPE: repo
|
|
56
|
+
LABELS: self-hosted
|
|
57
|
+
volumes:
|
|
58
|
+
- /var/run/docker.sock:/var/run/docker.sock
|
|
59
|
+
- .:/project:ro
|
|
60
|
+
restart: unless-stopped
|
|
61
|
+
|
|
62
|
+
volumes:
|
|
63
|
+
traefik_certs:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { middleware, config } from 'thepopebot/middleware';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Mario Zechner
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# pi-skills
|
|
2
|
+
|
|
3
|
+
A collection of skills for [pi-coding-agent](https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent), compatible with Claude Code, Codex CLI, Amp, and Droid.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### pi-coding-agent
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# User-level (available in all projects)
|
|
11
|
+
git clone https://github.com/badlogic/pi-skills ~/.pi/agent/skills/pi-skills
|
|
12
|
+
|
|
13
|
+
# Or project-level
|
|
14
|
+
git clone https://github.com/badlogic/pi-skills .pi/skills/pi-skills
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Codex CLI
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
git clone https://github.com/badlogic/pi-skills ~/.codex/skills/pi-skills
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Amp
|
|
24
|
+
|
|
25
|
+
Amp finds skills recursively in toolboxes:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
git clone https://github.com/badlogic/pi-skills ~/.config/amp/tools/pi-skills
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Droid (Factory)
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# User-level
|
|
35
|
+
git clone https://github.com/badlogic/pi-skills ~/.factory/skills/pi-skills
|
|
36
|
+
|
|
37
|
+
# Or project-level
|
|
38
|
+
git clone https://github.com/badlogic/pi-skills .factory/skills/pi-skills
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Claude Code
|
|
42
|
+
|
|
43
|
+
Claude Code only looks one level deep for `SKILL.md` files, so each skill folder must be directly under the skills directory. Clone the repo somewhere, then symlink individual skills:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Clone to a convenient location
|
|
47
|
+
git clone https://github.com/badlogic/pi-skills ~/pi-skills
|
|
48
|
+
|
|
49
|
+
# Symlink individual skills (user-level)
|
|
50
|
+
mkdir -p ~/.claude/skills
|
|
51
|
+
ln -s ~/pi-skills/brave-search ~/.claude/skills/brave-search
|
|
52
|
+
ln -s ~/pi-skills/browser-tools ~/.claude/skills/browser-tools
|
|
53
|
+
ln -s ~/pi-skills/gccli ~/.claude/skills/gccli
|
|
54
|
+
ln -s ~/pi-skills/gdcli ~/.claude/skills/gdcli
|
|
55
|
+
ln -s ~/pi-skills/gmcli ~/.claude/skills/gmcli
|
|
56
|
+
ln -s ~/pi-skills/transcribe ~/.claude/skills/transcribe
|
|
57
|
+
ln -s ~/pi-skills/vscode ~/.claude/skills/vscode
|
|
58
|
+
ln -s ~/pi-skills/youtube-transcript ~/.claude/skills/youtube-transcript
|
|
59
|
+
|
|
60
|
+
# Or project-level
|
|
61
|
+
mkdir -p .claude/skills
|
|
62
|
+
ln -s ~/pi-skills/brave-search .claude/skills/brave-search
|
|
63
|
+
ln -s ~/pi-skills/browser-tools .claude/skills/browser-tools
|
|
64
|
+
ln -s ~/pi-skills/gccli .claude/skills/gccli
|
|
65
|
+
ln -s ~/pi-skills/gdcli .claude/skills/gdcli
|
|
66
|
+
ln -s ~/pi-skills/gmcli .claude/skills/gmcli
|
|
67
|
+
ln -s ~/pi-skills/transcribe .claude/skills/transcribe
|
|
68
|
+
ln -s ~/pi-skills/vscode .claude/skills/vscode
|
|
69
|
+
ln -s ~/pi-skills/youtube-transcript .claude/skills/youtube-transcript
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Available Skills
|
|
73
|
+
|
|
74
|
+
| Skill | Description |
|
|
75
|
+
|-------|-------------|
|
|
76
|
+
| [brave-search](brave-search/SKILL.md) | Web search and content extraction via Brave Search |
|
|
77
|
+
| [browser-tools](browser-tools/SKILL.md) | Interactive browser automation via Chrome DevTools Protocol |
|
|
78
|
+
| [gccli](gccli/SKILL.md) | Google Calendar CLI for events and availability |
|
|
79
|
+
| [gdcli](gdcli/SKILL.md) | Google Drive CLI for file management and sharing |
|
|
80
|
+
| [gmcli](gmcli/SKILL.md) | Gmail CLI for email, drafts, and labels |
|
|
81
|
+
| [transcribe](transcribe/SKILL.md) | Speech-to-text transcription via Groq Whisper API |
|
|
82
|
+
| [vscode](vscode/SKILL.md) | VS Code integration for diffs and file comparison |
|
|
83
|
+
| [youtube-transcript](youtube-transcript/SKILL.md) | Fetch YouTube video transcripts |
|
|
84
|
+
|
|
85
|
+
## Skill Format
|
|
86
|
+
|
|
87
|
+
Each skill follows the pi/Claude Code format:
|
|
88
|
+
|
|
89
|
+
```markdown
|
|
90
|
+
---
|
|
91
|
+
name: skill-name
|
|
92
|
+
description: Short description shown to agent
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
# Instructions
|
|
96
|
+
|
|
97
|
+
Detailed instructions here...
|
|
98
|
+
Helper files available at: skills/skill-name/
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Skills use project-root-relative paths (e.g., `skills/brave-search/search.js`).
|
|
102
|
+
|
|
103
|
+
## Requirements
|
|
104
|
+
|
|
105
|
+
Some skills require additional setup. Generally, the agent will walk you through that. But if not, here you go:
|
|
106
|
+
|
|
107
|
+
- **brave-search**: Requires Node.js. Run `npm install` in the skill directory.
|
|
108
|
+
- **browser-tools**: Requires Chrome and Node.js. Run `npm install` in the skill directory.
|
|
109
|
+
- **gccli**: Requires Node.js. Install globally with `npm install -g @mariozechner/gccli`.
|
|
110
|
+
- **gdcli**: Requires Node.js. Install globally with `npm install -g @mariozechner/gdcli`.
|
|
111
|
+
- **gmcli**: Requires Node.js. Install globally with `npm install -g @mariozechner/gmcli`.
|
|
112
|
+
- **subagent**: Requires pi-coding-agent. Install globally with `npm install -g @mariozechner/pi-coding-agent`.
|
|
113
|
+
- **transcribe**: Requires curl and a Groq API key.
|
|
114
|
+
- **vscode**: Requires VS Code with `code` CLI in PATH.
|
|
115
|
+
- **youtube-transcript**: Requires Node.js. Run `npm install` in the skill directory.
|
|
116
|
+
|
|
117
|
+
## License
|
|
118
|
+
|
|
119
|
+
MIT
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: brave-search
|
|
3
|
+
description: Web search and content extraction via Brave Search API. Use for searching documentation, facts, or any web content. Lightweight, no browser required.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Brave Search
|
|
7
|
+
|
|
8
|
+
Web search and content extraction using the official Brave Search API. No browser required.
|
|
9
|
+
|
|
10
|
+
## Setup
|
|
11
|
+
|
|
12
|
+
Requires a Brave Search API account with a free subscription. A credit card is required to create the free subscription (you won't be charged).
|
|
13
|
+
|
|
14
|
+
1. Create an account at https://api-dashboard.search.brave.com/register
|
|
15
|
+
2. Create a "Free AI" subscription
|
|
16
|
+
3. Create an API key for the subscription
|
|
17
|
+
4. Add to your shell profile (`~/.profile` or `~/.zprofile` for zsh):
|
|
18
|
+
```bash
|
|
19
|
+
export BRAVE_API_KEY="your-api-key-here"
|
|
20
|
+
```
|
|
21
|
+
5. Install dependencies (run once):
|
|
22
|
+
```bash
|
|
23
|
+
cd skills/brave-search
|
|
24
|
+
npm install
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Search
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
skills/brave-search/search.js "query" # Basic search (5 results)
|
|
31
|
+
skills/brave-search/search.js "query" -n 10 # More results (max 20)
|
|
32
|
+
skills/brave-search/search.js "query" --content # Include page content as markdown
|
|
33
|
+
skills/brave-search/search.js "query" --freshness pw # Results from last week
|
|
34
|
+
skills/brave-search/search.js "query" --freshness 2024-01-01to2024-06-30 # Date range
|
|
35
|
+
skills/brave-search/search.js "query" --country DE # Results from Germany
|
|
36
|
+
skills/brave-search/search.js "query" -n 3 --content # Combined options
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Options
|
|
40
|
+
|
|
41
|
+
- `-n <num>` - Number of results (default: 5, max: 20)
|
|
42
|
+
- `--content` - Fetch and include page content as markdown
|
|
43
|
+
- `--country <code>` - Two-letter country code (default: US)
|
|
44
|
+
- `--freshness <period>` - Filter by time:
|
|
45
|
+
- `pd` - Past day (24 hours)
|
|
46
|
+
- `pw` - Past week
|
|
47
|
+
- `pm` - Past month
|
|
48
|
+
- `py` - Past year
|
|
49
|
+
- `YYYY-MM-DDtoYYYY-MM-DD` - Custom date range
|
|
50
|
+
|
|
51
|
+
## Extract Page Content
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
skills/brave-search/content.js https://example.com/article
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Fetches a URL and extracts readable content as markdown.
|
|
58
|
+
|
|
59
|
+
## Output Format
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
--- Result 1 ---
|
|
63
|
+
Title: Page Title
|
|
64
|
+
Link: https://example.com/page
|
|
65
|
+
Age: 2 days ago
|
|
66
|
+
Snippet: Description from search results
|
|
67
|
+
Content: (if --content flag used)
|
|
68
|
+
Markdown content extracted from the page...
|
|
69
|
+
|
|
70
|
+
--- Result 2 ---
|
|
71
|
+
...
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## When to Use
|
|
75
|
+
|
|
76
|
+
- Searching for documentation or API references
|
|
77
|
+
- Looking up facts or current information
|
|
78
|
+
- Fetching content from specific URLs
|
|
79
|
+
- Any task requiring web search without interactive browsing
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Readability } from "@mozilla/readability";
|
|
4
|
+
import { JSDOM } from "jsdom";
|
|
5
|
+
import TurndownService from "turndown";
|
|
6
|
+
import { gfm } from "turndown-plugin-gfm";
|
|
7
|
+
|
|
8
|
+
const url = process.argv[2];
|
|
9
|
+
|
|
10
|
+
if (!url) {
|
|
11
|
+
console.log("Usage: content.js <url>");
|
|
12
|
+
console.log("\nExtracts readable content from a webpage as markdown.");
|
|
13
|
+
console.log("\nExamples:");
|
|
14
|
+
console.log(" content.js https://example.com/article");
|
|
15
|
+
console.log(" content.js https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html");
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function htmlToMarkdown(html) {
|
|
20
|
+
const turndown = new TurndownService({ headingStyle: "atx", codeBlockStyle: "fenced" });
|
|
21
|
+
turndown.use(gfm);
|
|
22
|
+
turndown.addRule("removeEmptyLinks", {
|
|
23
|
+
filter: (node) => node.nodeName === "A" && !node.textContent?.trim(),
|
|
24
|
+
replacement: () => "",
|
|
25
|
+
});
|
|
26
|
+
return turndown
|
|
27
|
+
.turndown(html)
|
|
28
|
+
.replace(/\[\\?\[\s*\\?\]\]\([^)]*\)/g, "")
|
|
29
|
+
.replace(/ +/g, " ")
|
|
30
|
+
.replace(/\s+,/g, ",")
|
|
31
|
+
.replace(/\s+\./g, ".")
|
|
32
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
33
|
+
.trim();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const response = await fetch(url, {
|
|
38
|
+
headers: {
|
|
39
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
40
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
41
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
42
|
+
},
|
|
43
|
+
signal: AbortSignal.timeout(15000),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
console.error(`HTTP ${response.status}: ${response.statusText}`);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const html = await response.text();
|
|
52
|
+
const dom = new JSDOM(html, { url });
|
|
53
|
+
const reader = new Readability(dom.window.document);
|
|
54
|
+
const article = reader.parse();
|
|
55
|
+
|
|
56
|
+
if (article && article.content) {
|
|
57
|
+
if (article.title) {
|
|
58
|
+
console.log(`# ${article.title}\n`);
|
|
59
|
+
}
|
|
60
|
+
console.log(htmlToMarkdown(article.content));
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Fallback: try to extract main content
|
|
65
|
+
const fallbackDoc = new JSDOM(html, { url });
|
|
66
|
+
const body = fallbackDoc.window.document;
|
|
67
|
+
body.querySelectorAll("script, style, noscript, nav, header, footer, aside").forEach(el => el.remove());
|
|
68
|
+
|
|
69
|
+
const title = body.querySelector("title")?.textContent?.trim();
|
|
70
|
+
const main = body.querySelector("main, article, [role='main'], .content, #content") || body.body;
|
|
71
|
+
|
|
72
|
+
if (title) {
|
|
73
|
+
console.log(`# ${title}\n`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const text = main?.innerHTML || "";
|
|
77
|
+
if (text.trim().length > 100) {
|
|
78
|
+
console.log(htmlToMarkdown(text));
|
|
79
|
+
} else {
|
|
80
|
+
console.error("Could not extract readable content from this page.");
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
} catch (e) {
|
|
84
|
+
console.error(`Error: ${e.message}`);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|