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,221 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect } from 'react';
4
+ import { ZapIcon, ChevronDownIcon } from './icons.js';
5
+ import { getSwarmConfig } from '../actions.js';
6
+
7
+ const typeBadgeStyles = {
8
+ agent: 'bg-purple-500/10 text-purple-500',
9
+ command: 'bg-blue-500/10 text-blue-500',
10
+ webhook: 'bg-orange-500/10 text-orange-500',
11
+ };
12
+
13
+ const typeOrder = { agent: 0, command: 1, webhook: 2 };
14
+
15
+ function sortByType(items) {
16
+ return [...items].sort((a, b) => {
17
+ const actions_a = a.actions || [];
18
+ const actions_b = b.actions || [];
19
+ const ta = typeOrder[(actions_a[0]?.type) || 'agent'] ?? 99;
20
+ const tb = typeOrder[(actions_b[0]?.type) || 'agent'] ?? 99;
21
+ return ta - tb;
22
+ });
23
+ }
24
+
25
+ // ─────────────────────────────────────────────────────────────────────────────
26
+ // Group Header
27
+ // ─────────────────────────────────────────────────────────────────────────────
28
+
29
+ function GroupHeader({ label, count }) {
30
+ return (
31
+ <div className="flex items-center gap-2 pt-2 pb-1">
32
+ <span className="text-xs font-medium text-muted-foreground uppercase tracking-wide">{label}</span>
33
+ <span className="text-xs text-muted-foreground">({count})</span>
34
+ </div>
35
+ );
36
+ }
37
+
38
+ // ─────────────────────────────────────────────────────────────────────────────
39
+ // Action Card (nested inside trigger)
40
+ // ─────────────────────────────────────────────────────────────────────────────
41
+
42
+ function ActionCard({ action, index }) {
43
+ const type = action.type || 'agent';
44
+
45
+ return (
46
+ <div className="rounded-md border bg-background p-3">
47
+ <div className="flex items-center gap-2 mb-2">
48
+ <span className="text-xs text-muted-foreground font-medium">Action {index + 1}</span>
49
+ <span className={`inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium ${typeBadgeStyles[type] || typeBadgeStyles.agent}`}>
50
+ {type}
51
+ </span>
52
+ </div>
53
+ {type === 'agent' && action.job && (
54
+ <div>
55
+ <pre className="text-xs bg-muted rounded-md p-3 whitespace-pre-wrap break-words font-mono overflow-auto max-h-48">
56
+ {action.job}
57
+ </pre>
58
+ {(action.llm_provider || action.llm_model) && (
59
+ <div className="flex items-center gap-2 mt-2">
60
+ <span className="text-xs font-medium text-muted-foreground">LLM:</span>
61
+ <span className="inline-flex items-center rounded-full bg-purple-500/10 text-purple-500 px-2 py-0.5 text-[10px] font-medium">
62
+ {[action.llm_provider, action.llm_model].filter(Boolean).join(' / ')}
63
+ </span>
64
+ </div>
65
+ )}
66
+ </div>
67
+ )}
68
+ {type === 'command' && action.command && (
69
+ <pre className="text-xs bg-muted rounded-md p-3 whitespace-pre-wrap break-words font-mono overflow-auto max-h-48">
70
+ {action.command}
71
+ </pre>
72
+ )}
73
+ {type === 'webhook' && (
74
+ <div className="flex flex-col gap-2">
75
+ <pre className="text-xs bg-muted rounded-md p-3 whitespace-pre-wrap break-words font-mono overflow-auto">
76
+ {action.method && action.method !== 'POST' ? `${action.method} ` : ''}{action.url}
77
+ </pre>
78
+ {action.vars && Object.keys(action.vars).length > 0 && (
79
+ <div>
80
+ <p className="text-xs font-medium text-muted-foreground mb-1">Variables</p>
81
+ <pre className="text-xs bg-muted rounded-md p-3 whitespace-pre-wrap break-words font-mono overflow-auto max-h-48">
82
+ {JSON.stringify(action.vars, null, 2)}
83
+ </pre>
84
+ </div>
85
+ )}
86
+ </div>
87
+ )}
88
+ </div>
89
+ );
90
+ }
91
+
92
+ // ─────────────────────────────────────────────────────────────────────────────
93
+ // Trigger Card
94
+ // ─────────────────────────────────────────────────────────────────────────────
95
+
96
+ function TriggerCard({ trigger }) {
97
+ const [expanded, setExpanded] = useState(false);
98
+ const disabled = trigger.enabled === false;
99
+ const actions = trigger.actions || [];
100
+ const actionTypes = actions
101
+ .map((a) => a.type || 'agent')
102
+ .filter((v, i, arr) => arr.indexOf(v) === i);
103
+
104
+ return (
105
+ <div
106
+ className={`rounded-lg border bg-card transition-opacity ${disabled ? 'opacity-60' : ''}`}
107
+ >
108
+ <button
109
+ onClick={() => setExpanded(!expanded)}
110
+ className="flex items-center gap-3 w-full text-left p-4 hover:bg-accent/50 rounded-lg"
111
+ >
112
+ <div className="shrink-0 rounded-md bg-muted p-2">
113
+ <ZapIcon size={16} />
114
+ </div>
115
+ <div className="flex-1 min-w-0">
116
+ <p className="text-sm font-medium truncate">{trigger.name}</p>
117
+ <p className="text-xs text-muted-foreground mt-0.5">
118
+ <span className="font-mono">{trigger.watch_path}</span>
119
+ <span className="mx-1.5 text-border">|</span>
120
+ {actions.length} action{actions.length !== 1 ? 's' : ''}
121
+ {actionTypes.length > 0 && (
122
+ <span className="ml-1">({actionTypes.join(', ')})</span>
123
+ )}
124
+ </p>
125
+ </div>
126
+ <div className="flex items-center gap-2 shrink-0">
127
+ <span
128
+ className={`inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium ${
129
+ disabled ? 'bg-muted text-muted-foreground' : 'bg-green-500/10 text-green-500'
130
+ }`}
131
+ >
132
+ {disabled ? 'disabled' : 'enabled'}
133
+ </span>
134
+ <span className={`transition-transform ${expanded ? 'rotate-180' : ''}`}>
135
+ <ChevronDownIcon size={14} />
136
+ </span>
137
+ </div>
138
+ </button>
139
+
140
+ {expanded && (
141
+ <div className="border-t px-4 py-3 flex flex-col gap-2">
142
+ {actions.length === 0 ? (
143
+ <p className="text-xs text-muted-foreground">No actions defined.</p>
144
+ ) : (
145
+ actions.map((action, i) => (
146
+ <ActionCard key={i} action={action} index={i} />
147
+ ))
148
+ )}
149
+ </div>
150
+ )}
151
+ </div>
152
+ );
153
+ }
154
+
155
+ // ─────────────────────────────────────────────────────────────────────────────
156
+ // Main Page
157
+ // ─────────────────────────────────────────────────────────────────────────────
158
+
159
+ export function TriggersPage() {
160
+ const [triggers, setTriggers] = useState([]);
161
+ const [loading, setLoading] = useState(true);
162
+
163
+ useEffect(() => {
164
+ getSwarmConfig()
165
+ .then((data) => {
166
+ if (data?.triggers) setTriggers(data.triggers);
167
+ })
168
+ .catch(() => {})
169
+ .finally(() => setLoading(false));
170
+ }, []);
171
+
172
+ const enabled = sortByType(triggers.filter((t) => t.enabled !== false));
173
+ const disabled = sortByType(triggers.filter((t) => t.enabled === false));
174
+
175
+ return (
176
+ <>
177
+ {!loading && (
178
+ <p className="text-sm text-muted-foreground mb-4">
179
+ {triggers.length} trigger{triggers.length !== 1 ? 's' : ''} configured, {enabled.length} enabled
180
+ </p>
181
+ )}
182
+
183
+ {loading ? (
184
+ <div className="flex flex-col gap-3">
185
+ {[...Array(3)].map((_, i) => (
186
+ <div key={i} className="h-20 animate-pulse rounded-lg bg-border/50" />
187
+ ))}
188
+ </div>
189
+ ) : triggers.length === 0 ? (
190
+ <div className="flex flex-col items-center justify-center py-16 text-center">
191
+ <div className="rounded-full bg-muted p-4 mb-4">
192
+ <ZapIcon size={24} />
193
+ </div>
194
+ <p className="text-sm font-medium mb-1">No triggers configured</p>
195
+ <p className="text-xs text-muted-foreground max-w-sm">
196
+ Add webhook triggers by editing <span className="font-mono">config/TRIGGERS.json</span> in your project.
197
+ </p>
198
+ </div>
199
+ ) : (
200
+ <div className="flex flex-col gap-3">
201
+ {enabled.length > 0 && (
202
+ <>
203
+ <GroupHeader label="Enabled" count={enabled.length} />
204
+ {enabled.map((trigger, i) => (
205
+ <TriggerCard key={`enabled-${i}`} trigger={trigger} />
206
+ ))}
207
+ </>
208
+ )}
209
+ {disabled.length > 0 && (
210
+ <>
211
+ <GroupHeader label="Disabled" count={disabled.length} />
212
+ {disabled.map((trigger, i) => (
213
+ <TriggerCard key={`disabled-${i}`} trigger={trigger} />
214
+ ))}
215
+ </>
216
+ )}
217
+ </div>
218
+ )}
219
+ </>
220
+ );
221
+ }
@@ -0,0 +1,98 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useState, useRef, useEffect, useCallback } from "react";
4
+ import { ChevronDownIcon, CheckIcon, SearchIcon } from "../icons.js";
5
+ import { cn } from "../../utils.js";
6
+ function Combobox({ options = [], value, onChange, placeholder = "Select...", loading = false, disabled = false }) {
7
+ const [open, setOpen] = useState(false);
8
+ const [filter, setFilter] = useState("");
9
+ const ref = useRef(null);
10
+ const inputRef = useRef(null);
11
+ const selectedLabel = options.find((o) => o.value === value)?.label || "";
12
+ const filtered = filter ? options.filter((o) => o.label.toLowerCase().includes(filter.toLowerCase())) : options;
13
+ useEffect(() => {
14
+ if (!open) return;
15
+ const handleClick = (e) => {
16
+ if (ref.current && !ref.current.contains(e.target)) {
17
+ setOpen(false);
18
+ setFilter("");
19
+ }
20
+ };
21
+ const handleEsc = (e) => {
22
+ if (e.key === "Escape") {
23
+ setOpen(false);
24
+ setFilter("");
25
+ }
26
+ };
27
+ setTimeout(() => document.addEventListener("click", handleClick), 0);
28
+ document.addEventListener("keydown", handleEsc);
29
+ return () => {
30
+ document.removeEventListener("click", handleClick);
31
+ document.removeEventListener("keydown", handleEsc);
32
+ };
33
+ }, [open]);
34
+ useEffect(() => {
35
+ if (open) {
36
+ setTimeout(() => inputRef.current?.focus(), 0);
37
+ }
38
+ }, [open]);
39
+ const handleSelect = useCallback((val) => {
40
+ onChange(val);
41
+ setOpen(false);
42
+ setFilter("");
43
+ }, [onChange]);
44
+ return /* @__PURE__ */ jsxs("div", { ref, className: "relative", children: [
45
+ /* @__PURE__ */ jsxs(
46
+ "button",
47
+ {
48
+ type: "button",
49
+ disabled,
50
+ onClick: () => !disabled && setOpen(!open),
51
+ className: cn(
52
+ "flex items-center gap-2 rounded-lg border px-3 py-1.5 text-sm transition-colors w-full",
53
+ disabled ? "border-border bg-muted text-muted-foreground cursor-not-allowed opacity-60" : "border-border bg-background text-foreground hover:bg-muted cursor-pointer"
54
+ ),
55
+ children: [
56
+ /* @__PURE__ */ jsx("span", { className: cn("flex-1 text-left truncate", !value && "text-muted-foreground"), children: value ? selectedLabel : placeholder }),
57
+ /* @__PURE__ */ jsx(ChevronDownIcon, { size: 14, className: cn("text-muted-foreground transition-transform shrink-0", open && "rotate-180") })
58
+ ]
59
+ }
60
+ ),
61
+ open && /* @__PURE__ */ jsxs("div", { className: "absolute z-50 mt-1 w-full min-w-[200px] overflow-hidden rounded-lg border border-border bg-background shadow-lg", children: [
62
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-b border-border px-3 py-2", children: [
63
+ /* @__PURE__ */ jsx(SearchIcon, { size: 14, className: "text-muted-foreground shrink-0" }),
64
+ /* @__PURE__ */ jsx(
65
+ "input",
66
+ {
67
+ ref: inputRef,
68
+ type: "text",
69
+ value: filter,
70
+ onChange: (e) => setFilter(e.target.value),
71
+ placeholder: "Search...",
72
+ className: "flex-1 bg-transparent text-sm text-foreground placeholder:text-muted-foreground focus:outline-none"
73
+ }
74
+ )
75
+ ] }),
76
+ /* @__PURE__ */ jsx("div", { className: "max-h-[200px] overflow-y-auto p-1", children: loading ? /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-sm text-muted-foreground", children: "Loading..." }) : filtered.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-sm text-muted-foreground", children: "No results" }) : filtered.map((opt) => /* @__PURE__ */ jsxs(
77
+ "button",
78
+ {
79
+ type: "button",
80
+ onClick: () => handleSelect(opt.value),
81
+ className: cn(
82
+ "flex w-full items-center gap-2 rounded-md px-3 py-1.5 text-sm text-left transition-colors",
83
+ "hover:bg-muted",
84
+ opt.value === value && "bg-muted"
85
+ ),
86
+ children: [
87
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: opt.label }),
88
+ opt.value === value && /* @__PURE__ */ jsx(CheckIcon, { size: 14, className: "text-foreground shrink-0" })
89
+ ]
90
+ },
91
+ opt.value
92
+ )) })
93
+ ] })
94
+ ] });
95
+ }
96
+ export {
97
+ Combobox
98
+ };
@@ -0,0 +1,114 @@
1
+ 'use client';
2
+
3
+ import { useState, useRef, useEffect, useCallback } from 'react';
4
+ import { ChevronDownIcon, CheckIcon, SearchIcon } from '../icons.js';
5
+ import { cn } from '../../utils.js';
6
+
7
+ export function Combobox({ options = [], value, onChange, placeholder = 'Select...', loading = false, disabled = false }) {
8
+ const [open, setOpen] = useState(false);
9
+ const [filter, setFilter] = useState('');
10
+ const ref = useRef(null);
11
+ const inputRef = useRef(null);
12
+
13
+ const selectedLabel = options.find((o) => o.value === value)?.label || '';
14
+
15
+ const filtered = filter
16
+ ? options.filter((o) => o.label.toLowerCase().includes(filter.toLowerCase()))
17
+ : options;
18
+
19
+ // Close on click outside
20
+ useEffect(() => {
21
+ if (!open) return;
22
+ const handleClick = (e) => {
23
+ if (ref.current && !ref.current.contains(e.target)) {
24
+ setOpen(false);
25
+ setFilter('');
26
+ }
27
+ };
28
+ const handleEsc = (e) => {
29
+ if (e.key === 'Escape') {
30
+ setOpen(false);
31
+ setFilter('');
32
+ }
33
+ };
34
+ setTimeout(() => document.addEventListener('click', handleClick), 0);
35
+ document.addEventListener('keydown', handleEsc);
36
+ return () => {
37
+ document.removeEventListener('click', handleClick);
38
+ document.removeEventListener('keydown', handleEsc);
39
+ };
40
+ }, [open]);
41
+
42
+ // Auto-focus search input when opened
43
+ useEffect(() => {
44
+ if (open) {
45
+ setTimeout(() => inputRef.current?.focus(), 0);
46
+ }
47
+ }, [open]);
48
+
49
+ const handleSelect = useCallback((val) => {
50
+ onChange(val);
51
+ setOpen(false);
52
+ setFilter('');
53
+ }, [onChange]);
54
+
55
+ return (
56
+ <div ref={ref} className="relative">
57
+ <button
58
+ type="button"
59
+ disabled={disabled}
60
+ onClick={() => !disabled && setOpen(!open)}
61
+ className={cn(
62
+ 'flex items-center gap-2 rounded-lg border px-3 py-1.5 text-sm transition-colors w-full',
63
+ disabled
64
+ ? 'border-border bg-muted text-muted-foreground cursor-not-allowed opacity-60'
65
+ : 'border-border bg-background text-foreground hover:bg-muted cursor-pointer'
66
+ )}
67
+ >
68
+ <span className={cn('flex-1 text-left truncate', !value && 'text-muted-foreground')}>
69
+ {value ? selectedLabel : placeholder}
70
+ </span>
71
+ <ChevronDownIcon size={14} className={cn('text-muted-foreground transition-transform shrink-0', open && 'rotate-180')} />
72
+ </button>
73
+
74
+ {open && (
75
+ <div className="absolute z-50 mt-1 w-full min-w-[200px] overflow-hidden rounded-lg border border-border bg-background shadow-lg">
76
+ <div className="flex items-center gap-2 border-b border-border px-3 py-2">
77
+ <SearchIcon size={14} className="text-muted-foreground shrink-0" />
78
+ <input
79
+ ref={inputRef}
80
+ type="text"
81
+ value={filter}
82
+ onChange={(e) => setFilter(e.target.value)}
83
+ placeholder="Search..."
84
+ className="flex-1 bg-transparent text-sm text-foreground placeholder:text-muted-foreground focus:outline-none"
85
+ />
86
+ </div>
87
+ <div className="max-h-[200px] overflow-y-auto p-1">
88
+ {loading ? (
89
+ <div className="px-3 py-2 text-sm text-muted-foreground">Loading...</div>
90
+ ) : filtered.length === 0 ? (
91
+ <div className="px-3 py-2 text-sm text-muted-foreground">No results</div>
92
+ ) : (
93
+ filtered.map((opt) => (
94
+ <button
95
+ key={opt.value}
96
+ type="button"
97
+ onClick={() => handleSelect(opt.value)}
98
+ className={cn(
99
+ 'flex w-full items-center gap-2 rounded-md px-3 py-1.5 text-sm text-left transition-colors',
100
+ 'hover:bg-muted',
101
+ opt.value === value && 'bg-muted'
102
+ )}
103
+ >
104
+ <span className="flex-1 truncate">{opt.label}</span>
105
+ {opt.value === value && <CheckIcon size={14} className="text-foreground shrink-0" />}
106
+ </button>
107
+ ))
108
+ )}
109
+ </div>
110
+ </div>
111
+ )}
112
+ </div>
113
+ );
114
+ }
@@ -0,0 +1,53 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useRef } from "react";
4
+ import { cn } from "../../utils.js";
5
+ function ConfirmDialog({ open, onConfirm, onCancel, title, description, confirmLabel = "Delete", cancelLabel = "Cancel", variant = "destructive" }) {
6
+ const cancelRef = useRef(null);
7
+ useEffect(() => {
8
+ if (open && cancelRef.current) {
9
+ cancelRef.current.focus();
10
+ }
11
+ }, [open]);
12
+ useEffect(() => {
13
+ if (!open) return;
14
+ const handleEsc = (e) => {
15
+ if (e.key === "Escape") onCancel();
16
+ };
17
+ document.addEventListener("keydown", handleEsc);
18
+ return () => document.removeEventListener("keydown", handleEsc);
19
+ }, [open, onCancel]);
20
+ if (!open) return null;
21
+ return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
22
+ /* @__PURE__ */ jsx("div", { className: "fixed inset-0 bg-black/50", onClick: onCancel }),
23
+ /* @__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: [
24
+ /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold", children: title }),
25
+ description && /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: description }),
26
+ /* @__PURE__ */ jsxs("div", { className: "mt-4 flex justify-end gap-2", children: [
27
+ /* @__PURE__ */ jsx(
28
+ "button",
29
+ {
30
+ ref: cancelRef,
31
+ onClick: onCancel,
32
+ className: "rounded-md px-3 py-1.5 text-sm font-medium border border-input bg-background hover:bg-muted",
33
+ children: cancelLabel
34
+ }
35
+ ),
36
+ /* @__PURE__ */ jsx(
37
+ "button",
38
+ {
39
+ onClick: onConfirm,
40
+ className: cn(
41
+ "rounded-md px-3 py-1.5 text-sm font-medium text-white",
42
+ variant === "destructive" ? "bg-destructive hover:bg-destructive/90" : "bg-foreground hover:bg-foreground/90"
43
+ ),
44
+ children: confirmLabel
45
+ }
46
+ )
47
+ ] })
48
+ ] })
49
+ ] });
50
+ }
51
+ export {
52
+ ConfirmDialog
53
+ };
@@ -0,0 +1,57 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useRef } from 'react';
4
+ import { cn } from '../../utils.js';
5
+
6
+ export function ConfirmDialog({ open, onConfirm, onCancel, title, description, confirmLabel = 'Delete', cancelLabel = 'Cancel', variant = 'destructive' }) {
7
+ const cancelRef = useRef(null);
8
+
9
+ useEffect(() => {
10
+ if (open && cancelRef.current) {
11
+ cancelRef.current.focus();
12
+ }
13
+ }, [open]);
14
+
15
+ useEffect(() => {
16
+ if (!open) return;
17
+ const handleEsc = (e) => {
18
+ if (e.key === 'Escape') onCancel();
19
+ };
20
+ document.addEventListener('keydown', handleEsc);
21
+ return () => document.removeEventListener('keydown', handleEsc);
22
+ }, [open, onCancel]);
23
+
24
+ if (!open) return null;
25
+
26
+ return (
27
+ <div className="fixed inset-0 z-50 flex items-center justify-center">
28
+ <div className="fixed inset-0 bg-black/50" onClick={onCancel} />
29
+ <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()}>
30
+ <h3 className="text-lg font-semibold">{title}</h3>
31
+ {description && (
32
+ <p className="mt-2 text-sm text-muted-foreground">{description}</p>
33
+ )}
34
+ <div className="mt-4 flex justify-end gap-2">
35
+ <button
36
+ ref={cancelRef}
37
+ onClick={onCancel}
38
+ className="rounded-md px-3 py-1.5 text-sm font-medium border border-input bg-background hover:bg-muted"
39
+ >
40
+ {cancelLabel}
41
+ </button>
42
+ <button
43
+ onClick={onConfirm}
44
+ className={cn(
45
+ 'rounded-md px-3 py-1.5 text-sm font-medium text-white',
46
+ variant === 'destructive'
47
+ ? 'bg-destructive hover:bg-destructive/90'
48
+ : 'bg-foreground hover:bg-foreground/90'
49
+ )}
50
+ >
51
+ {confirmLabel}
52
+ </button>
53
+ </div>
54
+ </div>
55
+ </div>
56
+ );
57
+ }