thepopebot 1.2.57 → 1.2.58
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 +16 -0
- package/api/index.js +3 -6
- package/bin/cli.js +51 -30
- package/lib/channels/telegram.js +8 -6
- package/lib/db/api-keys.js +5 -2
- package/package.json +1 -1
- /package/templates/{gitignore → .gitignore.template} +0 -0
- /package/templates/{CLAUDE.md → CLAUDE.md.template} +0 -0
- /package/templates/api/{CLAUDE.md → CLAUDE.md.template} +0 -0
package/README.md
CHANGED
|
@@ -200,6 +200,22 @@ Pushing to `main` triggers the `rebuild-event-handler.yml` workflow on your serv
|
|
|
200
200
|
|
|
201
201
|
---
|
|
202
202
|
|
|
203
|
+
## Template File Conventions
|
|
204
|
+
|
|
205
|
+
The `templates/` directory contains files scaffolded into user projects by `thepopebot init`. Two naming conventions handle files that npm or AI tools would otherwise misinterpret:
|
|
206
|
+
|
|
207
|
+
**`.template` suffix** — Files ending in `.template` are scaffolded with the suffix stripped. This is used for files that npm mangles (`.gitignore`) or that AI tools would pick up as real project docs (`CLAUDE.md`).
|
|
208
|
+
|
|
209
|
+
| In `templates/` | Scaffolded as |
|
|
210
|
+
|-----------------|---------------|
|
|
211
|
+
| `.gitignore.template` | `.gitignore` |
|
|
212
|
+
| `CLAUDE.md.template` | `CLAUDE.md` |
|
|
213
|
+
| `api/CLAUDE.md.template` | `api/CLAUDE.md` |
|
|
214
|
+
|
|
215
|
+
**`CLAUDE.md` exclusion** — The scaffolding walker skips any file named `CLAUDE.md` (without the `.template` suffix). This is a safety net so a bare `CLAUDE.md` accidentally added to `templates/` never gets copied into user projects where AI tools would confuse it with real project instructions.
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
203
219
|
## Docs
|
|
204
220
|
|
|
205
221
|
| Document | Description |
|
package/api/index.js
CHANGED
|
@@ -161,12 +161,9 @@ async function processChannelMessage(adapter, normalized) {
|
|
|
161
161
|
async function handleGithubWebhook(request) {
|
|
162
162
|
const { GH_WEBHOOK_SECRET } = process.env;
|
|
163
163
|
|
|
164
|
-
// Validate webhook secret (timing-safe)
|
|
165
|
-
if (GH_WEBHOOK_SECRET) {
|
|
166
|
-
|
|
167
|
-
if (!safeCompare(headerSecret, GH_WEBHOOK_SECRET)) {
|
|
168
|
-
return Response.json({ error: 'Unauthorized' }, { status: 401 });
|
|
169
|
-
}
|
|
164
|
+
// Validate webhook secret (timing-safe, required)
|
|
165
|
+
if (!GH_WEBHOOK_SECRET || !safeCompare(request.headers.get('x-github-webhook-secret-token'), GH_WEBHOOK_SECRET)) {
|
|
166
|
+
return Response.json({ error: 'Unauthorized' }, { status: 401 });
|
|
170
167
|
}
|
|
171
168
|
|
|
172
169
|
const payload = await request.json();
|
package/bin/cli.js
CHANGED
|
@@ -25,6 +25,26 @@ function isManaged(relPath) {
|
|
|
25
25
|
return MANAGED_PATHS.some(p => relPath === p || relPath.startsWith(p));
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
// Files that must never be scaffolded directly (use .template suffix instead).
|
|
29
|
+
const EXCLUDED_FILENAMES = ['CLAUDE.md'];
|
|
30
|
+
|
|
31
|
+
// Files ending in .template are scaffolded with the suffix stripped.
|
|
32
|
+
// e.g. .gitignore.template → .gitignore, CLAUDE.md.template → CLAUDE.md
|
|
33
|
+
function destPath(templateRelPath) {
|
|
34
|
+
if (templateRelPath.endsWith('.template')) {
|
|
35
|
+
return templateRelPath.slice(0, -'.template'.length);
|
|
36
|
+
}
|
|
37
|
+
return templateRelPath;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function templatePath(userPath, templatesDir) {
|
|
41
|
+
const withSuffix = userPath + '.template';
|
|
42
|
+
if (fs.existsSync(path.join(templatesDir, withSuffix))) {
|
|
43
|
+
return withSuffix;
|
|
44
|
+
}
|
|
45
|
+
return userPath;
|
|
46
|
+
}
|
|
47
|
+
|
|
28
48
|
function printUsage() {
|
|
29
49
|
console.log(`
|
|
30
50
|
Usage: thepopebot <command>
|
|
@@ -49,7 +69,7 @@ function getTemplateFiles(templatesDir) {
|
|
|
49
69
|
const fullPath = path.join(dir, entry.name);
|
|
50
70
|
if (entry.isDirectory()) {
|
|
51
71
|
walk(fullPath);
|
|
52
|
-
} else {
|
|
72
|
+
} else if (!EXCLUDED_FILENAMES.includes(entry.name)) {
|
|
53
73
|
files.push(path.relative(templatesDir, fullPath));
|
|
54
74
|
}
|
|
55
75
|
}
|
|
@@ -74,29 +94,30 @@ function init() {
|
|
|
74
94
|
|
|
75
95
|
for (const relPath of templateFiles) {
|
|
76
96
|
const src = path.join(templatesDir, relPath);
|
|
77
|
-
const
|
|
97
|
+
const outPath = destPath(relPath);
|
|
98
|
+
const dest = path.join(cwd, outPath);
|
|
78
99
|
|
|
79
100
|
if (!fs.existsSync(dest)) {
|
|
80
101
|
// File doesn't exist — create it
|
|
81
102
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
82
103
|
fs.copyFileSync(src, dest);
|
|
83
|
-
created.push(
|
|
84
|
-
console.log(` Created ${
|
|
104
|
+
created.push(outPath);
|
|
105
|
+
console.log(` Created ${outPath}`);
|
|
85
106
|
} else {
|
|
86
107
|
// File exists — check if template has changed
|
|
87
108
|
const srcContent = fs.readFileSync(src);
|
|
88
109
|
const destContent = fs.readFileSync(dest);
|
|
89
110
|
if (srcContent.equals(destContent)) {
|
|
90
|
-
skipped.push(
|
|
91
|
-
} else if (!noManaged && isManaged(
|
|
111
|
+
skipped.push(outPath);
|
|
112
|
+
} else if (!noManaged && isManaged(outPath)) {
|
|
92
113
|
// Managed file differs — auto-update to match package
|
|
93
114
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
94
115
|
fs.copyFileSync(src, dest);
|
|
95
|
-
updated.push(
|
|
96
|
-
console.log(` Updated ${
|
|
116
|
+
updated.push(outPath);
|
|
117
|
+
console.log(` Updated ${outPath}`);
|
|
97
118
|
} else {
|
|
98
|
-
changed.push(
|
|
99
|
-
console.log(` Skipped ${
|
|
119
|
+
changed.push(outPath);
|
|
120
|
+
console.log(` Skipped ${outPath} (already exists)`);
|
|
100
121
|
}
|
|
101
122
|
}
|
|
102
123
|
}
|
|
@@ -163,14 +184,6 @@ function init() {
|
|
|
163
184
|
console.log(' To reset to default: npx thepopebot reset <file>');
|
|
164
185
|
}
|
|
165
186
|
|
|
166
|
-
// Handle gitignore rename (npm strips .gitignore from packages)
|
|
167
|
-
const gitignoreSrc = path.join(templatesDir, 'gitignore');
|
|
168
|
-
const gitignoreDest = path.join(cwd, '.gitignore');
|
|
169
|
-
if (fs.existsSync(gitignoreSrc) && !fs.existsSync(gitignoreDest)) {
|
|
170
|
-
fs.copyFileSync(gitignoreSrc, gitignoreDest);
|
|
171
|
-
console.log(' Created .gitignore');
|
|
172
|
-
}
|
|
173
|
-
|
|
174
187
|
// Run npm install
|
|
175
188
|
console.log('\nInstalling dependencies...\n');
|
|
176
189
|
execSync('npm install', { stdio: 'inherit', cwd });
|
|
@@ -207,14 +220,15 @@ function reset(filePath) {
|
|
|
207
220
|
console.log('\nAvailable template files:\n');
|
|
208
221
|
const files = getTemplateFiles(templatesDir);
|
|
209
222
|
for (const file of files) {
|
|
210
|
-
console.log(` ${file}`);
|
|
223
|
+
console.log(` ${destPath(file)}`);
|
|
211
224
|
}
|
|
212
225
|
console.log('\nUsage: thepopebot reset <file>');
|
|
213
226
|
console.log('Example: thepopebot reset config/SOUL.md\n');
|
|
214
227
|
return;
|
|
215
228
|
}
|
|
216
229
|
|
|
217
|
-
const
|
|
230
|
+
const tmplPath = templatePath(filePath, templatesDir);
|
|
231
|
+
const src = path.join(templatesDir, tmplPath);
|
|
218
232
|
const dest = path.join(cwd, filePath);
|
|
219
233
|
|
|
220
234
|
if (!fs.existsSync(src)) {
|
|
@@ -225,7 +239,7 @@ function reset(filePath) {
|
|
|
225
239
|
|
|
226
240
|
if (fs.statSync(src).isDirectory()) {
|
|
227
241
|
console.log(`\nRestoring ${filePath}/...\n`);
|
|
228
|
-
copyDirSyncForce(src, dest);
|
|
242
|
+
copyDirSyncForce(src, dest, tmplPath);
|
|
229
243
|
} else {
|
|
230
244
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
231
245
|
fs.copyFileSync(src, dest);
|
|
@@ -248,16 +262,17 @@ function diff(filePath) {
|
|
|
248
262
|
let anyDiff = false;
|
|
249
263
|
for (const file of files) {
|
|
250
264
|
const src = path.join(templatesDir, file);
|
|
251
|
-
const
|
|
265
|
+
const outPath = destPath(file);
|
|
266
|
+
const dest = path.join(cwd, outPath);
|
|
252
267
|
if (fs.existsSync(dest)) {
|
|
253
268
|
const srcContent = fs.readFileSync(src);
|
|
254
269
|
const destContent = fs.readFileSync(dest);
|
|
255
270
|
if (!srcContent.equals(destContent)) {
|
|
256
|
-
console.log(` ${
|
|
271
|
+
console.log(` ${outPath}`);
|
|
257
272
|
anyDiff = true;
|
|
258
273
|
}
|
|
259
274
|
} else {
|
|
260
|
-
console.log(` ${
|
|
275
|
+
console.log(` ${outPath} (missing)`);
|
|
261
276
|
anyDiff = true;
|
|
262
277
|
}
|
|
263
278
|
}
|
|
@@ -269,7 +284,8 @@ function diff(filePath) {
|
|
|
269
284
|
return;
|
|
270
285
|
}
|
|
271
286
|
|
|
272
|
-
const
|
|
287
|
+
const tmplPath = templatePath(filePath, templatesDir);
|
|
288
|
+
const src = path.join(templatesDir, tmplPath);
|
|
273
289
|
const dest = path.join(cwd, filePath);
|
|
274
290
|
|
|
275
291
|
if (!fs.existsSync(src)) {
|
|
@@ -293,17 +309,22 @@ function diff(filePath) {
|
|
|
293
309
|
}
|
|
294
310
|
}
|
|
295
311
|
|
|
296
|
-
function copyDirSyncForce(src, dest) {
|
|
312
|
+
function copyDirSyncForce(src, dest, templateRelBase = '') {
|
|
297
313
|
fs.mkdirSync(dest, { recursive: true });
|
|
298
314
|
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
299
315
|
for (const entry of entries) {
|
|
316
|
+
if (EXCLUDED_FILENAMES.includes(entry.name)) continue;
|
|
300
317
|
const srcPath = path.join(src, entry.name);
|
|
301
|
-
const
|
|
318
|
+
const templateRel = templateRelBase
|
|
319
|
+
? path.join(templateRelBase, entry.name)
|
|
320
|
+
: entry.name;
|
|
321
|
+
const outName = path.basename(destPath(templateRel));
|
|
322
|
+
const destFile = path.join(dest, outName);
|
|
302
323
|
if (entry.isDirectory()) {
|
|
303
|
-
copyDirSyncForce(srcPath,
|
|
324
|
+
copyDirSyncForce(srcPath, destFile, templateRel);
|
|
304
325
|
} else {
|
|
305
|
-
fs.copyFileSync(srcPath,
|
|
306
|
-
console.log(` Restored ${path.relative(process.cwd(),
|
|
326
|
+
fs.copyFileSync(srcPath, destFile);
|
|
327
|
+
console.log(` Restored ${path.relative(process.cwd(), destFile)}`);
|
|
307
328
|
}
|
|
308
329
|
}
|
|
309
330
|
}
|
package/lib/channels/telegram.js
CHANGED
|
@@ -21,12 +21,14 @@ class TelegramAdapter extends ChannelAdapter {
|
|
|
21
21
|
async receive(request) {
|
|
22
22
|
const { TELEGRAM_WEBHOOK_SECRET, TELEGRAM_CHAT_ID, TELEGRAM_VERIFICATION } = process.env;
|
|
23
23
|
|
|
24
|
-
// Validate secret token
|
|
25
|
-
if (TELEGRAM_WEBHOOK_SECRET) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
// Validate secret token (required)
|
|
25
|
+
if (!TELEGRAM_WEBHOOK_SECRET) {
|
|
26
|
+
console.error('[telegram] TELEGRAM_WEBHOOK_SECRET not configured — rejecting webhook');
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const headerSecret = request.headers.get('x-telegram-bot-api-secret-token');
|
|
30
|
+
if (headerSecret !== TELEGRAM_WEBHOOK_SECRET) {
|
|
31
|
+
return null;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
const update = await request.json();
|
package/lib/db/api-keys.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { randomUUID, randomBytes, createHash } from 'crypto';
|
|
1
|
+
import { randomUUID, randomBytes, createHash, timingSafeEqual } from 'crypto';
|
|
2
2
|
import { eq } from 'drizzle-orm';
|
|
3
3
|
import { getDb } from './index.js';
|
|
4
4
|
import { settings } from './schema.js';
|
|
@@ -137,7 +137,10 @@ export function verifyApiKey(rawKey) {
|
|
|
137
137
|
const keyHash = hashApiKey(rawKey);
|
|
138
138
|
const cached = _ensureCache();
|
|
139
139
|
|
|
140
|
-
if (!cached
|
|
140
|
+
if (!cached) return null;
|
|
141
|
+
const a = Buffer.from(cached.keyHash, 'hex');
|
|
142
|
+
const b = Buffer.from(keyHash, 'hex');
|
|
143
|
+
if (a.length !== b.length || !timingSafeEqual(a, b)) return null;
|
|
141
144
|
|
|
142
145
|
// Update last_used_at in background (non-blocking)
|
|
143
146
|
try {
|
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|