thepopebot 1.2.41 → 1.2.43

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.
@@ -164,11 +164,8 @@ export async function markNotificationsRead() {
164
164
  */
165
165
  export async function getAppVersion() {
166
166
  await requireAuth();
167
- const { createRequire } = await import('module');
168
- const require = createRequire(import.meta.url);
169
- const version = require('../../../package.json').version;
170
- const { getUpdateAvailable } = await import('../cron.js');
171
- return { version, updateAvailable: getUpdateAvailable() };
167
+ const { getInstalledVersion, getUpdateAvailable } = await import('../cron.js');
168
+ return { version: getInstalledVersion(), updateAvailable: getUpdateAvailable() };
172
169
  }
173
170
 
174
171
  /**
@@ -5,6 +5,7 @@ import { MessageIcon, TrashIcon, MoreHorizontalIcon, StarIcon, StarFilledIcon, P
5
5
  import { SidebarMenuButton, SidebarMenuItem, useSidebar } from "./ui/sidebar.js";
6
6
  import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator } from "./ui/dropdown-menu.js";
7
7
  import { ConfirmDialog } from "./ui/confirm-dialog.js";
8
+ import { RenameDialog } from "./ui/rename-dialog.js";
8
9
  import { useChatNav } from "./chat-nav-context.js";
9
10
  import { cn } from "../utils.js";
10
11
  function SidebarHistoryItem({ chat, isActive, onDelete, onStar, onRename }) {
@@ -13,9 +14,11 @@ function SidebarHistoryItem({ chat, isActive, onDelete, onStar, onRename }) {
13
14
  const [hovered, setHovered] = useState(false);
14
15
  const [dropdownOpen, setDropdownOpen] = useState(false);
15
16
  const [confirmDelete, setConfirmDelete] = useState(false);
17
+ const [renameDialogOpen, setRenameDialogOpen] = useState(false);
16
18
  const [editing, setEditing] = useState(false);
17
19
  const [editTitle, setEditTitle] = useState(chat.title || "");
18
20
  const inputRef = useRef(null);
21
+ const clickTimer = useRef(null);
19
22
  const showMenu = hovered || dropdownOpen;
20
23
  useEffect(() => {
21
24
  if (editing && inputRef.current) {
@@ -23,6 +26,11 @@ function SidebarHistoryItem({ chat, isActive, onDelete, onStar, onRename }) {
23
26
  inputRef.current.select();
24
27
  }
25
28
  }, [editing]);
29
+ useEffect(() => {
30
+ return () => {
31
+ if (clickTimer.current) clearTimeout(clickTimer.current);
32
+ };
33
+ }, []);
26
34
  const startRename = () => {
27
35
  setEditTitle(chat.title || "");
28
36
  setEditing(true);
@@ -69,8 +77,10 @@ function SidebarHistoryItem({ chat, isActive, onDelete, onStar, onRename }) {
69
77
  className: "pr-8",
70
78
  isActive,
71
79
  onClick: () => {
72
- navigateToChat(chat.id);
73
- setOpenMobile(false);
80
+ clickTimer.current = setTimeout(() => {
81
+ navigateToChat(chat.id);
82
+ setOpenMobile(false);
83
+ }, 250);
74
84
  },
75
85
  children: [
76
86
  /* @__PURE__ */ jsx(MessageIcon, { size: 14 }),
@@ -81,6 +91,7 @@ function SidebarHistoryItem({ chat, isActive, onDelete, onStar, onRename }) {
81
91
  onDoubleClick: (e) => {
82
92
  e.stopPropagation();
83
93
  e.preventDefault();
94
+ if (clickTimer.current) clearTimeout(clickTimer.current);
84
95
  startRename();
85
96
  },
86
97
  children: chat.title
@@ -124,7 +135,7 @@ function SidebarHistoryItem({ chat, isActive, onDelete, onStar, onRename }) {
124
135
  {
125
136
  onClick: (e) => {
126
137
  e.stopPropagation();
127
- startRename();
138
+ setRenameDialogOpen(true);
128
139
  },
129
140
  children: [
130
141
  /* @__PURE__ */ jsx(PencilIcon, { size: 14 }),
@@ -165,6 +176,15 @@ function SidebarHistoryItem({ chat, isActive, onDelete, onStar, onRename }) {
165
176
  },
166
177
  onCancel: () => setConfirmDelete(false)
167
178
  }
179
+ ),
180
+ /* @__PURE__ */ jsx(
181
+ RenameDialog,
182
+ {
183
+ open: renameDialogOpen,
184
+ currentValue: chat.title || "",
185
+ onSave: (newTitle) => onRename(chat.id, newTitle),
186
+ onCancel: () => setRenameDialogOpen(false)
187
+ }
168
188
  )
169
189
  ] });
