claudeck 1.0.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 (157) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +233 -0
  3. package/cli.js +2 -0
  4. package/config/agent-chains.json +16 -0
  5. package/config/agent-dags.json +16 -0
  6. package/config/agents.json +46 -0
  7. package/config/bot-prompt.json +3 -0
  8. package/config/folders.json +66 -0
  9. package/config/prompts.json +92 -0
  10. package/config/repos.json +86 -0
  11. package/config/telegram-config.json +17 -0
  12. package/config/workflows.json +90 -0
  13. package/db.js +1198 -0
  14. package/package.json +55 -0
  15. package/plugins/claude-editor/client.css +171 -0
  16. package/plugins/claude-editor/client.js +183 -0
  17. package/plugins/event-stream/client.css +207 -0
  18. package/plugins/event-stream/client.js +271 -0
  19. package/plugins/linear/client.css +345 -0
  20. package/plugins/linear/client.js +380 -0
  21. package/plugins/linear/config.json +5 -0
  22. package/plugins/linear/server.js +312 -0
  23. package/plugins/repos/client.css +549 -0
  24. package/plugins/repos/client.js +663 -0
  25. package/plugins/repos/server.js +232 -0
  26. package/plugins/sudoku/client.css +196 -0
  27. package/plugins/sudoku/client.js +329 -0
  28. package/plugins/tasks/client.css +414 -0
  29. package/plugins/tasks/client.js +394 -0
  30. package/plugins/tasks/server.js +116 -0
  31. package/plugins/tic-tac-toe/client.css +167 -0
  32. package/plugins/tic-tac-toe/client.js +241 -0
  33. package/public/css/core/components.css +232 -0
  34. package/public/css/core/layout.css +330 -0
  35. package/public/css/core/print.css +18 -0
  36. package/public/css/core/reset.css +36 -0
  37. package/public/css/core/responsive.css +378 -0
  38. package/public/css/core/theme.css +116 -0
  39. package/public/css/core/variables.css +93 -0
  40. package/public/css/features/agent-monitor.css +297 -0
  41. package/public/css/features/agent-sidebar.css +525 -0
  42. package/public/css/features/agents.css +996 -0
  43. package/public/css/features/analytics.css +181 -0
  44. package/public/css/features/background-sessions.css +321 -0
  45. package/public/css/features/cost-dashboard.css +168 -0
  46. package/public/css/features/home.css +313 -0
  47. package/public/css/features/retro-terminal.css +88 -0
  48. package/public/css/features/telegram.css +127 -0
  49. package/public/css/features/tour.css +148 -0
  50. package/public/css/features/voice-input.css +60 -0
  51. package/public/css/features/welcome.css +241 -0
  52. package/public/css/panels/assistant-bot.css +442 -0
  53. package/public/css/panels/dev-docs.css +292 -0
  54. package/public/css/panels/file-explorer.css +322 -0
  55. package/public/css/panels/git-panel.css +221 -0
  56. package/public/css/panels/mcp-manager.css +199 -0
  57. package/public/css/panels/tips-feed.css +353 -0
  58. package/public/css/ui/commands.css +273 -0
  59. package/public/css/ui/context-gauge.css +76 -0
  60. package/public/css/ui/file-picker.css +69 -0
  61. package/public/css/ui/image-attachments.css +106 -0
  62. package/public/css/ui/messages.css +884 -0
  63. package/public/css/ui/modals.css +122 -0
  64. package/public/css/ui/parallel.css +217 -0
  65. package/public/css/ui/permissions.css +110 -0
  66. package/public/css/ui/right-panel.css +481 -0
  67. package/public/css/ui/sessions.css +689 -0
  68. package/public/css/ui/status-bar.css +425 -0
  69. package/public/css/ui/toolbox.css +206 -0
  70. package/public/data/tips.json +218 -0
  71. package/public/icons/favicon.png +0 -0
  72. package/public/icons/icon-192.png +0 -0
  73. package/public/icons/icon-512.png +0 -0
  74. package/public/icons/whaly.png +0 -0
  75. package/public/index.html +1140 -0
  76. package/public/js/core/api.js +591 -0
  77. package/public/js/core/constants.js +3 -0
  78. package/public/js/core/dom.js +270 -0
  79. package/public/js/core/events.js +10 -0
  80. package/public/js/core/plugin-loader.js +153 -0
  81. package/public/js/core/store.js +39 -0
  82. package/public/js/core/utils.js +25 -0
  83. package/public/js/core/ws.js +64 -0
  84. package/public/js/features/agent-monitor.js +222 -0
  85. package/public/js/features/agents.js +1209 -0
  86. package/public/js/features/analytics.js +397 -0
  87. package/public/js/features/attachments.js +251 -0
  88. package/public/js/features/background-sessions.js +475 -0
  89. package/public/js/features/chat.js +589 -0
  90. package/public/js/features/cost-dashboard.js +152 -0
  91. package/public/js/features/dag-editor.js +399 -0
  92. package/public/js/features/easter-egg.js +46 -0
  93. package/public/js/features/home.js +270 -0
  94. package/public/js/features/projects.js +372 -0
  95. package/public/js/features/prompts.js +228 -0
  96. package/public/js/features/sessions.js +332 -0
  97. package/public/js/features/telegram.js +131 -0
  98. package/public/js/features/tour.js +210 -0
  99. package/public/js/features/voice-input.js +185 -0
  100. package/public/js/features/welcome.js +43 -0
  101. package/public/js/features/workflows.js +277 -0
  102. package/public/js/main.js +51 -0
  103. package/public/js/panels/assistant-bot.js +445 -0
  104. package/public/js/panels/dev-docs.js +380 -0
  105. package/public/js/panels/file-explorer.js +486 -0
  106. package/public/js/panels/git-panel.js +285 -0
  107. package/public/js/panels/mcp-manager.js +311 -0
  108. package/public/js/panels/tips-feed.js +303 -0
  109. package/public/js/ui/commands.js +114 -0
  110. package/public/js/ui/context-gauge.js +100 -0
  111. package/public/js/ui/diff.js +124 -0
  112. package/public/js/ui/disabled-tools.js +36 -0
  113. package/public/js/ui/export.js +74 -0
  114. package/public/js/ui/formatting.js +206 -0
  115. package/public/js/ui/header-dropdowns.js +72 -0
  116. package/public/js/ui/input-meta.js +71 -0
  117. package/public/js/ui/max-turns.js +21 -0
  118. package/public/js/ui/messages.js +387 -0
  119. package/public/js/ui/model-selector.js +20 -0
  120. package/public/js/ui/notifications.js +232 -0
  121. package/public/js/ui/parallel.js +176 -0
  122. package/public/js/ui/permissions.js +168 -0
  123. package/public/js/ui/right-panel.js +173 -0
  124. package/public/js/ui/shortcuts.js +143 -0
  125. package/public/js/ui/sidebar-toggle.js +29 -0
  126. package/public/js/ui/status-bar.js +172 -0
  127. package/public/js/ui/tab-sdk.js +623 -0
  128. package/public/js/ui/theme.js +38 -0
  129. package/public/manifest.json +13 -0
  130. package/public/offline.html +190 -0
  131. package/public/style.css +42 -0
  132. package/public/sw.js +91 -0
  133. package/server/agent-loop.js +385 -0
  134. package/server/dag-executor.js +265 -0
  135. package/server/orchestrator.js +514 -0
  136. package/server/paths.js +61 -0
  137. package/server/plugin-mount.js +56 -0
  138. package/server/push-sender.js +31 -0
  139. package/server/routes/agents.js +294 -0
  140. package/server/routes/bot.js +45 -0
  141. package/server/routes/exec.js +35 -0
  142. package/server/routes/files.js +218 -0
  143. package/server/routes/mcp.js +82 -0
  144. package/server/routes/messages.js +36 -0
  145. package/server/routes/notifications.js +37 -0
  146. package/server/routes/projects.js +207 -0
  147. package/server/routes/prompts.js +53 -0
  148. package/server/routes/sessions.js +103 -0
  149. package/server/routes/stats.js +143 -0
  150. package/server/routes/telegram.js +71 -0
  151. package/server/routes/tips.js +135 -0
  152. package/server/routes/workflows.js +81 -0
  153. package/server/summarizer.js +55 -0
  154. package/server/telegram-poller.js +205 -0
  155. package/server/telegram-sender.js +304 -0
  156. package/server/ws-handler.js +926 -0
  157. package/server.js +179 -0
