@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,102 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { PROJECT_ROOT, piSkillsDir } from '../paths.js';
|
|
4
|
+
import { loadAgentDescriptions } from '../agents.js';
|
|
5
|
+
|
|
6
|
+
const INCLUDE_PATTERN = /\{\{([^}]+\.md)\}\}/g;
|
|
7
|
+
const VARIABLE_PATTERN = /\{\{(datetime|skills|agents)\}\}/gi;
|
|
8
|
+
|
|
9
|
+
// Scan skill directories under .pi/skills/ for SKILL.md files and extract
|
|
10
|
+
// description from YAML frontmatter. Returns a bullet list of descriptions.
|
|
11
|
+
function loadSkillDescriptions() {
|
|
12
|
+
try {
|
|
13
|
+
if (!fs.existsSync(piSkillsDir)) {
|
|
14
|
+
return 'No additional abilities configured.';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const entries = fs.readdirSync(piSkillsDir, { withFileTypes: true });
|
|
18
|
+
const descriptions = [];
|
|
19
|
+
|
|
20
|
+
for (const entry of entries) {
|
|
21
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
22
|
+
|
|
23
|
+
const skillMdPath = path.join(piSkillsDir, entry.name, 'SKILL.md');
|
|
24
|
+
if (!fs.existsSync(skillMdPath)) continue;
|
|
25
|
+
|
|
26
|
+
const content = fs.readFileSync(skillMdPath, 'utf8');
|
|
27
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
28
|
+
if (!frontmatterMatch) continue;
|
|
29
|
+
|
|
30
|
+
const frontmatter = frontmatterMatch[1];
|
|
31
|
+
const descMatch = frontmatter.match(/^description:\s*(.+)$/m);
|
|
32
|
+
if (descMatch) {
|
|
33
|
+
descriptions.push(`- ${descMatch[1].trim()}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (descriptions.length === 0) {
|
|
38
|
+
return 'No additional abilities configured.';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return descriptions.join('\n');
|
|
42
|
+
} catch {
|
|
43
|
+
return 'No additional abilities configured.';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Resolve built-in variables like {{datetime}} and {{skills}}.
|
|
49
|
+
* @param {string} content - Content with possible variable placeholders
|
|
50
|
+
* @returns {string} Content with variables resolved
|
|
51
|
+
*/
|
|
52
|
+
function resolveVariables(content) {
|
|
53
|
+
return content.replace(VARIABLE_PATTERN, (match, variable) => {
|
|
54
|
+
switch (variable.toLowerCase()) {
|
|
55
|
+
case 'datetime':
|
|
56
|
+
return new Date().toISOString();
|
|
57
|
+
case 'skills':
|
|
58
|
+
return loadSkillDescriptions();
|
|
59
|
+
case 'agents':
|
|
60
|
+
return loadAgentDescriptions();
|
|
61
|
+
default:
|
|
62
|
+
return match;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Render a markdown file, resolving {{filepath}} includes recursively
|
|
69
|
+
* and {{datetime}}, {{skills}} built-in variables.
|
|
70
|
+
* Referenced file paths resolve relative to the project root.
|
|
71
|
+
* @param {string} filePath - Absolute path to the markdown file
|
|
72
|
+
* @param {string[]} [chain=[]] - Already-resolved file paths (for circular detection)
|
|
73
|
+
* @returns {string} Rendered markdown content
|
|
74
|
+
*/
|
|
75
|
+
function render_md(filePath, chain = []) {
|
|
76
|
+
const resolved = path.resolve(filePath);
|
|
77
|
+
|
|
78
|
+
if (chain.includes(resolved)) {
|
|
79
|
+
const cycle = [...chain, resolved].map((p) => path.relative(PROJECT_ROOT, p)).join(' -> ');
|
|
80
|
+
console.log(`[render_md] Circular include detected: ${cycle}`);
|
|
81
|
+
return '';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!fs.existsSync(resolved)) {
|
|
85
|
+
return '';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const content = fs.readFileSync(resolved, 'utf8');
|
|
89
|
+
const currentChain = [...chain, resolved];
|
|
90
|
+
|
|
91
|
+
const withIncludes = content.replace(INCLUDE_PATTERN, (match, includePath) => {
|
|
92
|
+
const includeResolved = path.resolve(PROJECT_ROOT, includePath.trim());
|
|
93
|
+
if (!fs.existsSync(includeResolved)) {
|
|
94
|
+
return match;
|
|
95
|
+
}
|
|
96
|
+
return render_md(includeResolved, currentChain);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return resolveVariables(withIncludes);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export { render_md };
|
package/package.json
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@harbinger-ai/harbinger",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Create autonomous AI agents with a two-layer architecture: Next.js Event Handler + Docker Agent.",
|
|
6
|
+
"bin": {
|
|
7
|
+
"harbinger": "./bin/cli.js",
|
|
8
|
+
"thepopebot": "./bin/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"main": "api/index.js",
|
|
11
|
+
"exports": {
|
|
12
|
+
"./api": "./api/index.js",
|
|
13
|
+
"./config": "./config/index.js",
|
|
14
|
+
"./instrumentation": "./config/instrumentation.js",
|
|
15
|
+
"./auth": "./lib/auth/index.js",
|
|
16
|
+
"./auth/actions": "./lib/auth/actions.js",
|
|
17
|
+
"./chat/api": "./lib/chat/api.js",
|
|
18
|
+
"./db": "./lib/db/index.js",
|
|
19
|
+
"./chat": "./lib/chat/components/index.js",
|
|
20
|
+
"./chat/actions": "./lib/chat/actions.js",
|
|
21
|
+
"./middleware": "./lib/auth/middleware.js",
|
|
22
|
+
"./mcp": "./lib/mcp/server.js",
|
|
23
|
+
"./package.json": "./package.json"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"agents/",
|
|
27
|
+
"bin/",
|
|
28
|
+
"lib/",
|
|
29
|
+
"api/",
|
|
30
|
+
"config/",
|
|
31
|
+
"setup/",
|
|
32
|
+
"templates/",
|
|
33
|
+
"drizzle/"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "esbuild lib/chat/components/*.jsx lib/chat/components/**/*.jsx --outdir=lib/chat/components --format=esm --jsx=automatic --outbase=lib/chat/components",
|
|
37
|
+
"prepublishOnly": "npm run build",
|
|
38
|
+
"local": "bash bin/local.sh",
|
|
39
|
+
"postinstall": "node -e \"import('./bin/postinstall.js').catch(()=>{})\"",
|
|
40
|
+
"db:generate": "drizzle-kit generate",
|
|
41
|
+
"db:studio": "drizzle-kit studio",
|
|
42
|
+
"test": "echo \"No tests yet\" && exit 0"
|
|
43
|
+
},
|
|
44
|
+
"keywords": [
|
|
45
|
+
"ai",
|
|
46
|
+
"agent",
|
|
47
|
+
"autonomous",
|
|
48
|
+
"telegram",
|
|
49
|
+
"bot",
|
|
50
|
+
"claude",
|
|
51
|
+
"nextjs"
|
|
52
|
+
],
|
|
53
|
+
"author": "Stephen Pope",
|
|
54
|
+
"license": "MIT",
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"@ai-sdk/react": "^2.0.0",
|
|
57
|
+
"@grammyjs/parse-mode": "^2.2.0",
|
|
58
|
+
"@langchain/anthropic": "^1.3.17",
|
|
59
|
+
"@langchain/core": "^1.1.24",
|
|
60
|
+
"@langchain/google-genai": "^2.1.18",
|
|
61
|
+
"@langchain/mcp-adapters": "^0.6.0",
|
|
62
|
+
"@langchain/langgraph": "^1.1.4",
|
|
63
|
+
"@langchain/langgraph-checkpoint-sqlite": "^1.0.1",
|
|
64
|
+
"@langchain/openai": "^1.2.7",
|
|
65
|
+
"@modelcontextprotocol/sdk": "^1.27.0",
|
|
66
|
+
"ai": "^5.0.0",
|
|
67
|
+
"bcrypt-ts": "^6.0.0",
|
|
68
|
+
"better-sqlite3": "^12.6.2",
|
|
69
|
+
"chalk": "^5.3.0",
|
|
70
|
+
"class-variance-authority": "^0.7.0",
|
|
71
|
+
"clsx": "^2.0.0",
|
|
72
|
+
"dotenv": "^16.3.1",
|
|
73
|
+
"framer-motion": "^11.0.0",
|
|
74
|
+
"drizzle-orm": "^0.44.0",
|
|
75
|
+
"grammy": "^1.39.3",
|
|
76
|
+
"@clack/prompts": "^0.10.0",
|
|
77
|
+
"lucide-react": "^0.400.0",
|
|
78
|
+
"node-cron": "^3.0.3",
|
|
79
|
+
"open": "^10.0.0",
|
|
80
|
+
"streamdown": "^2.2.0",
|
|
81
|
+
"tailwind-merge": "^3.0.0",
|
|
82
|
+
"uuid": "^9.0.0",
|
|
83
|
+
"zod": "^4.3.6"
|
|
84
|
+
},
|
|
85
|
+
"peerDependencies": {
|
|
86
|
+
"next": ">=15.5.12",
|
|
87
|
+
"next-auth": "^5.0.0-beta.30",
|
|
88
|
+
"react": ">=19.0.0",
|
|
89
|
+
"react-dom": ">=19.0.0"
|
|
90
|
+
},
|
|
91
|
+
"peerDependenciesMeta": {
|
|
92
|
+
"next-auth": {
|
|
93
|
+
"optional": false
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"devDependencies": {
|
|
97
|
+
"drizzle-kit": "^0.31.9",
|
|
98
|
+
"esbuild": "^0.27.3"
|
|
99
|
+
},
|
|
100
|
+
"engines": {
|
|
101
|
+
"node": ">=18.0.0"
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { writeFileSync, readFileSync, existsSync, mkdirSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
|
|
4
|
+
const ROOT_DIR = process.cwd();
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Validate Anthropic API key by making a minimal test call
|
|
8
|
+
*/
|
|
9
|
+
export async function validateAnthropicKey(key) {
|
|
10
|
+
try {
|
|
11
|
+
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
12
|
+
method: 'POST',
|
|
13
|
+
headers: {
|
|
14
|
+
'Content-Type': 'application/json',
|
|
15
|
+
'x-api-key': key,
|
|
16
|
+
'anthropic-version': '2023-06-01',
|
|
17
|
+
},
|
|
18
|
+
body: JSON.stringify({
|
|
19
|
+
model: 'claude-3-haiku-20240307',
|
|
20
|
+
max_tokens: 1,
|
|
21
|
+
messages: [{ role: 'user', content: 'Hi' }],
|
|
22
|
+
}),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
if (response.status === 401) {
|
|
26
|
+
return { valid: false, error: 'Invalid API key' };
|
|
27
|
+
}
|
|
28
|
+
if (response.status === 400) {
|
|
29
|
+
// Bad request but key is valid (e.g., rate limit, model error)
|
|
30
|
+
return { valid: true };
|
|
31
|
+
}
|
|
32
|
+
if (response.ok) {
|
|
33
|
+
return { valid: true };
|
|
34
|
+
}
|
|
35
|
+
return { valid: false, error: `HTTP ${response.status}` };
|
|
36
|
+
} catch (error) {
|
|
37
|
+
return { valid: false, error: error.message };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Generate .pi/agent/models.json for providers not built into PI.
|
|
43
|
+
* PI resolves the apiKey field as an env var name at runtime ($ENV_VAR).
|
|
44
|
+
*/
|
|
45
|
+
export function writeModelsJson(providerName, { baseUrl, apiKey, api, models }) {
|
|
46
|
+
const config = {
|
|
47
|
+
providers: {
|
|
48
|
+
[providerName]: {
|
|
49
|
+
baseUrl,
|
|
50
|
+
apiKey,
|
|
51
|
+
api: api || 'openai-completions',
|
|
52
|
+
models: models.map((m) => ({ id: m })),
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
const dir = join(ROOT_DIR, '.pi', 'agent');
|
|
57
|
+
mkdirSync(dir, { recursive: true });
|
|
58
|
+
writeFileSync(join(dir, 'models.json'), JSON.stringify(config, null, 2));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Update a single variable in an existing .env file
|
|
63
|
+
*/
|
|
64
|
+
export function updateEnvVariable(key, value) {
|
|
65
|
+
const envPath = join(ROOT_DIR, '.env');
|
|
66
|
+
if (!existsSync(envPath)) {
|
|
67
|
+
throw new Error('.env file not found. Run npm run setup first.');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let content = readFileSync(envPath, 'utf-8');
|
|
71
|
+
const regex = new RegExp(`^${key}=.*$`, 'm');
|
|
72
|
+
|
|
73
|
+
if (regex.test(content)) {
|
|
74
|
+
content = content.replace(regex, `${key}=${value}`);
|
|
75
|
+
} else {
|
|
76
|
+
content = content.trimEnd() + `\n${key}=${value}\n`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
writeFileSync(envPath, content);
|
|
80
|
+
return envPath;
|
|
81
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Parse .env file and return object, or null if no .env exists
|
|
6
|
+
*/
|
|
7
|
+
export function loadEnvFile(dir = process.cwd()) {
|
|
8
|
+
const envPath = join(dir, '.env');
|
|
9
|
+
if (!existsSync(envPath)) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
const content = readFileSync(envPath, 'utf-8');
|
|
13
|
+
const env = {};
|
|
14
|
+
for (const line of content.split('\n')) {
|
|
15
|
+
const match = line.match(/^([^#=]+)=(.*)$/);
|
|
16
|
+
if (match) {
|
|
17
|
+
env[match[1].trim()] = match[2].trim();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return env;
|
|
21
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Create a directory symlink (Unix) or junction/copy fallback (Windows).
|
|
6
|
+
*/
|
|
7
|
+
export function createDirLink(target, linkPath) {
|
|
8
|
+
if (process.platform !== 'win32') {
|
|
9
|
+
fs.symlinkSync(target, linkPath);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
// Junctions require absolute targets but don't require admin privileges
|
|
13
|
+
const absoluteTarget = path.resolve(path.dirname(linkPath), target);
|
|
14
|
+
try {
|
|
15
|
+
fs.symlinkSync(absoluteTarget, linkPath, 'junction');
|
|
16
|
+
} catch {
|
|
17
|
+
fs.cpSync(absoluteTarget, linkPath, { recursive: true });
|
|
18
|
+
console.log(' (copied — symlinks unavailable on this system)');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { execSync, exec } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
import { randomBytes } from 'crypto';
|
|
4
|
+
import { ghEnv } from './prerequisites.mjs';
|
|
5
|
+
|
|
6
|
+
const execAsync = promisify(exec);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Validate GitHub PAT by making a test API call
|
|
10
|
+
*/
|
|
11
|
+
export async function validatePAT(token) {
|
|
12
|
+
try {
|
|
13
|
+
const response = await fetch('https://api.github.com/user', {
|
|
14
|
+
headers: {
|
|
15
|
+
Authorization: `token ${token}`,
|
|
16
|
+
Accept: 'application/vnd.github.v3+json',
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
if (!response.ok) return { valid: false, error: 'Invalid token' };
|
|
20
|
+
const user = await response.json();
|
|
21
|
+
return { valid: true, user: user.login };
|
|
22
|
+
} catch (error) {
|
|
23
|
+
return { valid: false, error: error.message };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check PAT scopes/permissions
|
|
29
|
+
* Works with both classic tokens (x-oauth-scopes header) and fine-grained tokens
|
|
30
|
+
*/
|
|
31
|
+
export async function checkPATScopes(token) {
|
|
32
|
+
try {
|
|
33
|
+
const response = await fetch('https://api.github.com/user', {
|
|
34
|
+
headers: {
|
|
35
|
+
Authorization: `token ${token}`,
|
|
36
|
+
Accept: 'application/vnd.github.v3+json',
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
const scopes = response.headers.get('x-oauth-scopes') || '';
|
|
40
|
+
const scopeList = scopes.split(',').map((s) => s.trim()).filter(Boolean);
|
|
41
|
+
|
|
42
|
+
// Classic tokens have x-oauth-scopes header
|
|
43
|
+
if (scopeList.length > 0) {
|
|
44
|
+
return {
|
|
45
|
+
hasRepo: scopeList.includes('repo'),
|
|
46
|
+
hasWorkflow: scopeList.includes('workflow'),
|
|
47
|
+
scopes: scopeList,
|
|
48
|
+
isFineGrained: false,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Fine-grained tokens don't have x-oauth-scopes header
|
|
53
|
+
// We can't check permissions directly, so we assume valid if token works
|
|
54
|
+
return {
|
|
55
|
+
hasRepo: true,
|
|
56
|
+
hasWorkflow: true,
|
|
57
|
+
scopes: [],
|
|
58
|
+
isFineGrained: true,
|
|
59
|
+
};
|
|
60
|
+
} catch {
|
|
61
|
+
return { hasRepo: false, hasWorkflow: false, scopes: [], isFineGrained: false };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Set a GitHub repository secret using gh CLI
|
|
67
|
+
*/
|
|
68
|
+
export async function setSecret(owner, repo, name, value) {
|
|
69
|
+
try {
|
|
70
|
+
execSync(
|
|
71
|
+
`gh secret set ${name} --repo ${owner}/${repo}`,
|
|
72
|
+
{ input: value, encoding: 'utf-8', env: ghEnv(), stdio: ['pipe', 'pipe', 'pipe'] }
|
|
73
|
+
);
|
|
74
|
+
return { success: true };
|
|
75
|
+
} catch (error) {
|
|
76
|
+
return { success: false, error: error.message };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Set multiple GitHub secrets
|
|
82
|
+
*/
|
|
83
|
+
export async function setSecrets(owner, repo, secrets) {
|
|
84
|
+
const results = {};
|
|
85
|
+
for (const [name, value] of Object.entries(secrets)) {
|
|
86
|
+
results[name] = await setSecret(owner, repo, name, value);
|
|
87
|
+
}
|
|
88
|
+
return results;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* List existing secrets
|
|
93
|
+
*/
|
|
94
|
+
export async function listSecrets(owner, repo) {
|
|
95
|
+
try {
|
|
96
|
+
const { stdout } = await execAsync(`gh secret list --repo ${owner}/${repo}`, {
|
|
97
|
+
encoding: 'utf-8',
|
|
98
|
+
env: ghEnv(),
|
|
99
|
+
});
|
|
100
|
+
const secrets = stdout
|
|
101
|
+
.trim()
|
|
102
|
+
.split('\n')
|
|
103
|
+
.filter(Boolean)
|
|
104
|
+
.map((line) => line.split('\t')[0]);
|
|
105
|
+
return secrets;
|
|
106
|
+
} catch {
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Set a GitHub repository variable using gh CLI
|
|
113
|
+
*/
|
|
114
|
+
export async function setVariable(owner, repo, name, value) {
|
|
115
|
+
try {
|
|
116
|
+
execSync(
|
|
117
|
+
`gh variable set ${name} --repo ${owner}/${repo}`,
|
|
118
|
+
{ input: value, encoding: 'utf-8', env: ghEnv(), stdio: ['pipe', 'pipe', 'pipe'] }
|
|
119
|
+
);
|
|
120
|
+
return { success: true };
|
|
121
|
+
} catch (error) {
|
|
122
|
+
return { success: false, error: error.message };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Set multiple GitHub repository variables
|
|
128
|
+
*/
|
|
129
|
+
export async function setVariables(owner, repo, variables) {
|
|
130
|
+
const results = {};
|
|
131
|
+
for (const [name, value] of Object.entries(variables)) {
|
|
132
|
+
results[name] = await setVariable(owner, repo, name, value);
|
|
133
|
+
}
|
|
134
|
+
return results;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Generate a random webhook secret
|
|
139
|
+
*/
|
|
140
|
+
export function generateWebhookSecret() {
|
|
141
|
+
return randomBytes(32).toString('hex');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Get the GitHub PAT creation URL with pre-selected scopes
|
|
146
|
+
*/
|
|
147
|
+
export function getPATCreationURL() {
|
|
148
|
+
return 'https://github.com/settings/personal-access-tokens/new';
|
|
149
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { execSync, exec } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
|
|
4
|
+
const execAsync = promisify(exec);
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Return process.env without GITHUB_TOKEN and GH_TOKEN.
|
|
8
|
+
* The gh CLI auto-uses these env vars, which can shadow interactive login
|
|
9
|
+
* and cause secret/variable operations to fail with the wrong identity.
|
|
10
|
+
*/
|
|
11
|
+
export function ghEnv() {
|
|
12
|
+
const env = { ...process.env };
|
|
13
|
+
delete env.GITHUB_TOKEN;
|
|
14
|
+
delete env.GH_TOKEN;
|
|
15
|
+
return env;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Check if a command exists
|
|
20
|
+
*/
|
|
21
|
+
function commandExists(cmd) {
|
|
22
|
+
const checkCmd = process.platform === 'win32' ? 'where' : 'which';
|
|
23
|
+
try {
|
|
24
|
+
execSync(`${checkCmd} ${cmd}`, { stdio: 'ignore' });
|
|
25
|
+
return true;
|
|
26
|
+
} catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get Node.js version
|
|
33
|
+
*/
|
|
34
|
+
function getNodeVersion() {
|
|
35
|
+
try {
|
|
36
|
+
const version = execSync('node --version', { encoding: 'utf-8' }).trim();
|
|
37
|
+
return version.replace('v', '');
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Check if gh CLI is authenticated
|
|
45
|
+
*/
|
|
46
|
+
async function isGhAuthenticated() {
|
|
47
|
+
try {
|
|
48
|
+
await execAsync('gh auth status', { env: ghEnv() });
|
|
49
|
+
return true;
|
|
50
|
+
} catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get git remote info (owner/repo)
|
|
57
|
+
*/
|
|
58
|
+
function getGitRemoteInfo() {
|
|
59
|
+
try {
|
|
60
|
+
const remote = execSync('git remote get-url origin', { encoding: 'utf-8' }).trim();
|
|
61
|
+
// Handle both HTTPS and SSH formats
|
|
62
|
+
// https://github.com/owner/repo.git
|
|
63
|
+
// git@github.com:owner/repo.git
|
|
64
|
+
const httpsMatch = remote.match(/github\.com\/([^/]+)\/(.+?)(?:\.git)?$/);
|
|
65
|
+
const sshMatch = remote.match(/github\.com:([^/]+)\/(.+?)(?:\.git)?$/);
|
|
66
|
+
const match = httpsMatch || sshMatch;
|
|
67
|
+
if (match) {
|
|
68
|
+
return { owner: match[1], repo: match[2] };
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
} catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get package manager (pnpm preferred, npm fallback)
|
|
78
|
+
*/
|
|
79
|
+
function getPackageManager() {
|
|
80
|
+
if (commandExists('pnpm')) return 'pnpm';
|
|
81
|
+
if (commandExists('npm')) return 'npm';
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Check all prerequisites and return status
|
|
87
|
+
*/
|
|
88
|
+
export async function checkPrerequisites() {
|
|
89
|
+
const results = {
|
|
90
|
+
node: { installed: false, version: null, ok: false },
|
|
91
|
+
packageManager: { installed: false, name: null },
|
|
92
|
+
gh: { installed: false, authenticated: false },
|
|
93
|
+
ngrok: { installed: false },
|
|
94
|
+
git: { installed: false, remoteInfo: null },
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Check Node.js
|
|
98
|
+
const nodeVersion = getNodeVersion();
|
|
99
|
+
if (nodeVersion) {
|
|
100
|
+
results.node.installed = true;
|
|
101
|
+
results.node.version = nodeVersion;
|
|
102
|
+
const [major] = nodeVersion.split('.').map(Number);
|
|
103
|
+
results.node.ok = major >= 18;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Check package manager
|
|
107
|
+
const pm = getPackageManager();
|
|
108
|
+
if (pm) {
|
|
109
|
+
results.packageManager.installed = true;
|
|
110
|
+
results.packageManager.name = pm;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Check gh CLI
|
|
114
|
+
results.gh.installed = commandExists('gh');
|
|
115
|
+
if (results.gh.installed) {
|
|
116
|
+
results.gh.authenticated = await isGhAuthenticated();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Check ngrok
|
|
120
|
+
results.ngrok.installed = commandExists('ngrok');
|
|
121
|
+
|
|
122
|
+
// Check git
|
|
123
|
+
results.git.installed = commandExists('git');
|
|
124
|
+
if (results.git.installed) {
|
|
125
|
+
// Initialize git repo if needed (must happen before remote check)
|
|
126
|
+
try {
|
|
127
|
+
execSync('git rev-parse --git-dir', { stdio: 'ignore' });
|
|
128
|
+
results.git.initialized = true;
|
|
129
|
+
} catch {
|
|
130
|
+
results.git.initialized = false;
|
|
131
|
+
}
|
|
132
|
+
results.git.remoteInfo = getGitRemoteInfo();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return results;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Install a global npm package
|
|
140
|
+
*/
|
|
141
|
+
export async function installGlobalPackage(packageName) {
|
|
142
|
+
const pm = getPackageManager();
|
|
143
|
+
const cmd = pm === 'pnpm' ? `pnpm add -g ${packageName}` : `npm install -g ${packageName}`;
|
|
144
|
+
await execAsync(cmd);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Run gh auth login
|
|
149
|
+
*/
|
|
150
|
+
export async function runGhAuth() {
|
|
151
|
+
// This needs to be interactive, so we use execSync
|
|
152
|
+
execSync('gh auth login', { stdio: 'inherit', env: ghEnv() });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export { commandExists, getGitRemoteInfo, getPackageManager };
|