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 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
- const headerSecret = request.headers.get('x-github-webhook-secret-token');
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 dest = path.join(cwd, relPath);
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(relPath);
84
- console.log(` Created ${relPath}`);
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(relPath);
91
- } else if (!noManaged && isManaged(relPath)) {
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(relPath);
96
- console.log(` Updated ${relPath}`);
116
+ updated.push(outPath);
117
+ console.log(` Updated ${outPath}`);
97
118
  } else {
98
- changed.push(relPath);
99
- console.log(` Skipped ${relPath} (already exists)`);
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 src = path.join(templatesDir, filePath);
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 dest = path.join(cwd, file);
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(` ${file}`);
271
+ console.log(` ${outPath}`);
257
272
  anyDiff = true;
258
273
  }
259
274
  } else {
260
- console.log(` ${file} (missing)`);
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 src = path.join(templatesDir, filePath);
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 destPath = path.join(dest, entry.name);
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, destPath);
324
+ copyDirSyncForce(srcPath, destFile, templateRel);
304
325
  } else {
305
- fs.copyFileSync(srcPath, destPath);
306
- console.log(` Restored ${path.relative(process.cwd(), destPath)}`);
326
+ fs.copyFileSync(srcPath, destFile);
327
+ console.log(` Restored ${path.relative(process.cwd(), destFile)}`);
307
328
  }
308
329
  }
309
330
  }
@@ -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 if configured
25
- if (TELEGRAM_WEBHOOK_SECRET) {
26
- const headerSecret = request.headers.get('x-telegram-bot-api-secret-token');
27
- if (headerSecret !== TELEGRAM_WEBHOOK_SECRET) {
28
- return null;
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();
@@ -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 || cached.keyHash !== keyHash) return null;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thepopebot",
3
- "version": "1.2.57",
3
+ "version": "1.2.58",
4
4
  "type": "module",
5
5
  "description": "Create autonomous AI agents with a two-layer architecture: Next.js Event Handler + Docker Agent.",
6
6
  "bin": {
File without changes
File without changes
File without changes