@@ -0,0 +1,270 @@
1
+ // Centralized DOM references
2
+ export const $ = {
3
+ // Home
4
+ homeBtn: document.getElementById("home-btn"),
5
+ homePage: document.getElementById("home-page"),
6
+
7
+ // Main controls
8
+ projectSelect: document.getElementById("project-select"),
9
+ newSessionBtn: document.getElementById("new-session-btn"),
10
+ sessionList: document.getElementById("session-list"),
11
+ messagesDiv: document.getElementById("messages"),
12
+ messageInput: document.getElementById("message-input"),
13
+ sendBtn: document.getElementById("send-btn"),
14
+ stopBtn: document.getElementById("stop-btn"),
15
+ toggleParallelBtn: document.getElementById("toggle-parallel-btn"),
16
+
17
+ // Header
18
+ connectionDot: document.getElementById("connection-dot"),
19
+ connectionText: document.getElementById("connection-text"),
20
+ accountEmail: document.getElementById("account-email"),
21
+ accountPlan: document.getElementById("account-plan"),
22
+ totalCostEl: document.getElementById("total-cost"),
23
+ projectCostEl: document.getElementById("project-cost"),
24
+ headerProjectName: document.getElementById("header-project-name"),
25
+
26
+ // Toolbox
27
+ toolboxBtn: document.getElementById("toolbox-btn"),
28
+ toolboxPanel: document.getElementById("toolbox-panel"),
29
+
30
+ // Workflows (panel kept for legacy, button merged into agent-btn)
31
+ workflowBtn: document.getElementById("workflow-btn"), // removed from DOM — will be null
32
+ workflowPanel: document.getElementById("workflow-panel"),
33
+
34
+ // Workflow CRUD Modal
35
+ wfModal: document.getElementById("wf-modal"),
36
+ wfModalTitle: document.getElementById("wf-modal-title"),
37
+ wfModalClose: document.getElementById("wf-modal-close"),
38
+ wfModalCancel: document.getElementById("wf-modal-cancel"),
39
+ wfForm: document.getElementById("wf-form"),
40
+ wfFormTitle: document.getElementById("wf-form-title"),
41
+ wfFormDesc: document.getElementById("wf-form-desc"),
42
+ wfStepsList: document.getElementById("wf-steps-list"),
43
+ wfAddStepBtn: document.getElementById("wf-add-step-btn"),
44
+ wfFormEditId: document.getElementById("wf-form-edit-id"),
45
+
46
+ // Agents
47
+ agentBtn: document.getElementById("agent-btn"),
48
+ agentPanel: document.getElementById("agent-panel"),
49
+ agentModal: document.getElementById("agent-modal"),
50
+ agentModalTitle: document.getElementById("agent-modal-title"),
51
+ agentModalClose: document.getElementById("agent-modal-close"),
52
+ agentModalCancel: document.getElementById("agent-modal-cancel"),
53
+ agentForm: document.getElementById("agent-form"),
54
+ agentFormTitle: document.getElementById("agent-form-title"),
55
+ agentFormDesc: document.getElementById("agent-form-desc"),
56
+ agentFormIcon: document.getElementById("agent-form-icon"),
57
+ agentFormGoal: document.getElementById("agent-form-goal"),
58
+ agentFormMaxTurns: document.getElementById("agent-form-max-turns"),
59
+ agentFormTimeout: document.getElementById("agent-form-timeout"),
60
+ agentFormEditId: document.getElementById("agent-form-edit-id"),
61
+
62
+ // Agent Chains
63
+ chainModal: document.getElementById("chain-modal"),
64
+ chainModalTitle: document.getElementById("chain-modal-title"),
65
+ chainModalClose: document.getElementById("chain-modal-close"),
66
+ chainModalCancel: document.getElementById("chain-modal-cancel"),
67
+ chainForm: document.getElementById("chain-form"),
68
+ chainFormTitle: document.getElementById("chain-form-title"),
69
+ chainFormDesc: document.getElementById("chain-form-desc"),
70
+ chainAgentList: document.getElementById("chain-agent-list"),
71
+ chainAddAgentBtn: document.getElementById("chain-add-agent-btn"),
72
+ chainFormContext: document.getElementById("chain-form-context"),
73
+ chainFormEditId: document.getElementById("chain-form-edit-id"),
74
+
75
+ // DAG Editor
76
+ dagModal: document.getElementById("dag-modal"),
77
+ dagModalTitle: document.getElementById("dag-modal-title"),
78
+ dagModalClose: document.getElementById("dag-modal-close"),
79
+ dagModalCancel: document.getElementById("dag-modal-cancel"),
80
+ dagModalSave: document.getElementById("dag-modal-save"),
81
+ dagAutoLayout: document.getElementById("dag-auto-layout"),
82
+ dagFormTitle: document.getElementById("dag-form-title"),
83
+ dagFormDesc: document.getElementById("dag-form-desc"),
84
+ dagFormEditId: document.getElementById("dag-form-edit-id"),
85
+ dagNodePalette: document.getElementById("dag-node-palette"),
86
+ dagCanvas: document.getElementById("dag-canvas"),
87
+
88
+ // System prompt
89
+ spBadge: document.getElementById("system-prompt-badge"),
90
+ spEditBtn: document.getElementById("system-prompt-edit-btn"),
91
+ spModal: document.getElementById("system-prompt-modal"),
92
+ spTextarea: document.getElementById("sp-textarea"),
93
+ spForm: document.getElementById("system-prompt-form"),
94
+
95
+ // File picker
96
+ attachBtn: document.getElementById("attach-btn"),
97
+ attachBadge: document.getElementById("attach-badge"),
98
+ fpModal: document.getElementById("file-picker-modal"),
99
+ fpSearch: document.getElementById("fp-search"),
100
+ fpList: document.getElementById("fp-list"),
101
+ fpCount: document.getElementById("fp-count"),
102
+
103
+ // Image attachments
104
+ imageBtn: document.getElementById("image-btn"),
105
+ imageFileInput: document.getElementById("image-file-input"),
106
+ imagePreviewStrip: document.getElementById("image-preview-strip"),
107
+
108
+ // Voice input
109
+ micBtn: document.getElementById("mic-btn"),
110
+
111
+ // Prompt modal
112
+ promptModal: document.getElementById("prompt-modal"),
113
+ promptForm: document.getElementById("prompt-form"),
114
+ modalCloseBtn: document.getElementById("modal-close"),
115
+ modalCancelBtn: document.getElementById("modal-cancel"),
116
+
117
+ // Shortcuts
118
+ shortcutsModal: document.getElementById("shortcuts-modal"),
119
+
120
+ // Cost dashboard
121
+ costDashboardModal: document.getElementById("cost-dashboard-modal"),
122
+ costModalClose: document.getElementById("cost-modal-close"),
123
+
124
+ // Theme
125
+ themeToggleBtn: document.getElementById("theme-toggle-btn"),
126
+ themeIconSun: document.getElementById("theme-icon-sun"),
127
+ themeIconMoon: document.getElementById("theme-icon-moon"),
128
+
129
+ // Session search
130
+ sessionSearchInput: document.getElementById("session-search"),
131
+
132
+ // Context gauge
133
+ contextGauge: document.getElementById("context-gauge"),
134
+ contextGaugeFill: document.getElementById("context-gauge-fill"),
135
+ contextGaugeLabel: document.getElementById("context-gauge-label"),
136
+
137
+ // Streaming tokens (status bar)
138
+ streamingTokens: document.getElementById("sb-streaming-tokens"),
139
+ streamingTokensValue: document.getElementById("sb-tokens-value"),
140
+ streamingTokensSep: document.getElementById("sb-tokens-sep"),
141
+
142
+ // Model selector
143
+ modelSelect: document.getElementById("model-select"),
144
+
145
+ // Max turns selector
146
+ maxTurnsSelect: document.getElementById("max-turns-select"),
147
+
148
+ // Permissions
149
+ permModeSelect: document.getElementById("perm-mode-select"),
150
+ permModal: document.getElementById("perm-modal"),
151
+ permModalToolName: document.getElementById("perm-modal-tool-name"),
152
+ permModalSummary: document.getElementById("perm-modal-summary"),
153
+ permModalInput: document.getElementById("perm-modal-input"),
154
+ permAlwaysAllowCb: document.getElementById("perm-always-allow-cb"),
155
+ permAlwaysAllowTool: document.getElementById("perm-always-allow-tool"),
156
+ permAllowBtn: document.getElementById("perm-allow-btn"),
157
+ permDenyBtn: document.getElementById("perm-deny-btn"),
158
+
159
+ // Background sessions
160
+ bgConfirmModal: document.getElementById("bg-confirm-modal"),
161
+ bgConfirmCancel: document.getElementById("bg-confirm-cancel"),
162
+ bgConfirmAbort: document.getElementById("bg-confirm-abort"),
163
+ bgConfirmBackground: document.getElementById("bg-confirm-background"),
164
+ bgSessionIndicator: document.getElementById("bg-session-indicator"),
165
+ bgSessionBadge: document.getElementById("bg-session-badge"),
166
+
167
+ // Telegram
168
+ telegramBtn: document.getElementById("telegram-settings-btn"),
169
+ telegramModal: document.getElementById("telegram-modal"),
170
+ telegramEnabled: document.getElementById("telegram-enabled"),
171
+ telegramBotToken: document.getElementById("telegram-bot-token"),
172
+ telegramChatId: document.getElementById("telegram-chat-id"),
173
+ telegramAfkTimeout: document.getElementById("telegram-afk-timeout"),
174
+ telegramTestBtn: document.getElementById("telegram-test-btn"),
175
+ telegramSaveBtn: document.getElementById("telegram-save-btn"),
176
+ telegramClose: document.getElementById("telegram-close"),
177
+ telegramLabel: document.getElementById("telegram-label"),
178
+ telegramStatus: document.getElementById("telegram-status"),
179
+ tgNotifySession: document.getElementById("tg-notify-session"),
180
+ tgNotifyWorkflow: document.getElementById("tg-notify-workflow"),
181
+ tgNotifyChain: document.getElementById("tg-notify-chain"),
182
+ tgNotifyAgent: document.getElementById("tg-notify-agent"),
183
+ tgNotifyOrchestrator: document.getElementById("tg-notify-orchestrator"),
184
+ tgNotifyDag: document.getElementById("tg-notify-dag"),
185
+ tgNotifyErrors: document.getElementById("tg-notify-errors"),
186
+ tgNotifyPermissions: document.getElementById("tg-notify-permissions"),
187
+ tgNotifyStart: document.getElementById("tg-notify-start"),
188
+
189
+ // Tips feed panel
190
+ tipsFeedPanel: document.getElementById("tips-feed-panel"),
191
+ tipsFeedToggleBtn: document.getElementById("tips-feed-toggle-btn"),
192
+ tipsFeedClose: document.getElementById("tips-feed-close"),
193
+ tipsFeedContent: document.getElementById("tips-feed-content"),
194
+ tipsFeedResize: document.getElementById("tips-feed-resize"),
195
+
196
+ // Right panel
197
+ rightPanel: document.getElementById("right-panel"),
198
+ rightPanelToggleBtn: document.getElementById("right-panel-toggle-btn"),
199
+ rightPanelClose: document.getElementById("right-panel-close"),
200
+
201
+ // File explorer (inside right panel files tab)
202
+ fileExplorerSearch: document.getElementById("file-explorer-search"),
203
+ fileRefreshBtn: document.getElementById("file-refresh-btn"),
204
+ fileTree: document.getElementById("file-tree"),
205
+ filePreview: document.getElementById("file-preview"),
206
+ filePreviewName: document.getElementById("file-preview-name"),
207
+ filePreviewContent: document.getElementById("file-preview-content"),
208
+ filePreviewImage: document.getElementById("file-preview-image"),
209
+ filePreviewClose: document.getElementById("file-preview-close"),
210
+
211
+
212
+ // Git panel (inside right panel git tab)
213
+ gitBranchSelect: document.getElementById("git-branch-select"),
214
+ gitRefreshBtn: document.getElementById("git-refresh-btn"),
215
+ gitStatusList: document.getElementById("git-status-list"),
216
+ gitCommitMsg: document.getElementById("git-commit-msg"),
217
+ gitCommitBtn: document.getElementById("git-commit-btn"),
218
+ gitLogList: document.getElementById("git-log-list"),
219
+
220
+ // MCP manager
221
+ mcpToggleBtn: document.getElementById("mcp-toggle-btn"),
222
+ mcpModal: document.getElementById("mcp-modal"),
223
+ mcpModalClose: document.getElementById("mcp-modal-close"),
224
+ mcpServerList: document.getElementById("mcp-server-list"),
225
+ mcpFormContainer: document.getElementById("mcp-form-container"),
226
+ mcpFormTitle: document.getElementById("mcp-form-title"),
227
+ mcpForm: document.getElementById("mcp-form"),
228
+ mcpName: document.getElementById("mcp-name"),
229
+ mcpType: document.getElementById("mcp-type"),
230
+ mcpStdioFields: document.getElementById("mcp-stdio-fields"),
231
+ mcpUrlFields: document.getElementById("mcp-url-fields"),
232
+ mcpCommand: document.getElementById("mcp-command"),
233
+ mcpArgs: document.getElementById("mcp-args"),
234
+ mcpEnv: document.getElementById("mcp-env"),
235
+ mcpUrl: document.getElementById("mcp-url"),
236
+ mcpFormCancel: document.getElementById("mcp-form-cancel"),
237
+ mcpFormSave: document.getElementById("mcp-form-save"),
238
+ mcpAddBtn: document.getElementById("mcp-add-btn"),
239
+
240
+ // Sidebar toggle (mobile)
241
+ sidebarToggleBtn: document.getElementById("sidebar-toggle-btn"),
242
+ sidebarBackdrop: document.getElementById("sidebar-backdrop"),
243
+
244
+ // Agent sidebar
245
+ agentSidebar: document.getElementById("agent-sidebar"),
246
+ agentSidebarClose: document.getElementById("agent-sidebar-close"),
247
+
248
+ // Orchestrate modal
249
+ orchModal: document.getElementById("orch-modal"),
250
+ orchModalClose: document.getElementById("orch-modal-close"),
251
+ orchModalCancel: document.getElementById("orch-modal-cancel"),
252
+ orchModalRun: document.getElementById("orch-modal-run"),
253
+ orchTaskInput: document.getElementById("orch-task-input"),
254
+
255
+ // Agent monitor
256
+ agentMonitorModal: document.getElementById("agent-monitor-modal"),
257
+ agentMonitorClose: document.getElementById("agent-monitor-close"),
258
+ agentMonitorContent: document.getElementById("agent-monitor-content"),
259
+
260
+ // Add project modal
261
+ openVscodeBtn: document.getElementById("open-vscode-btn"),
262
+ addProjectBtn: document.getElementById("add-project-btn"),
263
+ addProjectModal: document.getElementById("add-project-modal"),
264
+ addProjectClose: document.getElementById("add-project-close"),
265
+ addProjectName: document.getElementById("add-project-name"),
266
+ addProjectConfirm: document.getElementById("add-project-confirm"),
267
+ folderBreadcrumb: document.getElementById("folder-breadcrumb"),
268
+ folderList: document.getElementById("folder-list"),
269
+
270
+ };
@@ -0,0 +1,10 @@
1
+ // Simple pub/sub event bus for cross-module communication
2
+ const bus = {};
3
+
4
+ export function emit(event, data) {
5
+ (bus[event] || []).forEach((fn) => fn(data));
6
+ }
7
+
8
+ export function on(event, fn) {
9
+ (bus[event] ||= []).push(fn);
10
+ }
@@ -0,0 +1,153 @@
1
+ // Plugin Loader — auto-discovers and loads tab-sdk plugins
2
+ //
3
+ // How it works:
4
+ // 1. Fetches GET /api/plugins → list of {name, js, css, source, apiBase} entries
5
+ // 2. Stores manifest for the marketplace UI
6
+ // 3. Only loads plugins the user has enabled (persisted in localStorage)
7
+ //
8
+ // Plugin sources (checked in order, first match wins):
9
+ // 1. Built-in plugins in plugins/<name>/ (client.js, client.css, server.js, config.json)
10
+ // 2. User plugins in ~/.claudeck/plugins/<name>/ (same directory structure)
11
+
12
+ const STORAGE_KEY = 'claudeck-enabled-plugins';
13
+ const ORDER_KEY = 'claudeck-plugin-order';
14
+ let availablePlugins = [];
15
+ const loadedPlugins = new Set();
16
+
17
+ /** Maps plugin file name → tab ID registered by that plugin */
18
+ const pluginTabIds = new Map();
19
+
20
+ /** Plugin descriptions for the marketplace. order: lower = higher in the list */
21
+ const pluginMeta = {
22
+ 'claude-editor': { description: 'Edit CLAUDE.md project instructions directly in the UI', icon: '📝', order: 5 },
23
+ 'event-stream': { description: 'Real-time WebSocket event viewer with filtering and search', icon: '⚡', order: 10 },
24
+ 'repos': { description: 'Git repository and group management with tree view', icon: '📁', order: 20 },
25
+ 'linear': { description: 'Linear issue tracking with settings and team management', icon: '📋', order: 25 },
26
+ 'tasks': { description: 'Todo list with priority levels and brag tracking', icon: '✅', order: 30 },
27
+ 'tic-tac-toe': { description: 'Classic tic-tac-toe game', icon: '🎮', order: 90 },
28
+ 'sudoku': { description: 'Sudoku puzzle game', icon: '🧩', order: 91 },
29
+ };
30
+ const defaultMeta = { description: 'A tab-sdk plugin', icon: '🧩', order: 100 };
31
+
32
+ export function getAvailablePlugins() {
33
+ return availablePlugins;
34
+ }
35
+
36
+ export function getEnabledPluginNames() {
37
+ try {
38
+ const raw = localStorage.getItem(STORAGE_KEY);
39
+ return raw ? JSON.parse(raw) : [];
40
+ } catch { return []; }
41
+ }
42
+
43
+ export function setEnabledPluginNames(names) {
44
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(names));
45
+ }
46
+
47
+ export function getPluginMeta(name) {
48
+ return pluginMeta[name] || defaultMeta;
49
+ }
50
+
51
+ export function getPluginOrder() {
52
+ try {
53
+ const raw = localStorage.getItem(ORDER_KEY);
54
+ return raw ? JSON.parse(raw) : [];
55
+ } catch { return []; }
56
+ }
57
+
58
+ export function setPluginOrder(names) {
59
+ localStorage.setItem(ORDER_KEY, JSON.stringify(names));
60
+ }
61
+
62
+ /** Return available plugins sorted by saved order (unsorted ones go to end by meta.order) */
63
+ export function getSortedPlugins() {
64
+ const saved = getPluginOrder();
65
+ const all = [...availablePlugins];
66
+ all.sort((a, b) => {
67
+ const ai = saved.indexOf(a.name);
68
+ const bi = saved.indexOf(b.name);
69
+ // Both in saved order — use saved positions
70
+ if (ai !== -1 && bi !== -1) return ai - bi;
71
+ // Only one in saved order — it comes first
72
+ if (ai !== -1) return -1;
73
+ if (bi !== -1) return 1;
74
+ // Neither saved — fall back to meta.order
75
+ return (getPluginMeta(a.name).order ?? 100) - (getPluginMeta(b.name).order ?? 100);
76
+ });
77
+ return all;
78
+ }
79
+
80
+ /** Optional hook: set by tab-sdk to get current registered tab IDs */
81
+ let _getRegisteredTabIds = null;
82
+ export function setTabIdResolver(fn) { _getRegisteredTabIds = fn; }
83
+
84
+ async function loadPlugin(plugin) {
85
+ if (loadedPlugins.has(plugin.name)) return;
86
+ if (plugin.css) {
87
+ const link = document.createElement('link');
88
+ link.rel = 'stylesheet';
89
+ link.href = `/${plugin.css}`;
90
+ link.dataset.plugin = plugin.name;
91
+ document.head.appendChild(link);
92
+ }
93
+
94
+ const before = _getRegisteredTabIds ? new Set(_getRegisteredTabIds()) : null;
95
+
96
+ try {
97
+ await import(`/${plugin.js}`);
98
+ loadedPlugins.add(plugin.name);
99
+ console.log(`Plugin loaded: ${plugin.name}`);
100
+
101
+ // Auto-detect which tab ID this plugin registered
102
+ if (before && _getRegisteredTabIds) {
103
+ for (const id of _getRegisteredTabIds()) {
104
+ if (!before.has(id)) {
105
+ pluginTabIds.set(plugin.name, id);
106
+ }
107
+ }
108
+ }
109
+ } catch (err) {
110
+ console.error(`Plugin failed: ${plugin.name}`, err);
111
+ }
112
+ }
113
+
114
+ export async function loadPluginByName(name) {
115
+ const plugin = availablePlugins.find(p => p.name === name);
116
+ if (plugin) await loadPlugin(plugin);
117
+ }
118
+
119
+ export function isPluginLoaded(name) {
120
+ return loadedPlugins.has(name);
121
+ }
122
+
123
+ /** Record which tab ID a plugin registered (called from tab-sdk) */
124
+ export function trackPluginTab(pluginName, tabId) {
125
+ pluginTabIds.set(pluginName, tabId);
126
+ }
127
+
128
+ /** Get the tab ID registered by a plugin */
129
+ export function getPluginTabId(pluginName) {
130
+ return pluginTabIds.get(pluginName);
131
+ }
132
+
133
+ /** Get all plugin-name → tab-id mappings */
134
+ export function getPluginTabMap() {
135
+ return pluginTabIds;
136
+ }
137
+
138
+ export async function loadPlugins() {
139
+ try {
140
+ const res = await fetch('/api/plugins');
141
+ if (!res.ok) { console.warn('Plugin discovery failed:', res.status); return; }
142
+
143
+ availablePlugins = await res.json();
144
+ if (!availablePlugins.length) return;
145
+
146
+ // Only load enabled plugins
147
+ const enabled = getEnabledPluginNames();
148
+ const toLoad = availablePlugins.filter(p => enabled.includes(p.name));
149
+ await Promise.all(toLoad.map(loadPlugin));
150
+ } catch (err) {
151
+ console.error('Plugin loader error:', err);
152
+ }
153
+ }
@@ -0,0 +1,39 @@
1
+ // Centralized reactive state store
2
+ const state = {
3
+ view: "home",
4
+ ws: null,
5
+ sessionId: null,
6
+ parallelMode: false,
7
+ streamingCharCount: 0,
8
+ prompts: [],
9
+ workflows: [],
10
+ agents: [],
11
+ projectsData: [],
12
+ attachedFiles: [],
13
+ imageAttachments: [],
14
+ allProjectFiles: [],
15
+ mermaidCounter: 0,
16
+ savedChatArea: null,
17
+ backgroundSessions: new Map(),
18
+ notificationsEnabled: false,
19
+ sessionTokens: { input: 0, output: 0, cacheRead: 0, cacheCreation: 0 },
20
+ };
21
+
22
+ const listeners = {};
23
+
24
+ export function getState(key) {
25
+ return state[key];
26
+ }
27
+
28
+ export function setState(key, val) {
29
+ state[key] = val;
30
+ emit(key, val);
31
+ }
32
+
33
+ export function on(key, fn) {
34
+ (listeners[key] ||= []).push(fn);
35
+ }
36
+
37
+ function emit(key, val) {
38
+ (listeners[key] || []).forEach((fn) => fn(val));
39
+ }
@@ -0,0 +1,25 @@
1
+ // Pure utility functions
2
+
3
+ export function escapeHtml(str) {
4
+ const div = document.createElement("div");
5
+ div.textContent = str;
6
+ return div.innerHTML;
7
+ }
8
+
9
+ export function slugify(title) {
10
+ return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
11
+ }
12
+
13
+ export function getToolDetail(name, input) {
14
+ if (!input) return "";
15
+ if (input.file_path) return escapeHtml(input.file_path);
16
+ if (input.command) return escapeHtml(input.command.slice(0, 80));
17
+ if (input.pattern) return escapeHtml(input.pattern);
18
+ if (input.query) return escapeHtml(input.query);
19
+ if (input.prompt) return escapeHtml(input.prompt.slice(0, 80));
20
+ return "";
21
+ }
22
+
23
+ export function scrollToBottom(pane) {
24
+ pane.messagesDiv.scrollTop = pane.messagesDiv.scrollHeight;
25
+ }
@@ -0,0 +1,64 @@
1
+ // WebSocket connection + message dispatch with exponential backoff
2
+ import { $ } from './dom.js';
3
+ import { getState, setState } from './store.js';
4
+ import { emit } from './events.js';
5
+
6
+ let backoffAttempt = 0;
7
+ let hasConnectedBefore = false;
8
+
9
+ const BACKOFF_BASE_MS = 2000;
10
+ const BACKOFF_FACTOR = 2;
11
+ const BACKOFF_MAX_MS = 30000;
12
+
13
+ function getBackoffDelay() {
14
+ const delay = Math.min(BACKOFF_BASE_MS * Math.pow(BACKOFF_FACTOR, backoffAttempt), BACKOFF_MAX_MS);
15
+ // Add 0-25% jitter
16
+ const jitter = delay * Math.random() * 0.25;
17
+ return delay + jitter;
18
+ }
19
+
20
+ export function connectWebSocket() {
21
+ const protocol = location.protocol === "https:" ? "wss:" : "ws:";
22
+ const ws = new WebSocket(`${protocol}//${location.host}/ws`);
23
+ setState("ws", ws);
24
+
25
+ ws.onopen = () => {
26
+ console.log("WebSocket connected");
27
+ $.connectionDot.className = "term-dot connected";
28
+ $.connectionText.textContent = "connected";
29
+ $.connectionText.className = "term-status ok";
30
+
31
+ // Reset backoff on successful connection
32
+ backoffAttempt = 0;
33
+
34
+ if (hasConnectedBefore) {
35
+ emit("ws:reconnected");
36
+ } else {
37
+ hasConnectedBefore = true;
38
+ emit("ws:connected");
39
+ }
40
+ };
41
+
42
+ ws.onmessage = (event) => {
43
+ const msg = JSON.parse(event.data);
44
+ emit("ws:message", msg);
45
+ };
46
+
47
+ ws.onclose = () => {
48
+ const delay = getBackoffDelay();
49
+ backoffAttempt++;
50
+ console.log(`WebSocket disconnected, reconnecting in ${Math.round(delay)}ms (attempt ${backoffAttempt})...`);
51
+ $.connectionDot.className = "term-dot reconnecting";
52
+ $.connectionText.textContent = "reconnecting";
53
+ $.connectionText.className = "term-status";
54
+ emit("ws:disconnected");
55
+ setTimeout(connectWebSocket, delay);
56
+ };
57
+
58
+ ws.onerror = (err) => {
59
+ console.error("WebSocket error:", err);
60
+ $.connectionDot.className = "term-dot";
61
+ $.connectionText.textContent = "disconnected";
62
+ $.connectionText.className = "term-status";
63
+ };
64
+ }