gigaclaw 1.4.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.
Files changed (249) hide show
  1. package/LICENSE +26 -0
  2. package/README.md +237 -0
  3. package/api/CLAUDE.md +19 -0
  4. package/api/index.js +265 -0
  5. package/bin/cli.js +823 -0
  6. package/bin/local.sh +85 -0
  7. package/bin/postinstall.js +63 -0
  8. package/config/index.js +26 -0
  9. package/config/instrumentation.js +62 -0
  10. package/drizzle/0000_initial.sql +52 -0
  11. package/drizzle/0001_nostalgic_sersi.sql +11 -0
  12. package/drizzle/0002_black_daimon_hellstrom.sql +19 -0
  13. package/drizzle/0003_rename_code_workspaces.sql +5 -0
  14. package/drizzle/meta/0000_snapshot.json +321 -0
  15. package/drizzle/meta/0001_snapshot.json +390 -0
  16. package/drizzle/meta/0002_snapshot.json +411 -0
  17. package/drizzle/meta/0003_snapshot.json +419 -0
  18. package/drizzle/meta/_journal.json +34 -0
  19. package/lib/actions.js +44 -0
  20. package/lib/ai/agent.js +86 -0
  21. package/lib/ai/index.js +342 -0
  22. package/lib/ai/model.js +180 -0
  23. package/lib/ai/tools.js +269 -0
  24. package/lib/ai/web-search.js +42 -0
  25. package/lib/auth/actions.js +28 -0
  26. package/lib/auth/config.js +27 -0
  27. package/lib/auth/edge-config.js +27 -0
  28. package/lib/auth/index.js +27 -0
  29. package/lib/auth/middleware.js +62 -0
  30. package/lib/channels/base.js +56 -0
  31. package/lib/channels/index.js +15 -0
  32. package/lib/channels/telegram.js +148 -0
  33. package/lib/chat/actions.js +579 -0
  34. package/lib/chat/api.js +140 -0
  35. package/lib/chat/components/app-sidebar.js +213 -0
  36. package/lib/chat/components/app-sidebar.jsx +279 -0
  37. package/lib/chat/components/chat-header.js +192 -0
  38. package/lib/chat/components/chat-header.jsx +223 -0
  39. package/lib/chat/components/chat-input.js +236 -0
  40. package/lib/chat/components/chat-input.jsx +249 -0
  41. package/lib/chat/components/chat-nav-context.js +11 -0
  42. package/lib/chat/components/chat-nav-context.jsx +11 -0
  43. package/lib/chat/components/chat-page.js +99 -0
  44. package/lib/chat/components/chat-page.jsx +121 -0
  45. package/lib/chat/components/chat.js +153 -0
  46. package/lib/chat/components/chat.jsx +199 -0
  47. package/lib/chat/components/chats-page.js +367 -0
  48. package/lib/chat/components/chats-page.jsx +394 -0
  49. package/lib/chat/components/code-mode-toggle.js +132 -0
  50. package/lib/chat/components/code-mode-toggle.jsx +163 -0
  51. package/lib/chat/components/crons-page.js +172 -0
  52. package/lib/chat/components/crons-page.jsx +244 -0
  53. package/lib/chat/components/greeting.js +11 -0
  54. package/lib/chat/components/greeting.jsx +16 -0
  55. package/lib/chat/components/icons.js +805 -0
  56. package/lib/chat/components/icons.jsx +751 -0
  57. package/lib/chat/components/index.js +20 -0
  58. package/lib/chat/components/message.js +363 -0
  59. package/lib/chat/components/message.jsx +422 -0
  60. package/lib/chat/components/messages.js +65 -0
  61. package/lib/chat/components/messages.jsx +74 -0
  62. package/lib/chat/components/notifications-page.js +56 -0
  63. package/lib/chat/components/notifications-page.jsx +87 -0
  64. package/lib/chat/components/page-layout.js +21 -0
  65. package/lib/chat/components/page-layout.jsx +28 -0
  66. package/lib/chat/components/pull-requests-page.js +103 -0
  67. package/lib/chat/components/pull-requests-page.jsx +113 -0
  68. package/lib/chat/components/settings-layout.js +39 -0
  69. package/lib/chat/components/settings-layout.jsx +53 -0
  70. package/lib/chat/components/settings-secrets-page.js +216 -0
  71. package/lib/chat/components/settings-secrets-page.jsx +264 -0
  72. package/lib/chat/components/sidebar-history-item.js +138 -0
  73. package/lib/chat/components/sidebar-history-item.jsx +119 -0
  74. package/lib/chat/components/sidebar-history.js +167 -0
  75. package/lib/chat/components/sidebar-history.jsx +220 -0
  76. package/lib/chat/components/sidebar-user-nav.js +61 -0
  77. package/lib/chat/components/sidebar-user-nav.jsx +77 -0
  78. package/lib/chat/components/swarm-page.js +157 -0
  79. package/lib/chat/components/swarm-page.jsx +210 -0
  80. package/lib/chat/components/tool-call.js +89 -0
  81. package/lib/chat/components/tool-call.jsx +107 -0
  82. package/lib/chat/components/triggers-page.js +153 -0
  83. package/lib/chat/components/triggers-page.jsx +221 -0
  84. package/lib/chat/components/ui/combobox.js +98 -0
  85. package/lib/chat/components/ui/combobox.jsx +114 -0
  86. package/lib/chat/components/ui/confirm-dialog.js +53 -0
  87. package/lib/chat/components/ui/confirm-dialog.jsx +57 -0
  88. package/lib/chat/components/ui/dropdown-menu.js +194 -0
  89. package/lib/chat/components/ui/dropdown-menu.jsx +215 -0
  90. package/lib/chat/components/ui/rename-dialog.js +78 -0
  91. package/lib/chat/components/ui/rename-dialog.jsx +74 -0
  92. package/lib/chat/components/ui/scroll-area.js +13 -0
  93. package/lib/chat/components/ui/scroll-area.jsx +17 -0
  94. package/lib/chat/components/ui/separator.js +21 -0
  95. package/lib/chat/components/ui/separator.jsx +18 -0
  96. package/lib/chat/components/ui/sheet.js +75 -0
  97. package/lib/chat/components/ui/sheet.jsx +95 -0
  98. package/lib/chat/components/ui/sidebar.js +228 -0
  99. package/lib/chat/components/ui/sidebar.jsx +246 -0
  100. package/lib/chat/components/ui/tooltip.js +56 -0
  101. package/lib/chat/components/ui/tooltip.jsx +66 -0
  102. package/lib/chat/components/upgrade-dialog.js +151 -0
  103. package/lib/chat/components/upgrade-dialog.jsx +170 -0
  104. package/lib/chat/utils.js +11 -0
  105. package/lib/code/actions.js +153 -0
  106. package/lib/code/code-page.js +22 -0
  107. package/lib/code/code-page.jsx +25 -0
  108. package/lib/code/index.js +1 -0
  109. package/lib/code/terminal-view.js +201 -0
  110. package/lib/code/terminal-view.jsx +224 -0
  111. package/lib/code/ws-proxy.js +80 -0
  112. package/lib/cron.js +246 -0
  113. package/lib/db/api-keys.js +163 -0
  114. package/lib/db/chats.js +168 -0
  115. package/lib/db/code-workspaces.js +110 -0
  116. package/lib/db/index.js +52 -0
  117. package/lib/db/notifications.js +99 -0
  118. package/lib/db/schema.js +66 -0
  119. package/lib/db/update-check.js +96 -0
  120. package/lib/db/users.js +89 -0
  121. package/lib/paths.js +42 -0
  122. package/lib/tools/create-job.js +97 -0
  123. package/lib/tools/docker.js +146 -0
  124. package/lib/tools/github.js +271 -0
  125. package/lib/tools/openai.js +35 -0
  126. package/lib/tools/telegram.js +292 -0
  127. package/lib/triggers.js +104 -0
  128. package/lib/utils/render-md.js +111 -0
  129. package/package.json +118 -0
  130. package/setup/lib/auth.mjs +81 -0
  131. package/setup/lib/env.mjs +21 -0
  132. package/setup/lib/fs-utils.mjs +20 -0
  133. package/setup/lib/github.mjs +149 -0
  134. package/setup/lib/prerequisites.mjs +155 -0
  135. package/setup/lib/prompts.mjs +267 -0
  136. package/setup/lib/providers.mjs +105 -0
  137. package/setup/lib/sync.mjs +125 -0
  138. package/setup/lib/targets.mjs +45 -0
  139. package/setup/lib/telegram-verify.mjs +63 -0
  140. package/setup/lib/telegram.mjs +76 -0
  141. package/setup/setup-cloud.mjs +833 -0
  142. package/setup/setup-local.mjs +377 -0
  143. package/setup/setup-telegram.mjs +265 -0
  144. package/setup/setup.mjs +87 -0
  145. package/templates/.dockerignore +5 -0
  146. package/templates/.env.example +104 -0
  147. package/templates/.github/workflows/auto-merge.yml +117 -0
  148. package/templates/.github/workflows/notify-job-failed.yml +64 -0
  149. package/templates/.github/workflows/notify-pr-complete.yml +119 -0
  150. package/templates/.github/workflows/rebuild-event-handler.yml +121 -0
  151. package/templates/.github/workflows/run-job.yml +89 -0
  152. package/templates/.github/workflows/upgrade-event-handler.yml +62 -0
  153. package/templates/.gitignore.template +45 -0
  154. package/templates/.pi/extensions/env-sanitizer/index.ts +48 -0
  155. package/templates/.pi/extensions/env-sanitizer/package.json +5 -0
  156. package/templates/CLAUDE.md +29 -0
  157. package/templates/CLAUDE.md.template +308 -0
  158. package/templates/app/api/[...gigaclaw]/route.js +1 -0
  159. package/templates/app/api/auth/[...nextauth]/route.js +1 -0
  160. package/templates/app/chat/[chatId]/page.js +9 -0
  161. package/templates/app/chats/page.js +7 -0
  162. package/templates/app/code/[codeWorkspaceId]/page.js +9 -0
  163. package/templates/app/components/ascii-logo.jsx +12 -0
  164. package/templates/app/components/login-form.jsx +92 -0
  165. package/templates/app/components/setup-form.jsx +82 -0
  166. package/templates/app/components/theme-provider.jsx +11 -0
  167. package/templates/app/components/theme-toggle.jsx +38 -0
  168. package/templates/app/components/ui/button.jsx +21 -0
  169. package/templates/app/components/ui/card.jsx +23 -0
  170. package/templates/app/components/ui/input.jsx +10 -0
  171. package/templates/app/components/ui/label.jsx +10 -0
  172. package/templates/app/crons/page.js +5 -0
  173. package/templates/app/globals.css +90 -0
  174. package/templates/app/layout.js +33 -0
  175. package/templates/app/login/page.js +15 -0
  176. package/templates/app/notifications/page.js +7 -0
  177. package/templates/app/page.js +7 -0
  178. package/templates/app/pull-requests/page.js +7 -0
  179. package/templates/app/settings/crons/page.js +5 -0
  180. package/templates/app/settings/layout.js +7 -0
  181. package/templates/app/settings/page.js +5 -0
  182. package/templates/app/settings/secrets/page.js +5 -0
  183. package/templates/app/settings/triggers/page.js +5 -0
  184. package/templates/app/stream/chat/route.js +1 -0
  185. package/templates/app/swarm/page.js +7 -0
  186. package/templates/app/triggers/page.js +5 -0
  187. package/templates/config/CODE_PLANNING.md +14 -0
  188. package/templates/config/CRONS.json +56 -0
  189. package/templates/config/HEARTBEAT.md +3 -0
  190. package/templates/config/JOB_AGENT.md +30 -0
  191. package/templates/config/JOB_PLANNING.md +240 -0
  192. package/templates/config/JOB_SUMMARY.md +130 -0
  193. package/templates/config/SKILL_BUILDING_GUIDE.md +96 -0
  194. package/templates/config/SOUL.md +48 -0
  195. package/templates/config/TRIGGERS.json +58 -0
  196. package/templates/config/WEB_SEARCH_AVAILABLE.md +5 -0
  197. package/templates/config/WEB_SEARCH_UNAVAILABLE.md +3 -0
  198. package/templates/docker/claude-code-job/Dockerfile +34 -0
  199. package/templates/docker/claude-code-job/entrypoint.sh +149 -0
  200. package/templates/docker/claude-code-workspace/.tmux.conf +5 -0
  201. package/templates/docker/claude-code-workspace/Dockerfile +61 -0
  202. package/templates/docker/claude-code-workspace/entrypoint.sh +51 -0
  203. package/templates/docker/event-handler/Dockerfile +20 -0
  204. package/templates/docker/event-handler/ecosystem.config.cjs +7 -0
  205. package/templates/docker/pi-coding-agent-job/Dockerfile +51 -0
  206. package/templates/docker/pi-coding-agent-job/entrypoint.sh +164 -0
  207. package/templates/docker-compose.local.yml +78 -0
  208. package/templates/docker-compose.yml +64 -0
  209. package/templates/instrumentation.js +6 -0
  210. package/templates/middleware.js +23 -0
  211. package/templates/next.config.mjs +3 -0
  212. package/templates/postcss.config.mjs +5 -0
  213. package/templates/public/favicon.ico +0 -0
  214. package/templates/server.js +25 -0
  215. package/templates/skills/LICENSE +21 -0
  216. package/templates/skills/README.md +119 -0
  217. package/templates/skills/brave-search/SKILL.md +79 -0
  218. package/templates/skills/brave-search/content.js +86 -0
  219. package/templates/skills/brave-search/package-lock.json +621 -0
  220. package/templates/skills/brave-search/package.json +14 -0
  221. package/templates/skills/brave-search/search.js +199 -0
  222. package/templates/skills/browser-tools/SKILL.md +196 -0
  223. package/templates/skills/browser-tools/browser-content.js +103 -0
  224. package/templates/skills/browser-tools/browser-cookies.js +35 -0
  225. package/templates/skills/browser-tools/browser-eval.js +53 -0
  226. package/templates/skills/browser-tools/browser-hn-scraper.js +108 -0
  227. package/templates/skills/browser-tools/browser-nav.js +44 -0
  228. package/templates/skills/browser-tools/browser-pick.js +162 -0
  229. package/templates/skills/browser-tools/browser-screenshot.js +34 -0
  230. package/templates/skills/browser-tools/browser-start.js +87 -0
  231. package/templates/skills/browser-tools/package-lock.json +2556 -0
  232. package/templates/skills/browser-tools/package.json +19 -0
  233. package/templates/skills/google-docs/SKILL.md +23 -0
  234. package/templates/skills/google-docs/create.sh +69 -0
  235. package/templates/skills/google-drive/SKILL.md +47 -0
  236. package/templates/skills/google-drive/delete.sh +47 -0
  237. package/templates/skills/google-drive/download.sh +50 -0
  238. package/templates/skills/google-drive/list.sh +41 -0
  239. package/templates/skills/google-drive/upload.sh +76 -0
  240. package/templates/skills/kie-ai/SKILL.md +38 -0
  241. package/templates/skills/kie-ai/generate-image.sh +77 -0
  242. package/templates/skills/kie-ai/generate-video.sh +69 -0
  243. package/templates/skills/llm-secrets/SKILL.md +34 -0
  244. package/templates/skills/llm-secrets/llm-secrets.js +33 -0
  245. package/templates/skills/modify-self/SKILL.md +12 -0
  246. package/templates/skills/youtube-transcript/SKILL.md +41 -0
  247. package/templates/skills/youtube-transcript/package-lock.json +24 -0
  248. package/templates/skills/youtube-transcript/package.json +8 -0
  249. package/templates/skills/youtube-transcript/transcript.js +84 -0
