@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,267 @@
|
|
|
1
|
+
import * as clack from '@clack/prompts';
|
|
2
|
+
import open from 'open';
|
|
3
|
+
import { PROVIDERS } from './providers.mjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Mask a secret, showing only last 4 characters
|
|
7
|
+
*/
|
|
8
|
+
export function maskSecret(secret) {
|
|
9
|
+
if (!secret || secret.length < 8) return '****';
|
|
10
|
+
return '****' + secret.slice(-4);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Handle cancel — exits cleanly if user pressed Ctrl+C
|
|
15
|
+
*/
|
|
16
|
+
function handleCancel(value) {
|
|
17
|
+
if (clack.isCancel(value)) {
|
|
18
|
+
clack.cancel('Setup cancelled.');
|
|
19
|
+
process.exit(0);
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Boolean gate — returns true (keep existing) or false (must configure).
|
|
26
|
+
* If no displayValue, returns false (must configure).
|
|
27
|
+
*/
|
|
28
|
+
export async function keepOrReconfigure(label, displayValue) {
|
|
29
|
+
if (!displayValue) return false;
|
|
30
|
+
clack.log.success(`${label}: ${displayValue}`);
|
|
31
|
+
const reconfig = handleCancel(await clack.confirm({
|
|
32
|
+
message: 'Reconfigure?',
|
|
33
|
+
initialValue: false,
|
|
34
|
+
}));
|
|
35
|
+
return !reconfig;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Prompt for GitHub PAT
|
|
40
|
+
*/
|
|
41
|
+
export async function promptForPAT() {
|
|
42
|
+
const pat = handleCancel(await clack.password({
|
|
43
|
+
message: 'Paste your GitHub Personal Access Token:',
|
|
44
|
+
validate: (input) => {
|
|
45
|
+
if (!input) return 'PAT is required';
|
|
46
|
+
if (!input.startsWith('ghp_') && !input.startsWith('github_pat_')) {
|
|
47
|
+
return 'Invalid PAT format. Should start with ghp_ or github_pat_';
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
}));
|
|
51
|
+
return pat;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Prompt for LLM provider selection
|
|
56
|
+
*/
|
|
57
|
+
export async function promptForProvider() {
|
|
58
|
+
const options = Object.entries(PROVIDERS).map(([key, p]) => ({
|
|
59
|
+
label: p.label,
|
|
60
|
+
value: key,
|
|
61
|
+
}));
|
|
62
|
+
options.push({ label: 'Local (OpenAI Compatible API)', value: 'custom' });
|
|
63
|
+
|
|
64
|
+
const provider = handleCancel(await clack.select({
|
|
65
|
+
message: 'Which LLM for your agent?',
|
|
66
|
+
options,
|
|
67
|
+
}));
|
|
68
|
+
return provider;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Prompt for model selection from a provider's model list
|
|
73
|
+
* @param {string} providerKey - Provider key from PROVIDERS registry
|
|
74
|
+
* @param {object} [options] - Options
|
|
75
|
+
* @param {string} [options.defaultModelId] - Override which model gets "(recommended)" instead of the registry default
|
|
76
|
+
*/
|
|
77
|
+
export async function promptForModel(providerKey, { defaultModelId } = {}) {
|
|
78
|
+
const provider = PROVIDERS[providerKey];
|
|
79
|
+
const isRecommended = (m) => defaultModelId ? m.id === defaultModelId : m.default;
|
|
80
|
+
const sorted = [...provider.models].sort((a, b) => isRecommended(b) - isRecommended(a));
|
|
81
|
+
const options = sorted.map((m) => ({
|
|
82
|
+
label: isRecommended(m) ? `${m.name} (recommended)` : m.name,
|
|
83
|
+
value: m.id,
|
|
84
|
+
}));
|
|
85
|
+
options.push({ label: 'Custom (enter model ID)', value: '__custom__' });
|
|
86
|
+
|
|
87
|
+
const model = handleCancel(await clack.select({
|
|
88
|
+
message: 'Which model?',
|
|
89
|
+
options,
|
|
90
|
+
}));
|
|
91
|
+
|
|
92
|
+
if (model === '__custom__') {
|
|
93
|
+
const customModel = handleCancel(await clack.text({
|
|
94
|
+
message: `Enter ${provider.name} model ID:`,
|
|
95
|
+
validate: (input) => {
|
|
96
|
+
if (!input) return 'Model ID is required';
|
|
97
|
+
},
|
|
98
|
+
}));
|
|
99
|
+
return customModel;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return model;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Prompt for an API key for a known provider
|
|
107
|
+
*/
|
|
108
|
+
export async function promptForApiKey(providerKey) {
|
|
109
|
+
const provider = PROVIDERS[providerKey];
|
|
110
|
+
|
|
111
|
+
const openPage = handleCancel(await clack.confirm({
|
|
112
|
+
message: `Open ${provider.name} API key page in browser?`,
|
|
113
|
+
initialValue: true,
|
|
114
|
+
}));
|
|
115
|
+
if (openPage) {
|
|
116
|
+
await open(provider.keyPage);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const key = handleCancel(await clack.password({
|
|
120
|
+
message: `Enter your ${provider.name} API key:`,
|
|
121
|
+
validate: (input) => {
|
|
122
|
+
if (!input) return `${provider.name} API key is required`;
|
|
123
|
+
if (provider.keyPrefix && !input.startsWith(provider.keyPrefix)) {
|
|
124
|
+
return `Invalid format. Should start with ${provider.keyPrefix}`;
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
}));
|
|
128
|
+
return key;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Prompt for an optional API key with a purpose description
|
|
133
|
+
*/
|
|
134
|
+
export async function promptForOptionalKey(providerKey, purpose) {
|
|
135
|
+
const provider = PROVIDERS[providerKey];
|
|
136
|
+
|
|
137
|
+
const addKey = handleCancel(await clack.confirm({
|
|
138
|
+
message: `Add ${provider.name} API key for ${purpose}? (optional)`,
|
|
139
|
+
initialValue: false,
|
|
140
|
+
}));
|
|
141
|
+
|
|
142
|
+
if (!addKey) return null;
|
|
143
|
+
|
|
144
|
+
const openPage = handleCancel(await clack.confirm({
|
|
145
|
+
message: `Open ${provider.name} API key page in browser?`,
|
|
146
|
+
initialValue: true,
|
|
147
|
+
}));
|
|
148
|
+
if (openPage) {
|
|
149
|
+
await open(provider.keyPage);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const key = handleCancel(await clack.password({
|
|
153
|
+
message: `Enter your ${provider.name} API key:`,
|
|
154
|
+
validate: (input) => {
|
|
155
|
+
if (!input) return 'Key is required if adding';
|
|
156
|
+
if (provider.keyPrefix && !input.startsWith(provider.keyPrefix)) {
|
|
157
|
+
return `Invalid format. Should start with ${provider.keyPrefix}`;
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
}));
|
|
161
|
+
return key;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Prompt for custom/local LLM provider details
|
|
166
|
+
*/
|
|
167
|
+
export async function promptForCustomProvider() {
|
|
168
|
+
const baseUrl = handleCancel(await clack.text({
|
|
169
|
+
message: 'API base URL (e.g., http://host.docker.internal:11434/v1):',
|
|
170
|
+
validate: (input) => {
|
|
171
|
+
if (!input) return 'Base URL is required';
|
|
172
|
+
if (!input.startsWith('http://') && !input.startsWith('https://')) {
|
|
173
|
+
return 'URL must start with http:// or https://';
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
}));
|
|
177
|
+
|
|
178
|
+
const model = handleCancel(await clack.text({
|
|
179
|
+
message: 'Model ID (e.g., qwen3:8b):',
|
|
180
|
+
validate: (input) => {
|
|
181
|
+
if (!input) return 'Model ID is required';
|
|
182
|
+
},
|
|
183
|
+
}));
|
|
184
|
+
|
|
185
|
+
const apiKey = handleCancel(await clack.password({
|
|
186
|
+
message: 'API key (leave blank if not needed):',
|
|
187
|
+
}));
|
|
188
|
+
|
|
189
|
+
return { baseUrl, model, apiKey: apiKey || '' };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Prompt for optional Brave Search API key
|
|
194
|
+
*/
|
|
195
|
+
export async function promptForBraveKey() {
|
|
196
|
+
const addKey = handleCancel(await clack.confirm({
|
|
197
|
+
message: 'Add Brave Search API key? (optional, lets your agent search the web)',
|
|
198
|
+
initialValue: false,
|
|
199
|
+
}));
|
|
200
|
+
|
|
201
|
+
if (!addKey) return null;
|
|
202
|
+
|
|
203
|
+
clack.log.info(
|
|
204
|
+
'To get a Brave Search API key:\n' +
|
|
205
|
+
' 1. Go to https://api-dashboard.search.brave.com/app/keys\n' +
|
|
206
|
+
' 2. Click "Get Started"\n' +
|
|
207
|
+
' 3. Create an account (or sign in)\n' +
|
|
208
|
+
' 4. Subscribe to a plan (free credits may be available)\n' +
|
|
209
|
+
' 5. Copy your API key'
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
const openPage = handleCancel(await clack.confirm({
|
|
213
|
+
message: 'Open Brave Search API page in browser?',
|
|
214
|
+
initialValue: true,
|
|
215
|
+
}));
|
|
216
|
+
if (openPage) {
|
|
217
|
+
await open('https://api-dashboard.search.brave.com/app/keys');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const key = handleCancel(await clack.password({
|
|
221
|
+
message: 'Enter your Brave Search API key:',
|
|
222
|
+
validate: (input) => {
|
|
223
|
+
if (!input) return 'Key is required if adding';
|
|
224
|
+
},
|
|
225
|
+
}));
|
|
226
|
+
return key;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Generate a Telegram webhook secret
|
|
231
|
+
*/
|
|
232
|
+
export async function generateTelegramWebhookSecret() {
|
|
233
|
+
const { randomBytes } = await import('crypto');
|
|
234
|
+
return randomBytes(32).toString('hex');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Prompt for confirmation (wraps clack.confirm with cancel handling)
|
|
239
|
+
*/
|
|
240
|
+
export async function confirm(message, initialValue = true) {
|
|
241
|
+
const result = handleCancel(await clack.confirm({
|
|
242
|
+
message,
|
|
243
|
+
initialValue,
|
|
244
|
+
}));
|
|
245
|
+
return result;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Press enter to continue
|
|
250
|
+
*/
|
|
251
|
+
export async function pressEnter(message = 'Press enter to continue') {
|
|
252
|
+
handleCancel(await clack.text({
|
|
253
|
+
message,
|
|
254
|
+
defaultValue: '',
|
|
255
|
+
}));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Prompt for text input
|
|
260
|
+
*/
|
|
261
|
+
export async function promptText(message, defaultValue = '') {
|
|
262
|
+
const value = handleCancel(await clack.text({
|
|
263
|
+
message,
|
|
264
|
+
defaultValue,
|
|
265
|
+
}));
|
|
266
|
+
return value;
|
|
267
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider registry — single source of truth for PI agent LLM providers.
|
|
3
|
+
*
|
|
4
|
+
* "builtin" means PI has a built-in provider (no models.json needed).
|
|
5
|
+
* Non-builtin providers (openai, custom) require a .pi/agent/models.json entry.
|
|
6
|
+
*/
|
|
7
|
+
export const PROVIDERS = {
|
|
8
|
+
anthropic: {
|
|
9
|
+
label: 'Claude (Anthropic)',
|
|
10
|
+
name: 'Anthropic',
|
|
11
|
+
envKey: 'ANTHROPIC_API_KEY',
|
|
12
|
+
keyPrefix: 'sk-ant-',
|
|
13
|
+
keyPage: 'https://platform.claude.com/settings/keys',
|
|
14
|
+
builtin: true,
|
|
15
|
+
oauthSupported: true,
|
|
16
|
+
models: [
|
|
17
|
+
{ id: 'claude-opus-4-6', name: 'Claude Opus 4.6', default: true },
|
|
18
|
+
{ id: 'claude-sonnet-4-6', name: 'Claude Sonnet 4.6' },
|
|
19
|
+
{ id: 'claude-haiku-4-5-20251001', name: 'Claude Haiku 4.5' },
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
openai: {
|
|
23
|
+
label: 'GPT (OpenAI)',
|
|
24
|
+
name: 'OpenAI',
|
|
25
|
+
envKey: 'OPENAI_API_KEY',
|
|
26
|
+
keyPrefix: 'sk-',
|
|
27
|
+
keyPage: 'https://platform.openai.com/settings/organization/api-keys',
|
|
28
|
+
builtin: false,
|
|
29
|
+
baseUrl: 'https://api.openai.com/v1',
|
|
30
|
+
api: 'openai-completions',
|
|
31
|
+
models: [
|
|
32
|
+
{ id: 'gpt-5.2', name: 'GPT-5.2', default: true },
|
|
33
|
+
{ id: 'gpt-4o', name: 'GPT-4o' },
|
|
34
|
+
{ id: 'o4-mini', name: 'o4-mini' },
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
google: {
|
|
38
|
+
label: 'Gemini (Google)',
|
|
39
|
+
name: 'Google',
|
|
40
|
+
envKey: 'GOOGLE_API_KEY',
|
|
41
|
+
keyPage: 'https://aistudio.google.com/apikey',
|
|
42
|
+
builtin: true,
|
|
43
|
+
models: [
|
|
44
|
+
{ id: 'gemini-3.1-pro', name: 'Gemini 3.1 Pro', default: true },
|
|
45
|
+
{ id: 'gemini-2.5-flash', name: 'Gemini 2.5 Flash' },
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import * as clack from '@clack/prompts';
|
|
2
|
+
import { updateEnvVariable } from './auth.mjs';
|
|
3
|
+
import { setSecret, setVariable } from './github.mjs';
|
|
4
|
+
import { CONFIG_TARGETS } from './targets.mjs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Sync collected config values to .env and GitHub.
|
|
8
|
+
*
|
|
9
|
+
* Only keys present in `collected` are considered.
|
|
10
|
+
* Only values that actually changed (vs `env`) are written.
|
|
11
|
+
*
|
|
12
|
+
* @param {object|null} env - Current .env values (null if no .env)
|
|
13
|
+
* @param {object} collected - All collected config values
|
|
14
|
+
* @param {object} options
|
|
15
|
+
* @param {string} options.owner - GitHub owner
|
|
16
|
+
* @param {string} options.repo - GitHub repo
|
|
17
|
+
* @returns {Promise<object>} Sync report
|
|
18
|
+
*/
|
|
19
|
+
export async function syncConfig(env, collected, { owner, repo }) {
|
|
20
|
+
const envUpdates = [];
|
|
21
|
+
const secretUpdates = [];
|
|
22
|
+
const variableUpdates = [];
|
|
23
|
+
const isFirstRun = !env?.GH_TOKEN;
|
|
24
|
+
|
|
25
|
+
for (const [key, value] of Object.entries(collected)) {
|
|
26
|
+
const target = CONFIG_TARGETS[key];
|
|
27
|
+
if (!target) continue;
|
|
28
|
+
|
|
29
|
+
const oldValue = env?.[key] ?? '';
|
|
30
|
+
const changed = value !== oldValue;
|
|
31
|
+
|
|
32
|
+
// .env — write if changed and target has env: true
|
|
33
|
+
if (target.env && changed) {
|
|
34
|
+
envUpdates.push({ key, value });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// GitHub secret — only if changed AND value is non-empty
|
|
38
|
+
if (target.secret && changed && value) {
|
|
39
|
+
const secretName = target.secret === true ? key : target.secret;
|
|
40
|
+
secretUpdates.push({ key, secretName, value });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// GitHub variable — only if changed
|
|
44
|
+
if (target.variable && changed) {
|
|
45
|
+
// Skip firstRunOnly variables unless this is the first setup
|
|
46
|
+
if (target.firstRunOnly && !isFirstRun) continue;
|
|
47
|
+
variableUpdates.push({ key, value });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Also handle firstRunOnly defaults that aren't in collected
|
|
52
|
+
if (isFirstRun) {
|
|
53
|
+
for (const [key, target] of Object.entries(CONFIG_TARGETS)) {
|
|
54
|
+
if (target.firstRunOnly && target.default && !(key in collected)) {
|
|
55
|
+
variableUpdates.push({ key, value: target.default });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const report = { env: [], secrets: [], variables: [], errors: [] };
|
|
61
|
+
|
|
62
|
+
if (envUpdates.length === 0 && secretUpdates.length === 0 && variableUpdates.length === 0) {
|
|
63
|
+
clack.log.info('Config unchanged');
|
|
64
|
+
return report;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const s = clack.spinner();
|
|
68
|
+
|
|
69
|
+
// Write .env updates
|
|
70
|
+
if (envUpdates.length > 0) {
|
|
71
|
+
for (const { key, value } of envUpdates) {
|
|
72
|
+
updateEnvVariable(key, value);
|
|
73
|
+
report.env.push(key);
|
|
74
|
+
}
|
|
75
|
+
clack.log.success(`Updated .env (${report.env.join(', ')})`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Set GitHub secrets
|
|
79
|
+
if (secretUpdates.length > 0) {
|
|
80
|
+
s.start('Setting GitHub secrets...');
|
|
81
|
+
let allOk = true;
|
|
82
|
+
for (const { key, secretName, value } of secretUpdates) {
|
|
83
|
+
const result = await setSecret(owner, repo, secretName, value);
|
|
84
|
+
if (result.success) {
|
|
85
|
+
report.secrets.push(secretName);
|
|
86
|
+
} else {
|
|
87
|
+
report.errors.push(`Failed to set secret ${secretName}: ${result.error}`);
|
|
88
|
+
allOk = false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (allOk) {
|
|
92
|
+
s.stop('GitHub secrets set');
|
|
93
|
+
} else {
|
|
94
|
+
s.stop('Some secrets failed');
|
|
95
|
+
for (const err of report.errors) {
|
|
96
|
+
clack.log.error(err);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Set GitHub variables
|
|
102
|
+
if (variableUpdates.length > 0) {
|
|
103
|
+
s.start('Setting GitHub variables...');
|
|
104
|
+
let allOk = true;
|
|
105
|
+
for (const { key, value } of variableUpdates) {
|
|
106
|
+
const result = await setVariable(owner, repo, key, value);
|
|
107
|
+
if (result.success) {
|
|
108
|
+
report.variables.push(key);
|
|
109
|
+
} else {
|
|
110
|
+
report.errors.push(`Failed to set variable ${key}: ${result.error}`);
|
|
111
|
+
allOk = false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (allOk) {
|
|
115
|
+
s.stop('GitHub variables set');
|
|
116
|
+
} else {
|
|
117
|
+
s.stop('Some variables failed');
|
|
118
|
+
for (const err of report.errors) {
|
|
119
|
+
clack.log.error(err);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return report;
|
|
125
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config target mapping — single source of truth for where each config key goes.
|
|
3
|
+
*
|
|
4
|
+
* `env: true` — written to .env
|
|
5
|
+
* `secret: true` — GitHub secret name === env key name
|
|
6
|
+
* `secret: 'NAME'` — GitHub secret uses a different name
|
|
7
|
+
* `variable: true` — GitHub repository variable
|
|
8
|
+
* `default` — default value for firstRunOnly variables
|
|
9
|
+
* `firstRunOnly: true` — only set on GitHub during first-time setup
|
|
10
|
+
*
|
|
11
|
+
* Only keys present in `collected` are synced — absent keys are untouched.
|
|
12
|
+
* Empty string values write to .env but do NOT set as GitHub secrets.
|
|
13
|
+
*/
|
|
14
|
+
export const CONFIG_TARGETS = {
|
|
15
|
+
GH_TOKEN: { env: true, secret: 'AGENT_GH_TOKEN' },
|
|
16
|
+
GH_OWNER: { env: true },
|
|
17
|
+
GH_REPO: { env: true },
|
|
18
|
+
|
|
19
|
+
LLM_PROVIDER: { env: true, variable: true },
|
|
20
|
+
LLM_MODEL: { env: true, variable: true },
|
|
21
|
+
ANTHROPIC_API_KEY: { env: true, secret: 'AGENT_ANTHROPIC_API_KEY' },
|
|
22
|
+
OPENAI_API_KEY: { env: true, secret: 'AGENT_OPENAI_API_KEY' },
|
|
23
|
+
GOOGLE_API_KEY: { env: true, secret: 'AGENT_GOOGLE_API_KEY' },
|
|
24
|
+
CUSTOM_API_KEY: { env: true, secret: 'AGENT_CUSTOM_API_KEY' },
|
|
25
|
+
OPENAI_BASE_URL: { env: true, variable: true },
|
|
26
|
+
|
|
27
|
+
CLAUDE_CODE_OAUTH_TOKEN: { env: true, secret: 'AGENT_CLAUDE_CODE_OAUTH_TOKEN' },
|
|
28
|
+
AGENT_BACKEND: { env: true, variable: true },
|
|
29
|
+
|
|
30
|
+
BRAVE_API_KEY: { secret: 'AGENT_LLM_BRAVE_API_KEY' },
|
|
31
|
+
|
|
32
|
+
GH_WEBHOOK_SECRET: { env: true, secret: true },
|
|
33
|
+
|
|
34
|
+
APP_URL: { env: true, variable: true },
|
|
35
|
+
APP_HOSTNAME: { env: true },
|
|
36
|
+
|
|
37
|
+
AUTO_MERGE: { variable: true, default: 'true', firstRunOnly: true },
|
|
38
|
+
ALLOWED_PATHS: { variable: true, default: '/logs', firstRunOnly: true },
|
|
39
|
+
RUNS_ON: { variable: true },
|
|
40
|
+
|
|
41
|
+
TELEGRAM_BOT_TOKEN: { env: true },
|
|
42
|
+
TELEGRAM_WEBHOOK_SECRET: { env: true },
|
|
43
|
+
TELEGRAM_CHAT_ID: { env: true },
|
|
44
|
+
TELEGRAM_VERIFICATION: { env: true },
|
|
45
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as clack from '@clack/prompts';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Run the chat ID verification flow
|
|
5
|
+
* @param {string} verificationCode - The code user should send to bot
|
|
6
|
+
* @param {object} [options] - Options
|
|
7
|
+
* @param {boolean} [options.allowSkip=false] - Allow pressing Enter to skip
|
|
8
|
+
* @returns {Promise<string|null>} - The chat ID or null if skipped
|
|
9
|
+
*/
|
|
10
|
+
export async function runVerificationFlow(verificationCode, { allowSkip = false } = {}) {
|
|
11
|
+
clack.log.warn('Chat ID Verification');
|
|
12
|
+
clack.log.info(
|
|
13
|
+
'To lock the bot to your chat, send the verification code.\n' +
|
|
14
|
+
` Send this message to your bot: ${verificationCode}\n` +
|
|
15
|
+
' The bot will reply with your chat ID. Paste it below.'
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const message = allowSkip
|
|
19
|
+
? 'Paste your chat ID from the bot (or press Enter to skip):'
|
|
20
|
+
: 'Paste your chat ID from the bot:';
|
|
21
|
+
|
|
22
|
+
const chatId = await clack.text({
|
|
23
|
+
message,
|
|
24
|
+
defaultValue: allowSkip ? '' : undefined,
|
|
25
|
+
validate: (input) => {
|
|
26
|
+
if (!input) return allowSkip ? undefined : 'Chat ID is required';
|
|
27
|
+
if (!/^-?\d+$/.test(input.trim())) {
|
|
28
|
+
return 'Chat ID should be a number (can be negative for groups)';
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (clack.isCancel(chatId)) {
|
|
34
|
+
clack.cancel('Setup cancelled.');
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return chatId.trim() || null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Wait for server to pick up .env changes and verify it's running
|
|
43
|
+
* @param {string} ngrokUrl - The ngrok URL
|
|
44
|
+
* @returns {Promise<boolean>} - True if verified successfully
|
|
45
|
+
*/
|
|
46
|
+
export async function verifyRestart(ngrokUrl) {
|
|
47
|
+
const s = clack.spinner();
|
|
48
|
+
s.start('Waiting for server to pick up changes...');
|
|
49
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
await fetch(`${ngrokUrl}/api/ping`, {
|
|
53
|
+
method: 'GET',
|
|
54
|
+
signal: AbortSignal.timeout(10000)
|
|
55
|
+
});
|
|
56
|
+
} catch (err) {
|
|
57
|
+
s.stop(`Server not reachable: ${err.message}`);
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
s.stop('Server is running');
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { randomBytes } from 'crypto';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generate a verification code for Telegram chat ID capture
|
|
5
|
+
*/
|
|
6
|
+
export function generateVerificationCode() {
|
|
7
|
+
return 'verify-' + randomBytes(4).toString('hex');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Register a Telegram webhook
|
|
12
|
+
*/
|
|
13
|
+
export async function setTelegramWebhook(botToken, webhookUrl, secretToken = null) {
|
|
14
|
+
// Delete first — Telegram ignores secret_token changes if the URL is unchanged
|
|
15
|
+
await deleteTelegramWebhook(botToken);
|
|
16
|
+
|
|
17
|
+
const body = { url: webhookUrl };
|
|
18
|
+
if (secretToken) {
|
|
19
|
+
body.secret_token = secretToken;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const response = await fetch(
|
|
23
|
+
`https://api.telegram.org/bot${botToken}/setWebhook`,
|
|
24
|
+
{
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers: { 'Content-Type': 'application/json' },
|
|
27
|
+
body: JSON.stringify(body),
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const result = await response.json();
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get current webhook info
|
|
37
|
+
*/
|
|
38
|
+
export async function getTelegramWebhookInfo(botToken) {
|
|
39
|
+
const response = await fetch(`https://api.telegram.org/bot${botToken}/getWebhookInfo`);
|
|
40
|
+
const result = await response.json();
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Delete existing webhook
|
|
46
|
+
*/
|
|
47
|
+
export async function deleteTelegramWebhook(botToken) {
|
|
48
|
+
const response = await fetch(`https://api.telegram.org/bot${botToken}/deleteWebhook`, {
|
|
49
|
+
method: 'POST',
|
|
50
|
+
});
|
|
51
|
+
const result = await response.json();
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Validate bot token by calling getMe
|
|
57
|
+
*/
|
|
58
|
+
export async function validateBotToken(botToken) {
|
|
59
|
+
try {
|
|
60
|
+
const response = await fetch(`https://api.telegram.org/bot${botToken}/getMe`);
|
|
61
|
+
const result = await response.json();
|
|
62
|
+
if (result.ok) {
|
|
63
|
+
return { valid: true, botInfo: result.result };
|
|
64
|
+
}
|
|
65
|
+
return { valid: false, error: result.description };
|
|
66
|
+
} catch (error) {
|
|
67
|
+
return { valid: false, error: error.message };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get BotFather URL for creating a new bot
|
|
73
|
+
*/
|
|
74
|
+
export function getBotFatherURL() {
|
|
75
|
+
return 'https://t.me/BotFather';
|
|
76
|
+
}
|