thepopebot 1.2.72-beta.13 → 1.2.72-beta.15
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/README.md +2 -2
- package/bin/cli.js +23 -6
- package/lib/ai/agent.js +2 -2
- package/lib/ai/index.js +6 -8
- package/lib/ai/tools.js +5 -5
- package/lib/chat/actions.js +14 -0
- package/lib/chat/components/chat-header.js +20 -2
- package/lib/chat/components/chat-header.jsx +26 -1
- package/lib/chat/components/message.js +2 -1
- package/lib/chat/components/message.jsx +2 -1
- package/lib/chat/components/tool-call.js +2 -1
- package/lib/chat/components/tool-call.jsx +2 -1
- package/lib/paths.js +2 -2
- package/lib/tools/create-job.js +5 -6
- package/package.json +1 -1
- package/setup/lib/prompts.mjs +11 -5
- package/setup/lib/targets.mjs +0 -3
- package/setup/setup.mjs +70 -61
- package/templates/.github/workflows/run-job.yml +2 -2
- package/templates/.gitignore.template +3 -2
- package/templates/CLAUDE.md.template +8 -6
- package/templates/config/EVENT_HANDLER.md +11 -11
- package/templates/config/{PI_SKILL_GUIDE.md → SKILL_BUILDING_GUIDE.md} +12 -12
- package/templates/docker/job-claude-code/Dockerfile +4 -0
- package/templates/docker/job-claude-code/entrypoint.sh +29 -0
- package/templates/docker/job-pi-coding-agent/entrypoint.sh +2 -2
- package/templates/{pi-skills → skills}/README.md +2 -2
- package/templates/{pi-skills → skills}/brave-search/SKILL.md +9 -9
- package/templates/{pi-skills → skills}/browser-tools/SKILL.md +12 -12
- package/templates/{pi-skills → skills}/browser-tools/browser-start.js +3 -2
- package/templates/{.pi/skills → skills}/llm-secrets/SKILL.md +1 -1
- package/templates/{.pi/skills → skills}/modify-self/SKILL.md +1 -1
- /package/templates/{pi-skills → skills}/LICENSE +0 -0
- /package/templates/{pi-skills → skills}/brave-search/content.js +0 -0
- /package/templates/{pi-skills → skills}/brave-search/package-lock.json +0 -0
- /package/templates/{pi-skills → skills}/brave-search/package.json +0 -0
- /package/templates/{pi-skills → skills}/brave-search/search.js +0 -0
- /package/templates/{pi-skills → skills}/browser-tools/browser-content.js +0 -0
- /package/templates/{pi-skills → skills}/browser-tools/browser-cookies.js +0 -0
- /package/templates/{pi-skills → skills}/browser-tools/browser-eval.js +0 -0
- /package/templates/{pi-skills → skills}/browser-tools/browser-hn-scraper.js +0 -0
- /package/templates/{pi-skills → skills}/browser-tools/browser-nav.js +0 -0
- /package/templates/{pi-skills → skills}/browser-tools/browser-pick.js +0 -0
- /package/templates/{pi-skills → skills}/browser-tools/browser-screenshot.js +0 -0
- /package/templates/{pi-skills → skills}/browser-tools/package-lock.json +0 -0
- /package/templates/{pi-skills → skills}/browser-tools/package.json +0 -0
- /package/templates/{.pi/skills → skills}/llm-secrets/llm-secrets.js +0 -0
package/README.md
CHANGED
|
@@ -289,7 +289,7 @@ See [docs/RUNNING_DIFFERENT_MODELS.md](docs/RUNNING_DIFFERENT_MODELS.md) for the
|
|
|
289
289
|
| [Auto-Merge](docs/AUTO_MERGE.md) | Auto-merge controls, ALLOWED_PATHS configuration |
|
|
290
290
|
| [Deployment](docs/DEPLOYMENT.md) | VPS setup, Docker Compose, HTTPS with Let's Encrypt |
|
|
291
291
|
| [Claude Code vs Pi](docs/CLAUDE_CODE_VS_PI.md) | Comparing the two agent backends (subscription vs API credits) |
|
|
292
|
-
| [How to
|
|
292
|
+
| [How to Build Skills](docs/HOW_TO_BUILD_SKILLS.md) | Guide to building and activating agent skills |
|
|
293
293
|
| [Pre-Release](docs/PRE_RELEASE.md) | Installing beta/alpha builds |
|
|
294
294
|
| [Security](docs/SECURITY.md) | Security disclaimer, local development risks |
|
|
295
295
|
| [Upgrading](docs/UPGRADE.md) | Automated upgrades, recovering from failed upgrades |
|
|
@@ -298,4 +298,4 @@ See [docs/RUNNING_DIFFERENT_MODELS.md](docs/RUNNING_DIFFERENT_MODELS.md) for the
|
|
|
298
298
|
|
|
299
299
|
| Document | Description |
|
|
300
300
|
|----------|-------------|
|
|
301
|
-
| [NPM](docs/NPM.md) | Updating
|
|
301
|
+
| [NPM](docs/NPM.md) | Updating skills, versioning, and publishing releases |
|
package/bin/cli.js
CHANGED
|
@@ -207,17 +207,34 @@ async function init() {
|
|
|
207
207
|
}
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
// Create default skill symlinks
|
|
211
|
-
const defaultSkills = ['browser-tools'];
|
|
210
|
+
// Create default skill activation symlinks
|
|
211
|
+
const defaultSkills = ['browser-tools', 'llm-secrets', 'modify-self'];
|
|
212
|
+
const activeDir = path.join(cwd, 'skills', 'active');
|
|
213
|
+
fs.mkdirSync(activeDir, { recursive: true });
|
|
212
214
|
for (const skill of defaultSkills) {
|
|
213
|
-
const symlink = path.join(
|
|
215
|
+
const symlink = path.join(activeDir, skill);
|
|
214
216
|
if (!fs.existsSync(symlink)) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
console.log(` Created .pi/skills/${skill} → ../../pi-skills/${skill}`);
|
|
217
|
+
createDirLink(`../${skill}`, symlink);
|
|
218
|
+
console.log(` Created skills/active/${skill} → ../${skill}`);
|
|
218
219
|
}
|
|
219
220
|
}
|
|
220
221
|
|
|
222
|
+
// Create .pi/skills → ../skills/active symlink
|
|
223
|
+
const piSkillsLink = path.join(cwd, '.pi', 'skills');
|
|
224
|
+
if (!fs.existsSync(piSkillsLink)) {
|
|
225
|
+
fs.mkdirSync(path.dirname(piSkillsLink), { recursive: true });
|
|
226
|
+
createDirLink('../skills/active', piSkillsLink);
|
|
227
|
+
console.log(' Created .pi/skills → ../skills/active');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Create .claude/skills → ../skills/active symlink
|
|
231
|
+
const claudeSkillsLink = path.join(cwd, '.claude', 'skills');
|
|
232
|
+
if (!fs.existsSync(claudeSkillsLink)) {
|
|
233
|
+
fs.mkdirSync(path.dirname(claudeSkillsLink), { recursive: true });
|
|
234
|
+
createDirLink('../skills/active', claudeSkillsLink);
|
|
235
|
+
console.log(' Created .claude/skills → ../skills/active');
|
|
236
|
+
}
|
|
237
|
+
|
|
221
238
|
// Report updated managed files
|
|
222
239
|
if (updated.length > 0) {
|
|
223
240
|
console.log('\n Updated managed files:');
|
package/lib/ai/agent.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createReactAgent } from '@langchain/langgraph/prebuilt';
|
|
2
2
|
import { SystemMessage } from '@langchain/core/messages';
|
|
3
3
|
import { createModel } from './model.js';
|
|
4
|
-
import { createJobTool, getJobStatusTool, getSystemTechnicalSpecsTool,
|
|
4
|
+
import { createJobTool, getJobStatusTool, getSystemTechnicalSpecsTool, getSkillBuildingGuideTool } from './tools.js';
|
|
5
5
|
import { SqliteSaver } from '@langchain/langgraph-checkpoint-sqlite';
|
|
6
6
|
import { eventHandlerMd, thepopebotDb } from '../paths.js';
|
|
7
7
|
import { render_md } from '../utils/render-md.js';
|
|
@@ -16,7 +16,7 @@ let _agent = null;
|
|
|
16
16
|
export async function getAgent() {
|
|
17
17
|
if (!_agent) {
|
|
18
18
|
const model = await createModel();
|
|
19
|
-
const tools = [createJobTool, getJobStatusTool, getSystemTechnicalSpecsTool,
|
|
19
|
+
const tools = [createJobTool, getJobStatusTool, getSystemTechnicalSpecsTool, getSkillBuildingGuideTool];
|
|
20
20
|
const checkpointer = SqliteSaver.fromConnString(thepopebotDb);
|
|
21
21
|
|
|
22
22
|
_agent = createReactAgent({
|
package/lib/ai/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { HumanMessage, AIMessage } from '@langchain/core/messages';
|
|
2
|
+
import { z } from 'zod';
|
|
2
3
|
import { getAgent } from './agent.js';
|
|
3
4
|
import { createModel } from './model.js';
|
|
4
5
|
import { jobSummaryMd } from '../paths.js';
|
|
@@ -207,6 +208,7 @@ async function* chatStream(threadId, message, attachments = [], options = {}) {
|
|
|
207
208
|
|
|
208
209
|
/**
|
|
209
210
|
* Auto-generate a chat title from the first user message (fire-and-forget).
|
|
211
|
+
* Uses structured output to avoid thinking-token leaks with extended-thinking models.
|
|
210
212
|
*/
|
|
211
213
|
async function autoTitle(threadId, firstMessage) {
|
|
212
214
|
try {
|
|
@@ -214,16 +216,12 @@ async function autoTitle(threadId, firstMessage) {
|
|
|
214
216
|
if (!chat || chat.title !== 'New Chat') return;
|
|
215
217
|
|
|
216
218
|
const model = await createModel({ maxTokens: 250 });
|
|
217
|
-
const response = await model.invoke([
|
|
218
|
-
['system', 'Generate a
|
|
219
|
+
const response = await model.withStructuredOutput(z.object({ title: z.string() })).invoke([
|
|
220
|
+
['system', 'Generate a descriptive (8-12 word) title for this chat based on the user\'s first message.'],
|
|
219
221
|
['human', firstMessage],
|
|
220
222
|
]);
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
: response.content.filter(b => b.type === 'text').map(b => b.text).join('');
|
|
224
|
-
const cleaned = title.replace(/^["']+|["']+$/g, '').trim();
|
|
225
|
-
if (cleaned) {
|
|
226
|
-
updateChatTitle(threadId, cleaned);
|
|
223
|
+
if (response.title.trim()) {
|
|
224
|
+
updateChatTitle(threadId, response.title.trim());
|
|
227
225
|
}
|
|
228
226
|
} catch (err) {
|
|
229
227
|
console.error('[autoTitle] Failed to generate title:', err.message);
|
package/lib/ai/tools.js
CHANGED
|
@@ -60,12 +60,12 @@ const getSystemTechnicalSpecsTool = tool(
|
|
|
60
60
|
{
|
|
61
61
|
name: 'get_system_technical_specs',
|
|
62
62
|
description:
|
|
63
|
-
'Read the system architecture and technical documentation (CLAUDE.md). You MUST call this before modifying any config file (CRONS.json, TRIGGERS.json, etc.) or system infrastructure — config entries have advanced fields (per-entry LLM overrides, webhook options, etc.) that are only documented here. Also use this when you need to understand how the system works — event handler, Docker agent, API routes, database, GitHub Actions, deployment, or file structure. NOT for
|
|
63
|
+
'Read the system architecture and technical documentation (CLAUDE.md). You MUST call this before modifying any config file (CRONS.json, TRIGGERS.json, etc.) or system infrastructure — config entries have advanced fields (per-entry LLM overrides, webhook options, etc.) that are only documented here. Also use this when you need to understand how the system works — event handler, Docker agent, API routes, database, GitHub Actions, deployment, or file structure. NOT for skill creation (use get_skill_building_guide for that).',
|
|
64
64
|
schema: z.object({}),
|
|
65
65
|
}
|
|
66
66
|
);
|
|
67
67
|
|
|
68
|
-
const
|
|
68
|
+
const getSkillBuildingGuideTool = tool(
|
|
69
69
|
async () => {
|
|
70
70
|
try {
|
|
71
71
|
return fs.readFileSync(skillGuidePath, 'utf8');
|
|
@@ -74,11 +74,11 @@ const getPiSkillCreationGuideTool = tool(
|
|
|
74
74
|
}
|
|
75
75
|
},
|
|
76
76
|
{
|
|
77
|
-
name: '
|
|
77
|
+
name: 'get_skill_building_guide',
|
|
78
78
|
description:
|
|
79
|
-
'Load the guide for creating, modifying, and understanding
|
|
79
|
+
'Load the guide for creating, modifying, and understanding agent skills. You MUST call this before creating or modifying any skill — the guide contains required file structure, naming conventions, SKILL.md frontmatter format, activation steps, and testing procedures that are only documented there. Skills are lightweight bash/Node.js wrappers in `skills/` that extend what agents can do. NOT for understanding the system architecture (use get_system_technical_specs for that).',
|
|
80
80
|
schema: z.object({}),
|
|
81
81
|
}
|
|
82
82
|
);
|
|
83
83
|
|
|
84
|
-
export { createJobTool, getJobStatusTool, getSystemTechnicalSpecsTool,
|
|
84
|
+
export { createJobTool, getJobStatusTool, getSystemTechnicalSpecsTool, getSkillBuildingGuideTool };
|
package/lib/chat/actions.js
CHANGED
|
@@ -85,6 +85,20 @@ export async function deleteChat(chatId) {
|
|
|
85
85
|
return { success: true };
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Get the title of a specific chat (with ownership check).
|
|
90
|
+
* @param {string} chatId
|
|
91
|
+
* @returns {Promise<string|null>}
|
|
92
|
+
*/
|
|
93
|
+
export async function getChatTitle(chatId) {
|
|
94
|
+
const user = await requireAuth();
|
|
95
|
+
const chat = getChatById(chatId);
|
|
96
|
+
if (!chat || (chat.userId !== user.id && chat.userId !== 'telegram')) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
return chat.title;
|
|
100
|
+
}
|
|
101
|
+
|
|
88
102
|
/**
|
|
89
103
|
* Rename a chat (with ownership check).
|
|
90
104
|
* @param {string} chatId
|
|
@@ -1,8 +1,26 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useEffect, useCallback } from "react";
|
|
3
4
|
import { SidebarTrigger } from "./ui/sidebar.js";
|
|
5
|
+
import { getChatTitle } from "../actions.js";
|
|
4
6
|
function ChatHeader({ chatId }) {
|
|
5
|
-
|
|
7
|
+
const [title, setTitle] = useState(null);
|
|
8
|
+
const fetchTitle = useCallback(() => {
|
|
9
|
+
getChatTitle(chatId).then((t) => {
|
|
10
|
+
if (t && t !== "New Chat") setTitle(t);
|
|
11
|
+
}).catch(() => {
|
|
12
|
+
});
|
|
13
|
+
}, [chatId]);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
fetchTitle();
|
|
16
|
+
const handler = () => fetchTitle();
|
|
17
|
+
window.addEventListener("chatsupdated", handler);
|
|
18
|
+
return () => window.removeEventListener("chatsupdated", handler);
|
|
19
|
+
}, [fetchTitle]);
|
|
20
|
+
return /* @__PURE__ */ jsxs("header", { className: "sticky top-0 flex items-center gap-2 border-b border-border bg-background px-4 py-3 z-10", children: [
|
|
21
|
+
/* @__PURE__ */ jsx("div", { className: "md:hidden", children: /* @__PURE__ */ jsx(SidebarTrigger, {}) }),
|
|
22
|
+
title && /* @__PURE__ */ jsx("h1", { className: "text-base font-medium text-muted-foreground", children: title })
|
|
23
|
+
] });
|
|
6
24
|
}
|
|
7
25
|
export {
|
|
8
26
|
ChatHeader
|
|
@@ -1,14 +1,39 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
3
4
|
import { SidebarTrigger } from './ui/sidebar.js';
|
|
5
|
+
import { getChatTitle } from '../actions.js';
|
|
4
6
|
|
|
5
7
|
export function ChatHeader({ chatId }) {
|
|
8
|
+
const [title, setTitle] = useState(null);
|
|
9
|
+
|
|
10
|
+
const fetchTitle = useCallback(() => {
|
|
11
|
+
getChatTitle(chatId)
|
|
12
|
+
.then((t) => {
|
|
13
|
+
if (t && t !== 'New Chat') setTitle(t);
|
|
14
|
+
})
|
|
15
|
+
.catch(() => {});
|
|
16
|
+
}, [chatId]);
|
|
17
|
+
|
|
18
|
+
// Fetch on mount and whenever chats are updated (title generated by LLM)
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
fetchTitle();
|
|
21
|
+
const handler = () => fetchTitle();
|
|
22
|
+
window.addEventListener('chatsupdated', handler);
|
|
23
|
+
return () => window.removeEventListener('chatsupdated', handler);
|
|
24
|
+
}, [fetchTitle]);
|
|
25
|
+
|
|
6
26
|
return (
|
|
7
|
-
<header className="sticky top-0 flex items-center gap-2 bg-background px-
|
|
27
|
+
<header className="sticky top-0 flex items-center gap-2 border-b border-border bg-background px-4 py-3 z-10">
|
|
8
28
|
{/* Mobile-only: open sidebar sheet */}
|
|
9
29
|
<div className="md:hidden">
|
|
10
30
|
<SidebarTrigger />
|
|
11
31
|
</div>
|
|
32
|
+
{title && (
|
|
33
|
+
<h1 className="text-base font-medium text-muted-foreground">
|
|
34
|
+
{title}
|
|
35
|
+
</h1>
|
|
36
|
+
)}
|
|
12
37
|
</header>
|
|
13
38
|
);
|
|
14
39
|
}
|
|
@@ -74,7 +74,8 @@ const linkSafety = {
|
|
|
74
74
|
const TOOL_DISPLAY_NAMES = {
|
|
75
75
|
create_job: "Create Job",
|
|
76
76
|
get_job_status: "Check Job Status",
|
|
77
|
-
get_system_technical_specs: "Read
|
|
77
|
+
get_system_technical_specs: "Read Tech Docs",
|
|
78
|
+
get_skill_building_guide: "Read Skill Docs"
|
|
78
79
|
};
|
|
79
80
|
function getToolDisplayName(toolName) {
|
|
80
81
|
return TOOL_DISPLAY_NAMES[toolName] || toolName.replace(/_/g, " ");
|
|
@@ -67,7 +67,8 @@ export const linkSafety = {
|
|
|
67
67
|
const TOOL_DISPLAY_NAMES = {
|
|
68
68
|
create_job: 'Create Job',
|
|
69
69
|
get_job_status: 'Check Job Status',
|
|
70
|
-
get_system_technical_specs: 'Read
|
|
70
|
+
get_system_technical_specs: 'Read Tech Docs',
|
|
71
|
+
get_skill_building_guide: 'Read Skill Docs',
|
|
71
72
|
};
|
|
72
73
|
|
|
73
74
|
function getToolDisplayName(toolName) {
|
|
@@ -6,7 +6,8 @@ import { cn } from "../utils.js";
|
|
|
6
6
|
const TOOL_DISPLAY_NAMES = {
|
|
7
7
|
create_job: "Create Job",
|
|
8
8
|
get_job_status: "Check Job Status",
|
|
9
|
-
get_system_technical_specs: "Read
|
|
9
|
+
get_system_technical_specs: "Read Tech Docs",
|
|
10
|
+
get_skill_building_guide: "Read Skill Docs"
|
|
10
11
|
};
|
|
11
12
|
function getToolDisplayName(toolName) {
|
|
12
13
|
return TOOL_DISPLAY_NAMES[toolName] || toolName.replace(/_/g, " ");
|
|
@@ -7,7 +7,8 @@ import { cn } from '../utils.js';
|
|
|
7
7
|
const TOOL_DISPLAY_NAMES = {
|
|
8
8
|
create_job: 'Create Job',
|
|
9
9
|
get_job_status: 'Check Job Status',
|
|
10
|
-
get_system_technical_specs: 'Read
|
|
10
|
+
get_system_technical_specs: 'Read Tech Docs',
|
|
11
|
+
get_skill_building_guide: 'Read Skill Docs',
|
|
11
12
|
};
|
|
12
13
|
|
|
13
14
|
function getToolDisplayName(toolName) {
|
package/lib/paths.js
CHANGED
|
@@ -19,9 +19,9 @@ export const eventHandlerMd = path.join(PROJECT_ROOT, 'config', 'EVENT_HANDLER.m
|
|
|
19
19
|
export const jobSummaryMd = path.join(PROJECT_ROOT, 'config', 'JOB_SUMMARY.md');
|
|
20
20
|
export const soulMd = path.join(PROJECT_ROOT, 'config', 'SOUL.md');
|
|
21
21
|
export const claudeMd = path.join(PROJECT_ROOT, 'CLAUDE.md');
|
|
22
|
-
export const skillGuidePath = path.join(PROJECT_ROOT, 'config', '
|
|
22
|
+
export const skillGuidePath = path.join(PROJECT_ROOT, 'config', 'SKILL_BUILDING_GUIDE.md');
|
|
23
23
|
|
|
24
|
-
//
|
|
24
|
+
// Active skills (resolves through .pi/skills → skills/active symlink)
|
|
25
25
|
export const piSkillsDir = path.join(PROJECT_ROOT, '.pi', 'skills');
|
|
26
26
|
|
|
27
27
|
// Working directories for command-type actions
|
package/lib/tools/create-job.js
CHANGED
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
import { v4 as uuidv4 } from 'uuid';
|
|
2
|
+
import { z } from 'zod';
|
|
2
3
|
import { githubApi } from './github.js';
|
|
3
4
|
import { createModel } from '../ai/model.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Generate a short descriptive title for a job using the LLM.
|
|
8
|
+
* Uses structured output to avoid thinking-token leaks with extended-thinking models.
|
|
7
9
|
* @param {string} jobDescription - The full job description
|
|
8
10
|
* @returns {Promise<string>} ~10 word title
|
|
9
11
|
*/
|
|
10
12
|
async function generateJobTitle(jobDescription) {
|
|
11
13
|
try {
|
|
12
14
|
const model = await createModel({ maxTokens: 100 });
|
|
13
|
-
const response = await model.invoke([
|
|
14
|
-
['system', 'Generate a descriptive ~10 word title for this agent job. The title should clearly describe what the job will do.
|
|
15
|
+
const response = await model.withStructuredOutput(z.object({ title: z.string() })).invoke([
|
|
16
|
+
['system', 'Generate a descriptive ~10 word title for this agent job. The title should clearly describe what the job will do.'],
|
|
15
17
|
['human', jobDescription],
|
|
16
18
|
]);
|
|
17
|
-
|
|
18
|
-
? response.content
|
|
19
|
-
: response.content.filter(b => b.type === 'text').map(b => b.text).join('');
|
|
20
|
-
return text.replace(/^["']+|["']+$/g, '').trim() || jobDescription.slice(0, 80);
|
|
19
|
+
return response.title.trim() || jobDescription.slice(0, 80);
|
|
21
20
|
} catch {
|
|
22
21
|
// Fallback: first line, truncated
|
|
23
22
|
const firstLine = jobDescription.split('\n').find(l => l.trim()) || jobDescription;
|
package/package.json
CHANGED
package/setup/lib/prompts.mjs
CHANGED
|
@@ -70,13 +70,19 @@ export async function promptForProvider() {
|
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
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
|
|
73
76
|
*/
|
|
74
|
-
export async function promptForModel(providerKey) {
|
|
77
|
+
export async function promptForModel(providerKey, { defaultModelId } = {}) {
|
|
75
78
|
const provider = PROVIDERS[providerKey];
|
|
76
|
-
const options = provider.models.map((m) =>
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
const options = provider.models.map((m) => {
|
|
80
|
+
const isRecommended = defaultModelId ? m.id === defaultModelId : m.default;
|
|
81
|
+
return {
|
|
82
|
+
label: isRecommended ? `${m.name} (recommended)` : m.name,
|
|
83
|
+
value: m.id,
|
|
84
|
+
};
|
|
85
|
+
});
|
|
80
86
|
options.push({ label: 'Custom (enter model ID)', value: '__custom__' });
|
|
81
87
|
|
|
82
88
|
const model = handleCancel(await clack.select({
|
package/setup/lib/targets.mjs
CHANGED
|
@@ -24,9 +24,6 @@ export const CONFIG_TARGETS = {
|
|
|
24
24
|
CUSTOM_API_KEY: { env: true, secret: 'AGENT_CUSTOM_API_KEY' },
|
|
25
25
|
OPENAI_BASE_URL: { env: true, variable: true },
|
|
26
26
|
|
|
27
|
-
AGENT_LLM_PROVIDER: { variable: true },
|
|
28
|
-
AGENT_LLM_MODEL: { variable: true },
|
|
29
|
-
|
|
30
27
|
CLAUDE_CODE_OAUTH_TOKEN: { env: true, secret: 'AGENT_CLAUDE_CODE_OAUTH_TOKEN' },
|
|
31
28
|
AGENT_BACKEND: { env: true, variable: true },
|
|
32
29
|
|
package/setup/setup.mjs
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
generateWebhookSecret,
|
|
33
33
|
getPATCreationURL,
|
|
34
34
|
setSecret,
|
|
35
|
+
setVariable,
|
|
35
36
|
} from './lib/github.mjs';
|
|
36
37
|
import { writeModelsJson } from './lib/auth.mjs';
|
|
37
38
|
import { loadEnvFile } from './lib/env.mjs';
|
|
@@ -50,7 +51,7 @@ async function main() {
|
|
|
50
51
|
console.log(chalk.cyan(logo));
|
|
51
52
|
clack.intro('Interactive Setup Wizard');
|
|
52
53
|
|
|
53
|
-
const TOTAL_STEPS =
|
|
54
|
+
const TOTAL_STEPS = 8;
|
|
54
55
|
let currentStep = 0;
|
|
55
56
|
|
|
56
57
|
// Load existing .env (always exists after init — seed .env has AUTH_SECRET etc.)
|
|
@@ -422,7 +423,7 @@ async function main() {
|
|
|
422
423
|
}
|
|
423
424
|
} else {
|
|
424
425
|
const providerConfig = PROVIDERS[chatProvider];
|
|
425
|
-
chatModel = await promptForModel(chatProvider);
|
|
426
|
+
chatModel = await promptForModel(chatProvider, { defaultModelId: 'claude-sonnet-4-6' });
|
|
426
427
|
const chatApiKey = await promptForApiKey(chatProvider);
|
|
427
428
|
collected[providerConfig.envKey] = chatApiKey;
|
|
428
429
|
|
|
@@ -495,8 +496,6 @@ async function main() {
|
|
|
495
496
|
});
|
|
496
497
|
if (clack.isCancel(customModel)) { clack.cancel('Setup cancelled.'); process.exit(0); }
|
|
497
498
|
agentModel = customModel;
|
|
498
|
-
collected.AGENT_LLM_PROVIDER = agentProvider;
|
|
499
|
-
collected.AGENT_LLM_MODEL = agentModel;
|
|
500
499
|
collected.RUNS_ON = 'self-hosted';
|
|
501
500
|
} else {
|
|
502
501
|
const agentProviderConfig = PROVIDERS[agentProvider];
|
|
@@ -511,9 +510,6 @@ async function main() {
|
|
|
511
510
|
clack.log.success(`Agent ${agentProviderConfig.name} key added (${maskSecret(agentApiKey)})`);
|
|
512
511
|
}
|
|
513
512
|
|
|
514
|
-
collected.AGENT_LLM_PROVIDER = agentProvider;
|
|
515
|
-
collected.AGENT_LLM_MODEL = agentModel;
|
|
516
|
-
|
|
517
513
|
// OAuth prompt — only when agent provider is Anthropic
|
|
518
514
|
if (agentProviderConfig.oauthSupported) {
|
|
519
515
|
let skipOAuth = false;
|
|
@@ -601,15 +597,15 @@ async function main() {
|
|
|
601
597
|
clack.log.success(`Brave Search key added (${maskSecret(braveKey)})`);
|
|
602
598
|
|
|
603
599
|
// Enable brave-search skill symlink
|
|
604
|
-
const braveSymlink = path.join(process.cwd(), '
|
|
600
|
+
const braveSymlink = path.join(process.cwd(), 'skills', 'active', 'brave-search');
|
|
605
601
|
if (!fs.existsSync(braveSymlink)) {
|
|
606
602
|
fs.mkdirSync(path.dirname(braveSymlink), { recursive: true });
|
|
607
|
-
createDirLink('
|
|
603
|
+
createDirLink('../brave-search', braveSymlink);
|
|
608
604
|
clack.log.success('Enabled brave-search skill');
|
|
609
605
|
|
|
610
606
|
// Commit and push the symlink so the Docker agent can use it
|
|
611
607
|
try {
|
|
612
|
-
execSync('git add
|
|
608
|
+
execSync('git add skills/active/brave-search', { stdio: 'ignore' });
|
|
613
609
|
execSync('git commit -m "enable brave-search skill [no ci]"', { stdio: 'ignore' });
|
|
614
610
|
const remote = execSync('git remote get-url origin', { encoding: 'utf-8' }).trim();
|
|
615
611
|
const authedUrl = remote.replace('https://github.com/', `https://x-access-token:${pat}@github.com/`);
|
|
@@ -685,6 +681,15 @@ async function main() {
|
|
|
685
681
|
|
|
686
682
|
const report = await syncConfig(env, collected, { owner, repo });
|
|
687
683
|
|
|
684
|
+
// If agent uses a different model/provider, overwrite the GitHub variable
|
|
685
|
+
// (.env keeps chatModel for the event handler, GitHub variable gets agentModel for jobs)
|
|
686
|
+
if (agentModel && agentModel !== chatModel) {
|
|
687
|
+
await setVariable(owner, repo, 'LLM_MODEL', agentModel);
|
|
688
|
+
}
|
|
689
|
+
if (agentProvider && agentProvider !== chatProvider) {
|
|
690
|
+
await setVariable(owner, repo, 'LLM_PROVIDER', agentProvider);
|
|
691
|
+
}
|
|
692
|
+
|
|
688
693
|
// Set agent API key as a separate GitHub secret (not in .env)
|
|
689
694
|
if (agentApiKeyInfo) {
|
|
690
695
|
const s2 = clack.spinner();
|
|
@@ -700,26 +705,14 @@ async function main() {
|
|
|
700
705
|
|
|
701
706
|
clack.log.info('Your agent includes a web chat interface at your APP_URL.');
|
|
702
707
|
|
|
703
|
-
// ─── Step 6: Build
|
|
704
|
-
clack.log.step(`[${++currentStep}/${TOTAL_STEPS}] Build
|
|
705
|
-
clack.log.info('Now we\'ll build your agent\'s web interface. Then you\'ll start the server with Docker so it can receive webhooks and serve the chat UI.');
|
|
706
|
-
|
|
707
|
-
// Check if server is already running
|
|
708
|
-
let serverAlreadyRunning = false;
|
|
709
|
-
try {
|
|
710
|
-
await fetch('http://localhost:80/api/ping', {
|
|
711
|
-
method: 'GET',
|
|
712
|
-
signal: AbortSignal.timeout(3000),
|
|
713
|
-
});
|
|
714
|
-
serverAlreadyRunning = true;
|
|
715
|
-
} catch {
|
|
716
|
-
// Server not reachable
|
|
717
|
-
}
|
|
708
|
+
// ─── Step 6: Build ──────────────────────────────────────────────────
|
|
709
|
+
clack.log.step(`[${++currentStep}/${TOTAL_STEPS}] Build`);
|
|
718
710
|
|
|
719
711
|
// Helper: run build with retry on failure
|
|
720
712
|
async function runBuildWithRetry() {
|
|
721
713
|
for (let attempt = 1; attempt <= 2; attempt++) {
|
|
722
714
|
try {
|
|
715
|
+
fs.rmSync(path.join(process.cwd(), '.next'), { recursive: true, force: true });
|
|
723
716
|
execSync('npm run build', { stdio: 'inherit' });
|
|
724
717
|
clack.log.success('Build complete');
|
|
725
718
|
return true;
|
|
@@ -736,43 +729,66 @@ async function main() {
|
|
|
736
729
|
clack.log.error(
|
|
737
730
|
'Cannot continue without a successful build.\n' +
|
|
738
731
|
' Fix the error above, then run:\n\n' +
|
|
739
|
-
' npm run build
|
|
740
|
-
' docker compose up -d'
|
|
732
|
+
' npm run build'
|
|
741
733
|
);
|
|
742
734
|
process.exit(1);
|
|
743
735
|
}
|
|
744
736
|
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
737
|
+
const hasExistingBuild = fs.existsSync(path.join(process.cwd(), '.next'));
|
|
738
|
+
|
|
739
|
+
if (hasExistingBuild) {
|
|
740
|
+
if (await confirm('Existing build found. Rebuild?')) {
|
|
741
|
+
clack.log.info('Building Next.js...');
|
|
749
742
|
await runBuildWithRetry();
|
|
743
|
+
} else {
|
|
744
|
+
clack.log.info('Skipping build');
|
|
750
745
|
}
|
|
751
746
|
} else {
|
|
752
747
|
clack.log.info('Building Next.js...');
|
|
753
748
|
await runBuildWithRetry();
|
|
749
|
+
}
|
|
754
750
|
|
|
755
|
-
|
|
751
|
+
// ─── Step 7: Start Server ─────────────────────────────────────────────
|
|
752
|
+
clack.log.step(`[${++currentStep}/${TOTAL_STEPS}] Start Server`);
|
|
756
753
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
754
|
+
let serverRunning = false;
|
|
755
|
+
try {
|
|
756
|
+
await fetch('http://localhost:80/api/ping', {
|
|
757
|
+
method: 'GET',
|
|
758
|
+
signal: AbortSignal.timeout(3000),
|
|
759
|
+
});
|
|
760
|
+
serverRunning = true;
|
|
761
|
+
} catch {
|
|
762
|
+
// Server not reachable
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
if (serverRunning) {
|
|
766
|
+
if (await confirm('Server is already running. Restart?')) {
|
|
767
|
+
const restartSpinner = clack.spinner();
|
|
768
|
+
restartSpinner.start('Restarting server...');
|
|
762
769
|
try {
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
signal: AbortSignal.timeout(5000),
|
|
766
|
-
});
|
|
767
|
-
serverSpinner.stop('Server is running');
|
|
768
|
-
serverReachable = true;
|
|
770
|
+
execSync('docker compose down && docker compose up -d', { stdio: 'ignore' });
|
|
771
|
+
restartSpinner.stop('Server restarted');
|
|
769
772
|
} catch {
|
|
770
|
-
|
|
773
|
+
restartSpinner.stop('Failed to restart server');
|
|
774
|
+
clack.log.warn('Run manually: docker compose down && docker compose up -d');
|
|
771
775
|
}
|
|
772
776
|
}
|
|
777
|
+
} else {
|
|
778
|
+
const startSpinner = clack.spinner();
|
|
779
|
+
startSpinner.start('Starting server...');
|
|
780
|
+
try {
|
|
781
|
+
execSync('docker compose up -d', { stdio: 'ignore' });
|
|
782
|
+
startSpinner.stop('Server started');
|
|
783
|
+
} catch {
|
|
784
|
+
startSpinner.stop('Failed to start server');
|
|
785
|
+
clack.log.warn('Run manually: docker compose up -d');
|
|
786
|
+
}
|
|
773
787
|
}
|
|
774
788
|
|
|
775
|
-
|
|
789
|
+
clack.log.info(`Server starting — visit ${appUrl} (may take 10-20 seconds to load)`);
|
|
790
|
+
|
|
791
|
+
// ─── Step 8: Summary ─────────────────────────────────────────────────
|
|
776
792
|
clack.log.step(`[${++currentStep}/${TOTAL_STEPS}] Setup Complete!`);
|
|
777
793
|
|
|
778
794
|
const chatProviderLabel = chatProvider === 'custom' ? 'Local (OpenAI Compatible API)' : PROVIDERS[chatProvider].label;
|
|
@@ -781,18 +797,15 @@ async function main() {
|
|
|
781
797
|
summary += `Repository: ${owner}/${repo}\n`;
|
|
782
798
|
summary += `App URL: ${appUrl}\n`;
|
|
783
799
|
|
|
784
|
-
if (agentProvider
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
summary += `Chat LLM: ${chatProviderLabel} (${chatModel})\n`;
|
|
792
|
-
summary += `Agent LLM: ${chatProviderLabel} (${agentModel})\n`;
|
|
800
|
+
if (agentProvider || agentModel) {
|
|
801
|
+
const agentProviderLabel = agentProvider
|
|
802
|
+
? (agentProvider === 'custom' ? 'Local (OpenAI Compatible API)' : PROVIDERS[agentProvider].label)
|
|
803
|
+
: chatProviderLabel;
|
|
804
|
+
const agentModelDisplay = agentModel || chatModel;
|
|
805
|
+
summary += `Chat LLM: ${chatProviderLabel} (${chatModel}) [.env]\n`;
|
|
806
|
+
summary += `Agent LLM: ${agentProviderLabel} (${agentModelDisplay}) [GitHub var]\n`;
|
|
793
807
|
} else {
|
|
794
|
-
|
|
795
|
-
summary += `Agent LLM: ${chatProviderLabel} (${chatModel})\n`;
|
|
808
|
+
summary += `LLM: ${chatProviderLabel} (${chatModel})\n`;
|
|
796
809
|
}
|
|
797
810
|
|
|
798
811
|
if (collected.AGENT_BACKEND) {
|
|
@@ -809,11 +822,7 @@ async function main() {
|
|
|
809
822
|
clack.log.info(`GitHub variables set: ${report.variables.join(', ')}`);
|
|
810
823
|
}
|
|
811
824
|
|
|
812
|
-
clack.outro(
|
|
813
|
-
`Start your server:\n\n` +
|
|
814
|
-
` docker compose up -d\n\n` +
|
|
815
|
-
`Then chat with your agent at ${appUrl}`
|
|
816
|
-
);
|
|
825
|
+
clack.outro(`Chat with your agent at ${appUrl}`);
|
|
817
826
|
}
|
|
818
827
|
|
|
819
828
|
main().catch((error) => {
|
|
@@ -52,8 +52,8 @@ jobs:
|
|
|
52
52
|
ALL_SECRETS: ${{ toJson(secrets) }}
|
|
53
53
|
JOB_IMAGE_URL: ${{ vars.JOB_IMAGE_URL }}
|
|
54
54
|
THEPOPEBOT_VERSION: ${{ steps.version.outputs.tag }}
|
|
55
|
-
LLM_MODEL: ${{ steps.job-config.outputs.llm_model || vars.
|
|
56
|
-
LLM_PROVIDER: ${{ steps.job-config.outputs.llm_provider || vars.
|
|
55
|
+
LLM_MODEL: ${{ steps.job-config.outputs.llm_model || vars.LLM_MODEL }}
|
|
56
|
+
LLM_PROVIDER: ${{ steps.job-config.outputs.llm_provider || vars.LLM_PROVIDER }}
|
|
57
57
|
OPENAI_BASE_URL: ${{ vars.OPENAI_BASE_URL }}
|
|
58
58
|
AGENT_BACKEND: ${{ steps.job-config.outputs.agent_backend || vars.AGENT_BACKEND || '' }}
|
|
59
59
|
run: |
|
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
*.key
|
|
7
7
|
|
|
8
8
|
.claude/*
|
|
9
|
+
!.claude/skills
|
|
9
10
|
|
|
10
11
|
# Pi system prompt (generated at runtime from SOUL.md)
|
|
11
12
|
.pi/SYSTEM.md
|
|
12
13
|
|
|
13
|
-
#
|
|
14
|
-
|
|
14
|
+
# Skills dependencies (installed at runtime in Docker for correct arch)
|
|
15
|
+
skills/*/node_modules/
|
|
15
16
|
|
|
16
17
|
# Node
|
|
17
18
|
node_modules/
|
|
@@ -31,14 +31,16 @@ project-root/
|
|
|
31
31
|
│ ├── AGENT.md # Agent runtime environment docs
|
|
32
32
|
│ ├── JOB_SUMMARY.md # Prompt for summarizing completed jobs
|
|
33
33
|
│ ├── HEARTBEAT.md # Self-monitoring / heartbeat behavior
|
|
34
|
-
│ ├──
|
|
34
|
+
│ ├── SKILL_BUILDING_GUIDE.md # Guide for building agent skills
|
|
35
35
|
│ ├── CRONS.json # Scheduled job definitions
|
|
36
36
|
│ └── TRIGGERS.json # Webhook trigger definitions
|
|
37
37
|
│
|
|
38
38
|
├── .github/workflows/ # GitHub Actions
|
|
39
39
|
├── docker/ # Docker files (job agent + event handler)
|
|
40
|
-
├──
|
|
41
|
-
|
|
40
|
+
├── skills/ # All available agent skills
|
|
41
|
+
│ └── active/ # Symlinks to active skills (shared by Pi + Claude Code)
|
|
42
|
+
├── .pi/skills → skills/active # Pi reads skills from here
|
|
43
|
+
├── .claude/skills → skills/active # Claude Code reads skills from here
|
|
42
44
|
├── cron/ # Scripts for command-type cron actions
|
|
43
45
|
├── triggers/ # Scripts for command-type trigger actions
|
|
44
46
|
├── logs/ # Per-job output (logs/<JOB_ID>/job.md + session .jsonl)
|
|
@@ -290,9 +292,9 @@ SQLite via Drizzle ORM at `data/thepopebot.sqlite`. Auto-initialized and auto-mi
|
|
|
290
292
|
|
|
291
293
|
## Customization
|
|
292
294
|
|
|
293
|
-
User-editable config files in `config/`: `SOUL.md` (personality), `EVENT_HANDLER.md` (LLM system prompt), `AGENT.md` (runtime docs), `JOB_SUMMARY.md` (job summaries), `HEARTBEAT.md` (self-monitoring), `
|
|
295
|
+
User-editable config files in `config/`: `SOUL.md` (personality), `EVENT_HANDLER.md` (LLM system prompt), `AGENT.md` (runtime docs), `JOB_SUMMARY.md` (job summaries), `HEARTBEAT.md` (self-monitoring), `SKILL_BUILDING_GUIDE.md` (skill guide), `CRONS.json` (scheduled jobs), `TRIGGERS.json` (webhook triggers).
|
|
294
296
|
|
|
295
|
-
Skills in `
|
|
297
|
+
Skills in `skills/` are activated by symlinking into `skills/active/`. Both `.pi/skills` and `.claude/skills` point to `skills/active/`. Scripts for command-type actions go in `cron/` and `triggers/`.
|
|
296
298
|
|
|
297
299
|
### Markdown includes and variables
|
|
298
300
|
|
|
@@ -302,4 +304,4 @@ Config markdown files support includes and built-in variables (processed by the
|
|
|
302
304
|
|--------|-------------|
|
|
303
305
|
| `{{ filepath.md }}` | Include another file (relative to project root, recursive with circular detection) |
|
|
304
306
|
| `{{datetime}}` | Current ISO timestamp |
|
|
305
|
-
| `{{skills}}` | Dynamic bullet list of active skill descriptions from
|
|
307
|
+
| `{{skills}}` | Dynamic bullet list of active skill descriptions from `skills/active/*/SKILL.md` frontmatter — never hardcode skill names, this is resolved at runtime |
|
|
@@ -10,7 +10,7 @@ You have four tools:
|
|
|
10
10
|
- **`create_job`** — dispatch a job for autonomous execution
|
|
11
11
|
- **`get_job_status`** — check on running or completed jobs
|
|
12
12
|
- **`get_system_technical_specs`** — read the system architecture docs (event handler, Docker agent, APIs, config, deployment). Use before planning jobs that modify system configuration.
|
|
13
|
-
- **`
|
|
13
|
+
- **`get_skill_building_guide`** — load the skill building guide (skill format, examples, activation, testing). Use when discussing or creating skills with the user.
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
@@ -29,17 +29,17 @@ These 7 tools are all Pi needs to accomplish most tasks. It can write code, inst
|
|
|
29
29
|
### What Pi can do with these tools
|
|
30
30
|
|
|
31
31
|
- **Self-modification** — update config files in `config/` (CRONS.json, TRIGGERS.json, SOUL.md, EVENT_HANDLER.md, AGENT.md, etc.). Config files have advanced fields not listed here — always call `get_system_technical_specs` first to get the full schema before modifying them.
|
|
32
|
-
- **Create new skills** — build new tools in `
|
|
32
|
+
- **Create new skills** — build new tools in `skills/` and activate them with symlinks in `skills/active/`
|
|
33
33
|
- **Code changes** — add features, fix bugs, refactor, build entire applications
|
|
34
34
|
- **Git** — commits changes, creates PRs automatically
|
|
35
35
|
|
|
36
|
-
### Active skills
|
|
36
|
+
### Active skills
|
|
37
37
|
|
|
38
|
-
Skills are lightweight wrappers (usually bash scripts) that give
|
|
38
|
+
Skills are lightweight wrappers (usually bash scripts) that give the agent access to external services. The agent reads the skill documentation, then invokes them via bash.
|
|
39
39
|
|
|
40
40
|
{{skills}}
|
|
41
41
|
|
|
42
|
-
If no skill exists for what the user needs,
|
|
42
|
+
If no skill exists for what the user needs, the agent can build more.
|
|
43
43
|
|
|
44
44
|
### Writing good job descriptions
|
|
45
45
|
|
|
@@ -94,9 +94,9 @@ The job description text becomes Pi's task prompt:
|
|
|
94
94
|
|
|
95
95
|
## Skills
|
|
96
96
|
|
|
97
|
-
Skills extend what
|
|
97
|
+
Skills extend what the agent can do — they're lightweight wrappers (usually bash scripts) that give the agent access to external services. If a user wants something no current skill covers, suggest creating one.
|
|
98
98
|
|
|
99
|
-
When discussing or creating skills, use `
|
|
99
|
+
When discussing or creating skills, use `get_skill_building_guide` to load the skill building guide. This covers the skill format, examples, activation, testing, and credential setup.
|
|
100
100
|
|
|
101
101
|
### Credential setup (handle in conversation, before creating the job)
|
|
102
102
|
|
|
@@ -200,12 +200,12 @@ Browser scraping:
|
|
|
200
200
|
> Navigate to https://example.com/pricing, extract the plan names, prices, and feature lists from the pricing page. Save the data as JSON at `data/pricing.json`.
|
|
201
201
|
|
|
202
202
|
New skill creation:
|
|
203
|
-
> Create a new skill at `
|
|
203
|
+
> Create a new skill at `skills/slack-post/`:
|
|
204
204
|
>
|
|
205
|
-
> 1. Create `SKILL.md` with frontmatter (name: slack-post, description: "Post messages to Slack channels via incoming webhook.") and usage docs referencing `
|
|
205
|
+
> 1. Create `SKILL.md` with frontmatter (name: slack-post, description: "Post messages to Slack channels via incoming webhook.") and usage docs referencing `skills/slack-post/post.sh <message>`
|
|
206
206
|
> 2. Create `post.sh` — bash script that takes a message argument, sends it to the Slack webhook URL via curl using $SLACK_WEBHOOK_URL. Make it executable.
|
|
207
|
-
> 3. Activate: `ln -s
|
|
208
|
-
> 4. Test: run `post.sh "test message from thepopebot"` and verify successful delivery. Fix any issues before committing.
|
|
207
|
+
> 3. Activate: `ln -s ../slack-post skills/active/slack-post`
|
|
208
|
+
> 4. Test: run `skills/slack-post/post.sh "test message from thepopebot"` and verify successful delivery. Fix any issues before committing.
|
|
209
209
|
|
|
210
210
|
---
|
|
211
211
|
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
# Skill
|
|
1
|
+
# Skill Building Guide
|
|
2
2
|
|
|
3
3
|
## What is a skill?
|
|
4
4
|
|
|
5
|
-
Skills are lightweight wrappers that extend
|
|
5
|
+
Skills are lightweight wrappers that extend agent abilities. They live in `skills/<skill-name>/` and are activated by symlinking into `skills/active/`. Both Pi and Claude Code discover skills from the same shared directory.
|
|
6
6
|
|
|
7
7
|
## Skill structure
|
|
8
8
|
|
|
9
9
|
- **`SKILL.md`** (required) — YAML frontmatter + markdown documentation
|
|
10
|
-
- **Scripts** (optional) — prefer bash (.sh)
|
|
10
|
+
- **Scripts** (optional) — prefer bash (.sh) for simplicity
|
|
11
11
|
- **`package.json`** (optional) — only if Node.js dependencies are truly needed
|
|
12
12
|
|
|
13
13
|
## SKILL.md format
|
|
14
14
|
|
|
15
|
-
The `description` from frontmatter appears in the
|
|
16
|
-
|
|
15
|
+
The `description` from frontmatter appears in the system prompt under "Active skills."
|
|
16
|
+
Use project-root-relative paths in documentation (e.g., `skills/<skill-name>/script.sh`).
|
|
17
17
|
|
|
18
18
|
```
|
|
19
19
|
---
|
|
@@ -26,7 +26,7 @@ description: One sentence describing what the skill does and when to use it.
|
|
|
26
26
|
## Usage
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
|
-
|
|
29
|
+
skills/skill-name/script.sh <args>
|
|
30
30
|
```
|
|
31
31
|
```
|
|
32
32
|
|
|
@@ -34,7 +34,7 @@ description: One sentence describing what the skill does and when to use it.
|
|
|
34
34
|
|
|
35
35
|
The built-in `transcribe` skill — a SKILL.md and a single bash script:
|
|
36
36
|
|
|
37
|
-
**
|
|
37
|
+
**skills/transcribe/SKILL.md:**
|
|
38
38
|
```
|
|
39
39
|
---
|
|
40
40
|
name: transcribe
|
|
@@ -50,11 +50,11 @@ Requires GROQ_API_KEY environment variable.
|
|
|
50
50
|
|
|
51
51
|
## Usage
|
|
52
52
|
```bash
|
|
53
|
-
|
|
53
|
+
skills/transcribe/transcribe.sh <audio-file>
|
|
54
54
|
```
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
**
|
|
57
|
+
**skills/transcribe/transcribe.sh:**
|
|
58
58
|
```bash
|
|
59
59
|
#!/bin/bash
|
|
60
60
|
if [ -z "$1" ]; then echo "Usage: transcribe.sh <audio-file>"; exit 1; fi
|
|
@@ -68,18 +68,18 @@ curl -s -X POST "https://api.groq.com/openai/v1/audio/transcriptions" \
|
|
|
68
68
|
|
|
69
69
|
## Example: Skill with Node.js dependencies
|
|
70
70
|
|
|
71
|
-
The built-in `brave-search` skill uses Node.js for HTML parsing (jsdom, readability, turndown). It has a `package.json` and `.js` scripts.
|
|
71
|
+
The built-in `brave-search` skill uses Node.js for HTML parsing (jsdom, readability, turndown). It has a `package.json` and `.js` scripts. Dependencies are installed automatically in Docker. Use this pattern only when bash + curl isn't sufficient.
|
|
72
72
|
|
|
73
73
|
## Activation
|
|
74
74
|
|
|
75
75
|
After creating skill files, symlink to activate:
|
|
76
76
|
```bash
|
|
77
|
-
ln -s
|
|
77
|
+
ln -s ../skill-name skills/active/skill-name
|
|
78
78
|
```
|
|
79
79
|
|
|
80
80
|
## Always build AND test in the same job
|
|
81
81
|
|
|
82
|
-
Tell
|
|
82
|
+
Tell the agent to test the skill with real input after creating it and fix any issues before committing. Don't create untested skills.
|
|
83
83
|
|
|
84
84
|
## Credential setup
|
|
85
85
|
|
|
@@ -5,6 +5,10 @@ RUN apt-get update && apt-get install -y \
|
|
|
5
5
|
jq \
|
|
6
6
|
curl \
|
|
7
7
|
procps \
|
|
8
|
+
libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 \
|
|
9
|
+
libcups2 libdrm2 libdbus-1-3 libxkbcommon0 \
|
|
10
|
+
libatspi2.0-0 libxcomposite1 libxdamage1 libxfixes3 \
|
|
11
|
+
libxrandr2 libgbm1 libasound2 libpango-1.0-0 libcairo2 \
|
|
8
12
|
&& rm -rf /var/lib/apt/lists/*
|
|
9
13
|
|
|
10
14
|
# Install GitHub CLI
|
|
@@ -41,6 +41,23 @@ fi
|
|
|
41
41
|
|
|
42
42
|
cd /job
|
|
43
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 puppeteer installed it (needed by browser-tools skill)
|
|
53
|
+
CHROME_PID=""
|
|
54
|
+
CHROME_BIN=$(find /home/agent/.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
|
+
|
|
44
61
|
# Setup logs
|
|
45
62
|
LOG_DIR="/job/logs/${JOB_ID}"
|
|
46
63
|
mkdir -p "${LOG_DIR}"
|
|
@@ -70,11 +87,18 @@ PROMPT="
|
|
|
70
87
|
|
|
71
88
|
${JOB_DESCRIPTION}"
|
|
72
89
|
|
|
90
|
+
# Build --model flag if LLM_MODEL is set
|
|
91
|
+
MODEL_FLAG=""
|
|
92
|
+
if [ -n "$LLM_MODEL" ]; then
|
|
93
|
+
MODEL_FLAG="--model $LLM_MODEL"
|
|
94
|
+
fi
|
|
95
|
+
|
|
73
96
|
# Run Claude Code — capture exit code instead of letting set -e kill the script
|
|
74
97
|
# stream-json gives the full conversation trace (thinking, tool calls, results)
|
|
75
98
|
# similar to Pi's .jsonl session logs
|
|
76
99
|
set +e
|
|
77
100
|
claude -p "$PROMPT" \
|
|
101
|
+
$MODEL_FLAG \
|
|
78
102
|
--append-system-prompt-file "$SYSTEM_PROMPT_FILE" \
|
|
79
103
|
--dangerously-skip-permissions \
|
|
80
104
|
--verbose \
|
|
@@ -98,6 +122,11 @@ fi
|
|
|
98
122
|
git push origin
|
|
99
123
|
set -e
|
|
100
124
|
|
|
125
|
+
# Cleanup Chrome
|
|
126
|
+
if [ -n "$CHROME_PID" ]; then
|
|
127
|
+
kill $CHROME_PID 2>/dev/null || true
|
|
128
|
+
fi
|
|
129
|
+
|
|
101
130
|
# Create PR (auto-merge handled by GitHub Actions workflow)
|
|
102
131
|
gh pr create --title "🤖 Agent Job: ${TITLE}" --body "${JOB_DESCRIPTION}" --base main || true
|
|
103
132
|
|
|
@@ -41,8 +41,8 @@ cd /job
|
|
|
41
41
|
# Create temp directory for agent use (gitignored via tmp/)
|
|
42
42
|
mkdir -p /job/tmp
|
|
43
43
|
|
|
44
|
-
# Install npm deps for
|
|
45
|
-
for skill_dir in /job
|
|
44
|
+
# Install npm deps for active skills (native deps need correct Linux arch)
|
|
45
|
+
for skill_dir in /job/skills/active/*/; do
|
|
46
46
|
if [ -f "${skill_dir}package.json" ]; then
|
|
47
47
|
echo "Installing skill deps: $(basename "$skill_dir")"
|
|
48
48
|
(cd "$skill_dir" && npm install --omit=dev --no-package-lock)
|
|
@@ -95,10 +95,10 @@ description: Short description shown to agent
|
|
|
95
95
|
# Instructions
|
|
96
96
|
|
|
97
97
|
Detailed instructions here...
|
|
98
|
-
Helper files available at:
|
|
98
|
+
Helper files available at: skills/skill-name/
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
Skills use project-root-relative paths (e.g., `skills/brave-search/search.js`).
|
|
102
102
|
|
|
103
103
|
## Requirements
|
|
104
104
|
|
|
@@ -20,20 +20,20 @@ Requires a Brave Search API account with a free subscription. A credit card is r
|
|
|
20
20
|
```
|
|
21
21
|
5. Install dependencies (run once):
|
|
22
22
|
```bash
|
|
23
|
-
cd
|
|
23
|
+
cd skills/brave-search
|
|
24
24
|
npm install
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
## Search
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
37
|
```
|
|
38
38
|
|
|
39
39
|
### Options
|
|
@@ -51,7 +51,7 @@ Requires a Brave Search API account with a free subscription. A credit card is r
|
|
|
51
51
|
## Extract Page Content
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
|
-
|
|
54
|
+
skills/brave-search/content.js https://example.com/article
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
Fetches a URL and extracts readable content as markdown.
|
|
@@ -12,15 +12,15 @@ Chrome DevTools Protocol tools for agent-assisted web automation. These tools co
|
|
|
12
12
|
Run once before first use:
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
cd
|
|
15
|
+
cd skills/browser-tools
|
|
16
16
|
npm install
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
## Start Chrome
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
skills/browser-tools/browser-start.js # Fresh profile
|
|
23
|
+
skills/browser-tools/browser-start.js --profile # Copy user's profile (cookies, logins)
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
Launch Chrome with remote debugging on `:9222`. Use `--profile` to preserve user's authentication state.
|
|
@@ -28,8 +28,8 @@ Launch Chrome with remote debugging on `:9222`. Use `--profile` to preserve user
|
|
|
28
28
|
## Navigate
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
skills/browser-tools/browser-nav.js https://example.com
|
|
32
|
+
skills/browser-tools/browser-nav.js https://example.com --new
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
Navigate to URLs. Use `--new` flag to open in a new tab instead of reusing current tab.
|
|
@@ -37,8 +37,8 @@ Navigate to URLs. Use `--new` flag to open in a new tab instead of reusing curre
|
|
|
37
37
|
## Evaluate JavaScript
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
skills/browser-tools/browser-eval.js 'document.title'
|
|
41
|
+
skills/browser-tools/browser-eval.js 'document.querySelectorAll("a").length'
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
Execute JavaScript in the active tab. Code runs in async context. Use this to extract data, inspect page state, or perform DOM operations programmatically.
|
|
@@ -46,7 +46,7 @@ Execute JavaScript in the active tab. Code runs in async context. Use this to ex
|
|
|
46
46
|
## Screenshot
|
|
47
47
|
|
|
48
48
|
```bash
|
|
49
|
-
|
|
49
|
+
skills/browser-tools/browser-screenshot.js
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
Capture current viewport and return temporary file path. Use this to visually inspect page state or verify UI changes.
|
|
@@ -54,7 +54,7 @@ Capture current viewport and return temporary file path. Use this to visually in
|
|
|
54
54
|
## Pick Elements
|
|
55
55
|
|
|
56
56
|
```bash
|
|
57
|
-
|
|
57
|
+
skills/browser-tools/browser-pick.js "Click the submit button"
|
|
58
58
|
```
|
|
59
59
|
|
|
60
60
|
**IMPORTANT**: Use this tool when the user wants to select specific DOM elements on the page. This launches an interactive picker that lets the user click elements to select them. The user can select multiple elements (Cmd/Ctrl+Click) and press Enter when done. The tool returns CSS selectors for the selected elements.
|
|
@@ -67,7 +67,7 @@ Common use cases:
|
|
|
67
67
|
## Cookies
|
|
68
68
|
|
|
69
69
|
```bash
|
|
70
|
-
|
|
70
|
+
skills/browser-tools/browser-cookies.js
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
Display all cookies for the current tab including domain, path, httpOnly, and secure flags. Use this to debug authentication issues or inspect session state.
|
|
@@ -75,7 +75,7 @@ Display all cookies for the current tab including domain, path, httpOnly, and se
|
|
|
75
75
|
## Extract Page Content
|
|
76
76
|
|
|
77
77
|
```bash
|
|
78
|
-
|
|
78
|
+
skills/browser-tools/browser-content.js https://example.com
|
|
79
79
|
```
|
|
80
80
|
|
|
81
81
|
Navigate to a URL and extract readable content as markdown. Uses Mozilla Readability for article extraction and Turndown for HTML-to-markdown conversion. Works on pages with JavaScript content (waits for page to load).
|
|
@@ -174,7 +174,7 @@ Extract structured state in one call:
|
|
|
174
174
|
If DOM updates after actions, add a small delay with bash:
|
|
175
175
|
|
|
176
176
|
```bash
|
|
177
|
-
sleep 0.5 &&
|
|
177
|
+
sleep 0.5 && skills/browser-tools/browser-eval.js '...'
|
|
178
178
|
```
|
|
179
179
|
|
|
180
180
|
### Investigate Before Interacting
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { spawn, execSync } from "node:child_process";
|
|
4
|
-
import puppeteer from "puppeteer
|
|
4
|
+
import puppeteer from "puppeteer";
|
|
5
5
|
|
|
6
6
|
const useProfile = process.argv[2] === "--profile";
|
|
7
7
|
|
|
@@ -51,8 +51,9 @@ if (useProfile) {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
// Start Chrome with flags to force new instance
|
|
54
|
+
const chromePath = puppeteer.executablePath();
|
|
54
55
|
spawn(
|
|
55
|
-
|
|
56
|
+
chromePath,
|
|
56
57
|
[
|
|
57
58
|
"--remote-debugging-port=9222",
|
|
58
59
|
`--user-data-dir=${SCRAPING_DIR}`,
|
|
@@ -6,7 +6,7 @@ description: List available LLM-accessible credentials. Use when you need API ke
|
|
|
6
6
|
# List Available Secrets
|
|
7
7
|
|
|
8
8
|
```bash
|
|
9
|
-
|
|
9
|
+
skills/llm-secrets/llm-secrets.js
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
Shows the names of available secret keys (not values). Output example:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|