@@ -0,0 +1,264 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect } from 'react';
4
+ import { KeyIcon, CopyIcon, CheckIcon, TrashIcon, RefreshIcon } from './icons.js';
5
+ import { createNewApiKey, getApiKeys, deleteApiKey } from '../actions.js';
6
+
7
+ function timeAgo(ts) {
8
+ if (!ts) return 'Never';
9
+ const seconds = Math.floor((Date.now() - ts) / 1000);
10
+ if (seconds < 60) return 'just now';
11
+ const minutes = Math.floor(seconds / 60);
12
+ if (minutes < 60) return `${minutes}m ago`;
13
+ const hours = Math.floor(minutes / 60);
14
+ if (hours < 24) return `${hours}h ago`;
15
+ const days = Math.floor(hours / 24);
16
+ if (days < 30) return `${days}d ago`;
17
+ const months = Math.floor(days / 30);
18
+ return `${months}mo ago`;
19
+ }
20
+
21
+ function formatDate(ts) {
22
+ if (!ts) return '—';
23
+ return new Date(ts).toLocaleDateString(undefined, {
24
+ year: 'numeric',
25
+ month: 'short',
26
+ day: 'numeric',
27
+ });
28
+ }
29
+
30
+ function CopyButton({ text }) {
31
+ const [copied, setCopied] = useState(false);
32
+
33
+ const handleCopy = async () => {
34
+ try {
35
+ await navigator.clipboard.writeText(text);
36
+ setCopied(true);
37
+ setTimeout(() => setCopied(false), 2000);
38
+ } catch {
39
+ const textarea = document.createElement('textarea');
40
+ textarea.value = text;
41
+ document.body.appendChild(textarea);
42
+ textarea.select();
43
+ document.execCommand('copy');
44
+ document.body.removeChild(textarea);
45
+ setCopied(true);
46
+ setTimeout(() => setCopied(false), 2000);
47
+ }
48
+ };
49
+
50
+ return (
51
+ <button
52
+ onClick={handleCopy}
53
+ className="inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium border border-border bg-background text-muted-foreground hover:bg-accent hover:text-foreground"
54
+ >
55
+ {copied ? <CheckIcon size={14} /> : <CopyIcon size={14} />}
56
+ {copied ? 'Copied' : 'Copy'}
57
+ </button>
58
+ );
59
+ }
60
+
61
+ // ─────────────────────────────────────────────────────────────────────────────
62
+ // Section wrapper — reusable for each secrets section
63
+ // ─────────────────────────────────────────────────────────────────────────────
64
+
65
+ function Section({ title, description, children }) {
66
+ return (
67
+ <div className="pb-8 mb-8 border-b border-border last:border-b-0 last:pb-0 last:mb-0">
68
+ <h2 className="text-base font-medium mb-1">{title}</h2>
69
+ {description && (
70
+ <p className="text-sm text-muted-foreground mb-4">{description}</p>
71
+ )}
72
+ {children}
73
+ </div>
74
+ );
75
+ }
76
+
77
+ // ─────────────────────────────────────────────────────────────────────────────
78
+ // API Key section
79
+ // ─────────────────────────────────────────────────────────────────────────────
80
+
81
+ function ApiKeySection() {
82
+ const [currentKey, setCurrentKey] = useState(null);
83
+ const [loading, setLoading] = useState(true);
84
+ const [creating, setCreating] = useState(false);
85
+ const [newKey, setNewKey] = useState(null);
86
+ const [confirmDelete, setConfirmDelete] = useState(false);
87
+ const [confirmRegenerate, setConfirmRegenerate] = useState(false);
88
+ const [error, setError] = useState(null);
89
+
90
+ const loadKey = async () => {
91
+ try {
92
+ const result = await getApiKeys();
93
+ setCurrentKey(result);
94
+ } catch {
95
+ // ignore
96
+ } finally {
97
+ setLoading(false);
98
+ }
99
+ };
100
+
101
+ useEffect(() => {
102
+ loadKey();
103
+ }, []);
104
+
105
+ const handleCreate = async () => {
106
+ if (creating) return;
107
+ setCreating(true);
108
+ setError(null);
109
+ setConfirmRegenerate(false);
110
+ try {
111
+ const result = await createNewApiKey();
112
+ if (result.error) {
113
+ setError(result.error);
114
+ } else {
115
+ setNewKey(result.key);
116
+ await loadKey();
117
+ }
118
+ } catch {
119
+ setError('Failed to create API key');
120
+ } finally {
121
+ setCreating(false);
122
+ }
123
+ };
124
+
125
+ const handleDelete = async () => {
126
+ if (!confirmDelete) {
127
+ setConfirmDelete(true);
128
+ setTimeout(() => setConfirmDelete(false), 3000);
129
+ return;
130
+ }
131
+ try {
132
+ await deleteApiKey();
133
+ setCurrentKey(null);
134
+ setNewKey(null);
135
+ setConfirmDelete(false);
136
+ } catch {
137
+ // ignore
138
+ }
139
+ };
140
+
141
+ const handleRegenerate = () => {
142
+ if (!confirmRegenerate) {
143
+ setConfirmRegenerate(true);
144
+ setTimeout(() => setConfirmRegenerate(false), 3000);
145
+ return;
146
+ }
147
+ handleCreate();
148
+ };
149
+
150
+ if (loading) {
151
+ return <div className="h-14 animate-pulse rounded-md bg-border/50" />;
152
+ }
153
+
154
+ return (
155
+ <div>
156
+ {error && (
157
+ <p className="text-sm text-destructive mb-4">{error}</p>
158
+ )}
159
+
160
+ {/* New key banner */}
161
+ {newKey && (
162
+ <div className="rounded-lg border border-green-500/30 bg-green-500/5 p-4 mb-4">
163
+ <div className="flex items-start justify-between gap-3 mb-2">
164
+ <p className="text-sm font-medium text-green-600 dark:text-green-400">
165
+ API key created — copy it now. You won't be able to see it again.
166
+ </p>
167
+ <button
168
+ onClick={() => setNewKey(null)}
169
+ className="text-xs text-muted-foreground hover:text-foreground shrink-0"
170
+ >
171
+ Dismiss
172
+ </button>
173
+ </div>
174
+ <div className="flex items-center gap-2">
175
+ <code className="flex-1 rounded-md bg-muted px-3 py-2 text-xs font-mono break-all select-all">
176
+ {newKey}
177
+ </code>
178
+ <CopyButton text={newKey} />
179
+ </div>
180
+ </div>
181
+ )}
182
+
183
+ {currentKey ? (
184
+ <div className="rounded-lg border bg-card p-4">
185
+ <div className="flex items-center justify-between">
186
+ <div className="flex items-center gap-3">
187
+ <div className="shrink-0 rounded-md bg-muted p-2">
188
+ <KeyIcon size={16} />
189
+ </div>
190
+ <div>
191
+ <code className="text-sm font-mono">{currentKey.keyPrefix}...</code>
192
+ <p className="text-xs text-muted-foreground mt-0.5">
193
+ Created {formatDate(currentKey.createdAt)}
194
+ {currentKey.lastUsedAt && (
195
+ <span className="ml-2">· Last used {timeAgo(currentKey.lastUsedAt)}</span>
196
+ )}
197
+ </p>
198
+ </div>
199
+ </div>
200
+ <div className="flex items-center gap-2">
201
+ <button
202
+ onClick={handleRegenerate}
203
+ disabled={creating}
204
+ className={`inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium border ${
205
+ confirmRegenerate
206
+ ? 'border-yellow-500 text-yellow-600 hover:bg-yellow-500/10'
207
+ : 'border-border text-muted-foreground hover:bg-accent hover:text-foreground'
208
+ } disabled:opacity-50`}
209
+ >
210
+ <RefreshIcon size={12} />
211
+ {creating ? 'Generating...' : confirmRegenerate ? 'Confirm regenerate' : 'Regenerate'}
212
+ </button>
213
+ <button
214
+ onClick={handleDelete}
215
+ className={`inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium border ${
216
+ confirmDelete
217
+ ? 'border-destructive text-destructive hover:bg-destructive/10'
218
+ : 'border-border text-muted-foreground hover:text-destructive hover:border-destructive/50'
219
+ }`}
220
+ >
221
+ <TrashIcon size={12} />
222
+ {confirmDelete ? 'Confirm delete' : 'Delete'}
223
+ </button>
224
+ </div>
225
+ </div>
226
+ </div>
227
+ ) : (
228
+ <div className="rounded-lg border border-dashed bg-card p-6 flex flex-col items-center text-center">
229
+ <p className="text-sm text-muted-foreground mb-3">No API key configured</p>
230
+ <button
231
+ onClick={handleCreate}
232
+ disabled={creating}
233
+ className="inline-flex items-center gap-2 rounded-md px-3 py-2 text-sm font-medium bg-foreground text-background hover:bg-foreground/90 disabled:opacity-50 disabled:pointer-events-none"
234
+ >
235
+ {creating ? 'Creating...' : 'Create API key'}
236
+ </button>
237
+ </div>
238
+ )}
239
+ </div>
240
+ );
241
+ }
242
+
243
+ // ─────────────────────────────────────────────────────────────────────────────
244
+ // Main page
245
+ // ─────────────────────────────────────────────────────────────────────────────
246
+
247
+ export function SettingsSecretsPage() {
248
+ return (
249
+ <div>
250
+ <Section
251
+ title="API Key"
252
+ description="Authenticates external requests to /api endpoints. Pass via the x-api-key header."
253
+ >
254
+ <ApiKeySection />
255
+ </Section>
256
+
257
+ {/* Future sections go here, e.g.:
258
+ <Section title="GitHub Token" description="...">
259
+ <GitHubTokenSection />
260
+ </Section>
261
+ */}
262
+ </div>
263
+ );
264
+ }
@@ -0,0 +1,138 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import { MessageIcon, CodeIcon, TrashIcon, MoreHorizontalIcon, StarIcon, StarFilledIcon, PencilIcon } from "./icons.js";
5
+ import { SidebarMenuButton, SidebarMenuItem, useSidebar } from "./ui/sidebar.js";
6
+ import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator } from "./ui/dropdown-menu.js";
7
+ import { ConfirmDialog } from "./ui/confirm-dialog.js";
8
+ import { RenameDialog } from "./ui/rename-dialog.js";
9
+ import { useChatNav } from "./chat-nav-context.js";
10
+ import { cn } from "../utils.js";
11
+ function SidebarHistoryItem({ chat, isActive, onDelete, onStar, onRename }) {
12
+ const { navigateToChat } = useChatNav();
13
+ const { setOpenMobile } = useSidebar();
14
+ const [hovered, setHovered] = useState(false);
15
+ const [dropdownOpen, setDropdownOpen] = useState(false);
16
+ const [confirmDelete, setConfirmDelete] = useState(false);
17
+ const [renameDialogOpen, setRenameDialogOpen] = useState(false);
18
+ const showMenu = hovered || dropdownOpen;
19
+ return /* @__PURE__ */ jsxs(SidebarMenuItem, { children: [
20
+ /* @__PURE__ */ jsxs(
21
+ "div",
22
+ {
23
+ className: "relative group",
24
+ onMouseEnter: () => setHovered(true),
25
+ onMouseLeave: () => setHovered(false),
26
+ children: [
27
+ /* @__PURE__ */ jsxs(
28
+ SidebarMenuButton,
29
+ {
30
+ href: chat.codeWorkspaceId && chat.containerName ? `/code/${chat.codeWorkspaceId}` : `/chat/${chat.id}`,
31
+ className: "pr-8",
32
+ isActive,
33
+ onClick: (e) => {
34
+ e.preventDefault();
35
+ if (chat.codeWorkspaceId && chat.containerName) {
36
+ window.location.href = `/code/${chat.codeWorkspaceId}`;
37
+ } else {
38
+ navigateToChat(chat.id);
39
+ }
40
+ setOpenMobile(false);
41
+ },
42
+ children: [
43
+ chat.codeWorkspaceId && chat.containerName ? /* @__PURE__ */ jsx(CodeIcon, { size: 14 }) : /* @__PURE__ */ jsx(MessageIcon, { size: 14 }),
44
+ /* @__PURE__ */ jsx("span", { className: "truncate flex-1", children: chat.title })
45
+ ]
46
+ }
47
+ ),
48
+ /* @__PURE__ */ jsx("div", { className: cn(
49
+ "absolute right-1 top-1/2 -translate-y-1/2 z-10",
50
+ showMenu ? "opacity-100" : "opacity-0 pointer-events-none"
51
+ ), children: /* @__PURE__ */ jsxs(DropdownMenu, { open: dropdownOpen, onOpenChange: setDropdownOpen, children: [
52
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
53
+ "button",
54
+ {
55
+ className: cn(
56
+ "rounded-md p-1",
57
+ "text-muted-foreground hover:text-foreground",
58
+ "bg-foreground/10 hover:bg-foreground/15"
59
+ ),
60
+ "aria-label": "Chat options",
61
+ children: /* @__PURE__ */ jsx(MoreHorizontalIcon, { size: 16 })
62
+ }
63
+ ) }),
64
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", side: "bottom", children: [
65
+ /* @__PURE__ */ jsxs(
66
+ DropdownMenuItem,
67
+ {
68
+ onClick: (e) => {
69
+ e.stopPropagation();
70
+ onStar(chat.id);
71
+ },
72
+ children: [
73
+ chat.starred ? /* @__PURE__ */ jsx(StarFilledIcon, { size: 14 }) : /* @__PURE__ */ jsx(StarIcon, { size: 14 }),
74
+ chat.starred ? "Unstar" : "Star"
75
+ ]
76
+ }
77
+ ),
78
+ /* @__PURE__ */ jsxs(
79
+ DropdownMenuItem,
80
+ {
81
+ onClick: (e) => {
82
+ e.stopPropagation();
83
+ setRenameDialogOpen(true);
84
+ },
85
+ children: [
86
+ /* @__PURE__ */ jsx(PencilIcon, { size: 14 }),
87
+ "Rename"
88
+ ]
89
+ }
90
+ ),
91
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
92
+ /* @__PURE__ */ jsxs(
93
+ DropdownMenuItem,
94
+ {
95
+ className: "text-destructive hover:text-destructive",
96
+ onClick: (e) => {
97
+ e.stopPropagation();
98
+ setConfirmDelete(true);
99
+ },
100
+ children: [
101
+ /* @__PURE__ */ jsx(TrashIcon, { size: 14 }),
102
+ "Delete"
103
+ ]
104
+ }
105
+ )
106
+ ] })
107
+ ] }) })
108
+ ]
109
+ }
110
+ ),
111
+ /* @__PURE__ */ jsx(
112
+ ConfirmDialog,
113
+ {
114
+ open: confirmDelete,
115
+ title: "Delete chat?",
116
+ description: "This will permanently delete this chat and all its messages.",
117
+ confirmLabel: "Delete",
118
+ onConfirm: () => {
119
+ setConfirmDelete(false);
120
+ onDelete(chat.id);
121
+ },
122
+ onCancel: () => setConfirmDelete(false)
123
+ }
124
+ ),
125
+ /* @__PURE__ */ jsx(
126
+ RenameDialog,
127
+ {
128
+ open: renameDialogOpen,
129
+ currentValue: chat.title || "",
130
+ onSave: (newTitle) => onRename(chat.id, newTitle),
131
+ onCancel: () => setRenameDialogOpen(false)
132
+ }
133
+ )
134
+ ] });
135
+ }
136
+ export {
137
+ SidebarHistoryItem
138
+ };
@@ -0,0 +1,119 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { MessageIcon, CodeIcon, TrashIcon, MoreHorizontalIcon, StarIcon, StarFilledIcon, PencilIcon } from './icons.js';
5
+ import { SidebarMenuButton, SidebarMenuItem, useSidebar } from './ui/sidebar.js';
6
+ import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator } from './ui/dropdown-menu.js';
7
+ import { ConfirmDialog } from './ui/confirm-dialog.js';
8
+ import { RenameDialog } from './ui/rename-dialog.js';
9
+ import { useChatNav } from './chat-nav-context.js';
10
+ import { cn } from '../utils.js';
11
+
12
+ export function SidebarHistoryItem({ chat, isActive, onDelete, onStar, onRename }) {
13
+ const { navigateToChat } = useChatNav();
14
+ const { setOpenMobile } = useSidebar();
15
+ const [hovered, setHovered] = useState(false);
16
+ const [dropdownOpen, setDropdownOpen] = useState(false);
17
+ const [confirmDelete, setConfirmDelete] = useState(false);
18
+ const [renameDialogOpen, setRenameDialogOpen] = useState(false);
19
+
20
+ const showMenu = hovered || dropdownOpen;
21
+
22
+ return (
23
+ <SidebarMenuItem>
24
+ <div
25
+ className="relative group"
26
+ onMouseEnter={() => setHovered(true)}
27
+ onMouseLeave={() => setHovered(false)}
28
+ >
29
+ <SidebarMenuButton
30
+ href={chat.codeWorkspaceId && chat.containerName ? `/code/${chat.codeWorkspaceId}` : `/chat/${chat.id}`}
31
+ className="pr-8"
32
+ isActive={isActive}
33
+ onClick={(e) => {
34
+ e.preventDefault();
35
+ if (chat.codeWorkspaceId && chat.containerName) {
36
+ window.location.href = `/code/${chat.codeWorkspaceId}`;
37
+ } else {
38
+ navigateToChat(chat.id);
39
+ }
40
+ setOpenMobile(false);
41
+ }}
42
+ >
43
+ {chat.codeWorkspaceId && chat.containerName ? <CodeIcon size={14} /> : <MessageIcon size={14} />}
44
+ <span className="truncate flex-1">
45
+ {chat.title}
46
+ </span>
47
+ </SidebarMenuButton>
48
+
49
+ <div className={cn(
50
+ 'absolute right-1 top-1/2 -translate-y-1/2 z-10',
51
+ showMenu ? 'opacity-100' : 'opacity-0 pointer-events-none'
52
+ )}>
53
+ <DropdownMenu open={dropdownOpen} onOpenChange={setDropdownOpen}>
54
+ <DropdownMenuTrigger asChild>
55
+ <button
56
+ className={cn(
57
+ 'rounded-md p-1',
58
+ 'text-muted-foreground hover:text-foreground',
59
+ 'bg-foreground/10 hover:bg-foreground/15'
60
+ )}
61
+ aria-label="Chat options"
62
+ >
63
+ <MoreHorizontalIcon size={16} />
64
+ </button>
65
+ </DropdownMenuTrigger>
66
+ <DropdownMenuContent align="end" side="bottom">
67
+ <DropdownMenuItem
68
+ onClick={(e) => {
69
+ e.stopPropagation();
70
+ onStar(chat.id);
71
+ }}
72
+ >
73
+ {chat.starred ? <StarFilledIcon size={14} /> : <StarIcon size={14} />}
74
+ {chat.starred ? 'Unstar' : 'Star'}
75
+ </DropdownMenuItem>
76
+ <DropdownMenuItem
77
+ onClick={(e) => {
78
+ e.stopPropagation();
79
+ setRenameDialogOpen(true);
80
+ }}
81
+ >
82
+ <PencilIcon size={14} />
83
+ Rename
84
+ </DropdownMenuItem>
85
+ <DropdownMenuSeparator />
86
+ <DropdownMenuItem
87
+ className="text-destructive hover:text-destructive"
88
+ onClick={(e) => {
89
+ e.stopPropagation();
90
+ setConfirmDelete(true);
91
+ }}
92
+ >
93
+ <TrashIcon size={14} />
94
+ Delete
95
+ </DropdownMenuItem>
96
+ </DropdownMenuContent>
97
+ </DropdownMenu>
98
+ </div>
99
+ </div>
100
+ <ConfirmDialog
101
+ open={confirmDelete}
102
+ title="Delete chat?"
103
+ description="This will permanently delete this chat and all its messages."
104
+ confirmLabel="Delete"
105
+ onConfirm={() => {
106
+ setConfirmDelete(false);
107
+ onDelete(chat.id);
108
+ }}
109
+ onCancel={() => setConfirmDelete(false)}
110
+ />
111
+ <RenameDialog
112
+ open={renameDialogOpen}
113
+ currentValue={chat.title || ''}
114
+ onSave={(newTitle) => onRename(chat.id, newTitle)}
115
+ onCancel={() => setRenameDialogOpen(false)}
116
+ />
117
+ </SidebarMenuItem>
118
+ );
119
+ }
@@ -0,0 +1,167 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useState } from "react";
4
+ import { SidebarHistoryItem } from "./sidebar-history-item.js";
5
+ import { SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarMenu } from "./ui/sidebar.js";
6
+ import { useChatNav } from "./chat-nav-context.js";
7
+ import { getChats, deleteChat, renameChat, starChat } from "../actions.js";
8
+ import { cn } from "../utils.js";
9
+ import { MessageIcon, CodeIcon } from "./icons.js";
10
+ function groupChatsByDate(chats) {
11
+ const now = /* @__PURE__ */ new Date();
12
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
13
+ const yesterday = new Date(today.getTime() - 864e5);
14
+ const last7Days = new Date(today.getTime() - 7 * 864e5);
15
+ const last30Days = new Date(today.getTime() - 30 * 864e5);
16
+ const groups = {
17
+ Starred: [],
18
+ Today: [],
19
+ Yesterday: [],
20
+ "Last 7 Days": [],
21
+ "Last 30 Days": [],
22
+ Older: []
23
+ };
24
+ for (const chat of chats) {
25
+ if (chat.starred) {
26
+ groups.Starred.push(chat);
27
+ continue;
28
+ }
29
+ const date = new Date(chat.updatedAt);
30
+ if (date >= today) {
31
+ groups.Today.push(chat);
32
+ } else if (date >= yesterday) {
33
+ groups.Yesterday.push(chat);
34
+ } else if (date >= last7Days) {
35
+ groups["Last 7 Days"].push(chat);
36
+ } else if (date >= last30Days) {
37
+ groups["Last 30 Days"].push(chat);
38
+ } else {
39
+ groups.Older.push(chat);
40
+ }
41
+ }
42
+ return groups;
43
+ }
44
+ const FILTERS = [
45
+ { value: "all", label: "All", icon: null },
46
+ { value: "chat", label: "Chat", icon: MessageIcon },
47
+ { value: "code", label: "Code", icon: CodeIcon }
48
+ ];
49
+ function ChatTypeFilter({ filter, setFilter }) {
50
+ return /* @__PURE__ */ jsx("div", { className: "flex items-center gap-0.5 px-2 mb-1", children: FILTERS.map(({ value, label, icon: Icon }) => /* @__PURE__ */ jsxs(
51
+ "button",
52
+ {
53
+ onClick: () => setFilter(value),
54
+ className: cn(
55
+ "flex items-center gap-1 rounded-md px-2 py-0.5 text-xs font-medium transition-colors",
56
+ filter === value ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"
57
+ ),
58
+ children: [
59
+ Icon && /* @__PURE__ */ jsx(Icon, { size: 12 }),
60
+ label
61
+ ]
62
+ },
63
+ value
64
+ )) });
65
+ }
66
+ const isCodeChat = (chat) => Boolean(chat.codeWorkspaceId && chat.containerName);
67
+ function SidebarHistory() {
68
+ const [chats, setChats] = useState([]);
69
+ const [loading, setLoading] = useState(true);
70
+ const [filter, setFilter] = useState("all");
71
+ const updateFilter = (v) => {
72
+ setFilter(v);
73
+ try {
74
+ localStorage.setItem("sidebar-chat-filter", v);
75
+ } catch {
76
+ }
77
+ };
78
+ useEffect(() => {
79
+ try {
80
+ const v = localStorage.getItem("sidebar-chat-filter");
81
+ if (v === "chat" || v === "code") setFilter(v);
82
+ } catch {
83
+ }
84
+ }, []);
85
+ const { activeChatId, navigateToChat } = useChatNav();
86
+ const loadChats = async () => {
87
+ try {
88
+ const result = await getChats();
89
+ setChats(result);
90
+ } catch (err) {
91
+ console.error("Failed to load chats:", err);
92
+ } finally {
93
+ setLoading(false);
94
+ }
95
+ };
96
+ useEffect(() => {
97
+ loadChats();
98
+ }, [activeChatId]);
99
+ useEffect(() => {
100
+ const handler = () => loadChats();
101
+ window.addEventListener("chatsupdated", handler);
102
+ return () => window.removeEventListener("chatsupdated", handler);
103
+ }, []);
104
+ const handleDelete = async (chatId) => {
105
+ setChats((prev) => prev.filter((c) => c.id !== chatId));
106
+ const { success } = await deleteChat(chatId);
107
+ if (success) {
108
+ if (chatId === activeChatId) {
109
+ navigateToChat(null);
110
+ }
111
+ } else {
112
+ loadChats();
113
+ }
114
+ };
115
+ const handleStar = async (chatId) => {
116
+ setChats(
117
+ (prev) => prev.map((c) => c.id === chatId ? { ...c, starred: c.starred ? 0 : 1 } : c)
118
+ );
119
+ const { success } = await starChat(chatId);
120
+ if (!success) loadChats();
121
+ };
122
+ const handleRename = async (chatId, title) => {
123
+ setChats(
124
+ (prev) => prev.map((c) => c.id === chatId ? { ...c, title } : c)
125
+ );
126
+ const { success } = await renameChat(chatId, title);
127
+ if (!success) loadChats();
128
+ };
129
+ if (loading && chats.length === 0) {
130
+ return /* @__PURE__ */ jsx(SidebarGroup, { children: /* @__PURE__ */ jsxs(SidebarGroupContent, { children: [
131
+ /* @__PURE__ */ jsx(ChatTypeFilter, { filter, setFilter: updateFilter }),
132
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2 px-2", children: [...Array(5)].map((_, i) => /* @__PURE__ */ jsx("div", { className: "h-8 animate-pulse rounded-md bg-border/50" }, i)) })
133
+ ] }) });
134
+ }
135
+ if (chats.length === 0) {
136
+ return /* @__PURE__ */ jsx(SidebarGroup, { children: /* @__PURE__ */ jsx(SidebarGroupContent, { children: /* @__PURE__ */ jsx("p", { className: "px-4 py-2 text-sm text-muted-foreground", children: "No chats yet. Start a conversation!" }) }) });
137
+ }
138
+ const filteredChats = filter === "all" ? chats : filter === "code" ? chats.filter(isCodeChat) : chats.filter((c) => !isCodeChat(c));
139
+ const grouped = groupChatsByDate(filteredChats);
140
+ const hasResults = Object.values(grouped).some((g) => g.length > 0);
141
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
142
+ /* @__PURE__ */ jsx(SidebarGroup, { children: /* @__PURE__ */ jsx(SidebarGroupContent, { children: /* @__PURE__ */ jsx(ChatTypeFilter, { filter, setFilter: updateFilter }) }) }),
143
+ hasResults ? Object.entries(grouped).map(
144
+ ([label, groupChats]) => groupChats.length > 0 && /* @__PURE__ */ jsxs(SidebarGroup, { children: [
145
+ /* @__PURE__ */ jsx(SidebarGroupLabel, { children: label }),
146
+ /* @__PURE__ */ jsx(SidebarGroupContent, { children: /* @__PURE__ */ jsx(SidebarMenu, { children: groupChats.map((chat) => /* @__PURE__ */ jsx(
147
+ SidebarHistoryItem,
148
+ {
149
+ chat,
150
+ isActive: chat.id === activeChatId,
151
+ onDelete: handleDelete,
152
+ onStar: handleStar,
153
+ onRename: handleRename
154
+ },
155
+ chat.id
156
+ )) }) })
157
+ ] }, label)
158
+ ) : /* @__PURE__ */ jsx(SidebarGroup, { children: /* @__PURE__ */ jsx(SidebarGroupContent, { children: /* @__PURE__ */ jsxs("p", { className: "px-4 py-2 text-sm text-muted-foreground", children: [
159
+ "No ",
160
+ filter === "code" ? "code" : "chat",
161
+ " chats yet."
162
+ ] }) }) })
163
+ ] });
164
+ }
165
+ export {
166
+ SidebarHistory
167
+ };