170
190
  }
@@ -5,6 +5,7 @@ import { MessageIcon, TrashIcon, MoreHorizontalIcon, StarIcon, StarFilledIcon, P
5
5
  import { SidebarMenuButton, SidebarMenuItem, useSidebar } from './ui/sidebar.js';
6
6
  import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator } from './ui/dropdown-menu.js';
7
7
  import { ConfirmDialog } from './ui/confirm-dialog.js';
8
+ import { RenameDialog } from './ui/rename-dialog.js';
8
9
  import { useChatNav } from './chat-nav-context.js';
9
10
  import { cn } from '../utils.js';
10
11
 
@@ -14,9 +15,11 @@ export function SidebarHistoryItem({ chat, isActive, onDelete, onStar, onRename
14
15
  const [hovered, setHovered] = useState(false);
15
16
  const [dropdownOpen, setDropdownOpen] = useState(false);
16
17
  const [confirmDelete, setConfirmDelete] = useState(false);
18
+ const [renameDialogOpen, setRenameDialogOpen] = useState(false);
17
19
  const [editing, setEditing] = useState(false);
18
20
  const [editTitle, setEditTitle] = useState(chat.title || '');
19
21
  const inputRef = useRef(null);
22
+ const clickTimer = useRef(null);
20
23
 
21
24
  const showMenu = hovered || dropdownOpen;
22
25
 
@@ -27,6 +30,12 @@ export function SidebarHistoryItem({ chat, isActive, onDelete, onStar, onRename
27
30
  }
28
31
  }, [editing]);
29
32
 
33
+ useEffect(() => {
34
+ return () => {
35
+ if (clickTimer.current) clearTimeout(clickTimer.current);
36
+ };
37
+ }, []);
38
+
30
39
  const startRename = () => {
31
40
  setEditTitle(chat.title || '');
32
41
  setEditing(true);
@@ -73,8 +82,10 @@ export function SidebarHistoryItem({ chat, isActive, onDelete, onStar, onRename
73
82
  className="pr-8"
74
83
  isActive={isActive}
75
84
  onClick={() => {
76
- navigateToChat(chat.id);
77
- setOpenMobile(false);
85
+ clickTimer.current = setTimeout(() => {
86
+ navigateToChat(chat.id);
87
+ setOpenMobile(false);
88
+ }, 250);
78
89
  }}
79
90
  >
80
91
  <MessageIcon size={14} />
@@ -83,6 +94,7 @@ export function SidebarHistoryItem({ chat, isActive, onDelete, onStar, onRename
83
94
  onDoubleClick={(e) => {
84
95
  e.stopPropagation();
85
96
  e.preventDefault();
97
+ if (clickTimer.current) clearTimeout(clickTimer.current);
86
98
  startRename();
87
99
  }}
88
100
  >
@@ -122,7 +134,7 @@ export function SidebarHistoryItem({ chat, isActive, onDelete, onStar, onRename
122
134
  <DropdownMenuItem
123
135
  onClick={(e) => {
124
136
  e.stopPropagation();
125
- startRename();
137
+ setRenameDialogOpen(true);
126
138
  }}
127
139
  >
128
140
  <PencilIcon size={14} />
@@ -155,6 +167,12 @@ export function SidebarHistoryItem({ chat, isActive, onDelete, onStar, onRename
155
167
  }}
156
168
  onCancel={() => setConfirmDelete(false)}
157
169
  />
170
+ <RenameDialog
171
+ open={renameDialogOpen}
172
+ currentValue={chat.title || ''}
173
+ onSave={(newTitle) => onRename(chat.id, newTitle)}
174
+ onCancel={() => setRenameDialogOpen(false)}
175
+ />
158
176
  </SidebarMenuItem>
159
177
  );
160
178
  }
@@ -0,0 +1,74 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useState, useEffect, useRef } from "react";
4
+ function RenameDialog({ open, onSave, onCancel, title = "Rename chat", currentValue = "" }) {
5
+ const [value, setValue] = useState(currentValue);
6
+ const inputRef = useRef(null);
7
+ useEffect(() => {
8
+ if (open) {
9
+ setValue(currentValue);
10
+ setTimeout(() => {
11
+ if (inputRef.current) {
12
+ inputRef.current.focus();
13
+ inputRef.current.select();
14
+ }
15
+ }, 0);
16
+ }
17
+ }, [open, currentValue]);
18
+ useEffect(() => {
19
+ if (!open) return;
20
+ const handleEsc = (e) => {
21
+ if (e.key === "Escape") onCancel();
22
+ };
23
+ document.addEventListener("keydown", handleEsc);
24
+ return () => document.removeEventListener("keydown", handleEsc);
25
+ }, [open, onCancel]);
26
+ const handleSave = () => {
27
+ const trimmed = value.trim();
28
+ if (trimmed && trimmed !== currentValue) {
29
+ onSave(trimmed);
30
+ }
31
+ onCancel();
32
+ };
33
+ if (!open) return null;
34
+ return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
35
+ /* @__PURE__ */ jsx("div", { className: "fixed inset-0 bg-black/50", onClick: onCancel }),
36
+ /* @__PURE__ */ jsxs("div", { className: "relative z-50 w-full max-w-sm rounded-lg border border-border bg-background p-6 shadow-lg", onClick: (e) => e.stopPropagation(), children: [
37
+ /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold", children: title }),
38
+ /* @__PURE__ */ jsx(
39
+ "input",
40
+ {
41
+ ref: inputRef,
42
+ type: "text",
43
+ value,
44
+ onChange: (e) => setValue(e.target.value),
45
+ onKeyDown: (e) => {
46
+ if (e.key === "Enter") handleSave();
47
+ },
48
+ className: "mt-3 w-full rounded-md border border-input bg-background px-3 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
49
+ }
50
+ ),
51
+ /* @__PURE__ */ jsxs("div", { className: "mt-4 flex justify-end gap-2", children: [
52
+ /* @__PURE__ */ jsx(
53
+ "button",
54
+ {
55
+ onClick: onCancel,
56
+ className: "rounded-md px-3 py-1.5 text-sm font-medium border border-input bg-background hover:bg-muted",
57
+ children: "Cancel"
58
+ }
59
+ ),
60
+ /* @__PURE__ */ jsx(
61
+ "button",
62
+ {
63
+ onClick: handleSave,
64
+ className: "rounded-md px-3 py-1.5 text-sm font-medium text-white bg-foreground hover:bg-foreground/90",
65
+ children: "Save"
66
+ }
67
+ )
68
+ ] })
69
+ ] })
70
+ ] });
71
+ }
72
+ export {
73
+ RenameDialog
74
+ };
@@ -0,0 +1,72 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect, useRef } from 'react';
4
+
5
+ export function RenameDialog({ open, onSave, onCancel, title = 'Rename chat', currentValue = '' }) {
6
+ const [value, setValue] = useState(currentValue);
7
+ const inputRef = useRef(null);
8
+
9
+ useEffect(() => {
10
+ if (open) {
11
+ setValue(currentValue);
12
+ setTimeout(() => {
13
+ if (inputRef.current) {
14
+ inputRef.current.focus();
15
+ inputRef.current.select();
16
+ }
17
+ }, 0);
18
+ }
19
+ }, [open, currentValue]);
20
+
21
+ useEffect(() => {
22
+ if (!open) return;
23
+ const handleEsc = (e) => {
24
+ if (e.key === 'Escape') onCancel();
25
+ };
26
+ document.addEventListener('keydown', handleEsc);
27
+ return () => document.removeEventListener('keydown', handleEsc);
28
+ }, [open, onCancel]);
29
+
30
+ const handleSave = () => {
31
+ const trimmed = value.trim();
32
+ if (trimmed && trimmed !== currentValue) {
33
+ onSave(trimmed);
34
+ }
35
+ onCancel();
36
+ };
37
+
38
+ if (!open) return null;
39
+
40
+ return (
41
+ <div className="fixed inset-0 z-50 flex items-center justify-center">
42
+ <div className="fixed inset-0 bg-black/50" onClick={onCancel} />
43
+ <div className="relative z-50 w-full max-w-sm rounded-lg border border-border bg-background p-6 shadow-lg" onClick={(e) => e.stopPropagation()}>
44
+ <h3 className="text-lg font-semibold">{title}</h3>
45
+ <input
46
+ ref={inputRef}
47
+ type="text"
48
+ value={value}
49
+ onChange={(e) => setValue(e.target.value)}
50
+ onKeyDown={(e) => {
51
+ if (e.key === 'Enter') handleSave();
52
+ }}
53
+ className="mt-3 w-full rounded-md border border-input bg-background px-3 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
54
+ />
55
+ <div className="mt-4 flex justify-end gap-2">
56
+ <button
57
+ onClick={onCancel}
58
+ className="rounded-md px-3 py-1.5 text-sm font-medium border border-input bg-background hover:bg-muted"
59
+ >
60
+ Cancel
61
+ </button>
62
+ <button
63
+ onClick={handleSave}
64
+ className="rounded-md px-3 py-1.5 text-sm font-medium text-white bg-foreground hover:bg-foreground/90"
65
+ >
66
+ Save
67
+ </button>
68
+ </div>
69
+ </div>
70
+ </div>
71
+ );
72
+ }
package/lib/cron.js CHANGED
@@ -1,16 +1,21 @@
1
1
  import cron from 'node-cron';
2
2
  import fs from 'fs';
3
- import { fileURLToPath } from 'url';
4
- import { dirname, join } from 'path';
3
+ import path from 'path';
5
4
  import { cronsFile, cronDir } from './paths.js';
6
5
  import { executeAction } from './actions.js';
7
6
 
8
- // Read installed version once at module load (doesn't change while server runs)
9
- const __filename = fileURLToPath(import.meta.url);
10
- const __dirname = dirname(__filename);
11
- const _installedVersion = JSON.parse(
12
- fs.readFileSync(join(__dirname, '..', 'package.json'), 'utf8')
13
- ).version;
7
+ // Installed version resolved lazily on first version check.
8
+ // Can't read at module scope because webpack bakes in the build-time path
9
+ // (e.g. /Users/dev/project/...) which doesn't exist at runtime in Docker.
10
+ let _installedVersion = null;
11
+
12
+ function getInstalledVersion() {
13
+ if (!_installedVersion) {
14
+ const pkgPath = path.join(process.cwd(), 'node_modules', 'thepopebot', 'package.json');
15
+ _installedVersion = JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version;
16
+ }
17
+ return _installedVersion;
18
+ }
14
19
 
15
20
  // In-memory flag for available update (read by sidebar, written by cron)
16
21
  let _updateAvailable = null;
@@ -62,8 +67,9 @@ async function runVersionCheck() {
62
67
  const data = await res.json();
63
68
  const latest = data.version;
64
69
 
65
- if (isVersionNewer(latest, _installedVersion)) {
66
- console.log(`[version check] update available: ${_installedVersion} → ${latest}`);
70
+ const installed = getInstalledVersion();
71
+ if (isVersionNewer(latest, installed)) {
72
+ console.log(`[version check] update available: ${installed} → ${latest}`);
67
73
  setUpdateAvailable(latest);
68
74
  // Persist to DB
69
75
  const { setAvailableVersion } = await import('./db/update-check.js');
@@ -143,4 +149,4 @@ function loadCrons() {
143
149
  return tasks;
144
150
  }
145
151
 
146
- export { loadCrons, startBuiltinCrons, getUpdateAvailable, setUpdateAvailable };
152
+ export { loadCrons, startBuiltinCrons, getUpdateAvailable, setUpdateAvailable, getInstalledVersion };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thepopebot",
3
- "version": "1.2.41",
3
+ "version": "1.2.43",
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": {