clodds 1.2.2 → 1.2.4

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.
@@ -0,0 +1,356 @@
1
+ /**
2
+ * Command palette for slash commands
3
+ */
4
+
5
+ const CAT_ICONS = {
6
+ 'Core': '\u2699',
7
+ 'Market Data': '\uD83D\uDCCA',
8
+ 'Polymarket': '\uD83D\uDFE3',
9
+ 'Kalshi': '\uD83C\uDFAF',
10
+ 'Hyperliquid': '\uD83D\uDFE2',
11
+ 'CEX Futures': '\uD83D\uDCC8',
12
+ 'Sportsbooks': '\u26BD',
13
+ 'Manifold': '\uD83C\uDFB2',
14
+ 'Metaculus': '\uD83D\uDD2E',
15
+ 'PredictIt': '\uD83C\uDFDB\uFE0F',
16
+ 'Predict.fun': '\uD83C\uDFAE',
17
+ 'Opinion': '\uD83D\uDCAC',
18
+ 'Veil': '\uD83D\uDD12',
19
+ 'AgentBets': '\uD83E\uDD16',
20
+ 'Solana DeFi': '\uD83D\uDFE1',
21
+ 'EVM DeFi': '\uD83D\uDD37',
22
+ 'Virtuals & Agents': '\uD83E\uDD16',
23
+ 'Bots & Execution': '\u26A1',
24
+ 'Portfolio': '\uD83D\uDCBC',
25
+ 'Strategy': '\uD83E\uDDE0',
26
+ 'Wallet': '\uD83D\uDC5B',
27
+ 'Automation': '\uD83D\uDD04',
28
+ 'Config': '\uD83D\uDD27',
29
+ 'Tools': '\uD83E\uDDF0',
30
+ 'Bittensor': '\uD83E\uDDE0',
31
+ 'Other': '\uD83D\uDCE6',
32
+ };
33
+
34
+ // Display order for categories in the palette
35
+ const CAT_ORDER = [
36
+ 'Core',
37
+ 'Market Data',
38
+ 'Polymarket',
39
+ 'Kalshi',
40
+ 'Sportsbooks',
41
+ 'Manifold',
42
+ 'Metaculus',
43
+ 'PredictIt',
44
+ 'Predict.fun',
45
+ 'Opinion',
46
+ 'AgentBets',
47
+ 'Veil',
48
+ 'Hyperliquid',
49
+ 'CEX Futures',
50
+ 'Solana DeFi',
51
+ 'EVM DeFi',
52
+ 'Virtuals & Agents',
53
+ 'Portfolio',
54
+ 'Strategy',
55
+ 'Wallet',
56
+ 'Bots & Execution',
57
+ 'Automation',
58
+ 'Tools',
59
+ 'Bittensor',
60
+ 'Config',
61
+ 'Other',
62
+ ];
63
+
64
+ // Super-categories group multiple categories under a section header
65
+ const SUPER_CATEGORIES = {
66
+ 'Prediction Markets': ['Polymarket', 'Kalshi', 'Sportsbooks', 'Manifold', 'Metaculus', 'PredictIt', 'Predict.fun', 'Opinion', 'AgentBets', 'Veil'],
67
+ 'Futures & Perps': ['Hyperliquid', 'CEX Futures'],
68
+ 'DeFi': ['Solana DeFi', 'EVM DeFi', 'Virtuals & Agents'],
69
+ };
70
+
71
+ // Reverse lookup: category → super-category
72
+ const CAT_TO_SUPER = {};
73
+ for (const [superCat, cats] of Object.entries(SUPER_CATEGORIES)) {
74
+ for (const cat of cats) CAT_TO_SUPER[cat] = superCat;
75
+ }
76
+
77
+ export class CommandPalette {
78
+ constructor(paletteEl, inputEl, sendBtnEl) {
79
+ this.paletteEl = paletteEl;
80
+ this.inputEl = inputEl;
81
+ this.sendBtnEl = sendBtnEl;
82
+ this.allCommands = [];
83
+ this.filteredCommands = [];
84
+ this.activeIndex = -1;
85
+ this.visible = false;
86
+ this.subcommandMode = false;
87
+ this.onExecute = null;
88
+ this._loadCommands();
89
+ }
90
+
91
+ async _loadCommands() {
92
+ try {
93
+ const r = await fetch('/api/commands');
94
+ if (!r.ok) throw new Error('non-ok');
95
+ const data = await r.json();
96
+ this.allCommands = data.commands || [];
97
+ } catch {
98
+ // Retry once after 3s if initial load fails
99
+ if (!this._retried) {
100
+ this._retried = true;
101
+ setTimeout(() => this._loadCommands(), 3000);
102
+ }
103
+ }
104
+ }
105
+
106
+ _esc(text) {
107
+ return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
108
+ }
109
+
110
+ show(filter) {
111
+ const text = filter.slice(1);
112
+ const spaceIdx = text.indexOf(' ');
113
+
114
+ if (spaceIdx > 0) {
115
+ this._showSubcommands(text, spaceIdx);
116
+ return;
117
+ }
118
+
119
+ this.subcommandMode = false;
120
+ const query = text.toLowerCase();
121
+ this.filteredCommands = query
122
+ ? this.allCommands.filter(c =>
123
+ c.name.toLowerCase().includes(query) ||
124
+ c.description.toLowerCase().includes(query) ||
125
+ c.category.toLowerCase().includes(query))
126
+ : this.allCommands;
127
+
128
+ if (!this.filteredCommands.length) { this.hide(); return; }
129
+
130
+ const groups = {};
131
+ for (const cmd of this.filteredCommands) {
132
+ (groups[cmd.category] = groups[cmd.category] || []).push(cmd);
133
+ }
134
+
135
+ let html = '<div class="cmd-palette-header">'
136
+ + '<span>Commands</span>'
137
+ + '<span class="cmd-palette-hint"><kbd>\u2191\u2193</kbd> navigate <kbd>Tab</kbd> select <kbd>Esc</kbd> close</span>'
138
+ + '</div>';
139
+
140
+ let idx = 0;
141
+ const sortedCategories = Object.keys(groups).sort((a, b) => {
142
+ const ai = CAT_ORDER.indexOf(a);
143
+ const bi = CAT_ORDER.indexOf(b);
144
+ return (ai === -1 ? 999 : ai) - (bi === -1 ? 999 : bi);
145
+ });
146
+ let lastSuperCat = null;
147
+ for (const category of sortedCategories) {
148
+ // Insert super-category header when entering a new group
149
+ const superCat = CAT_TO_SUPER[category] || null;
150
+ if (superCat && superCat !== lastSuperCat) {
151
+ html += '<div class="cmd-super-header">' + this._esc(superCat) + '</div>';
152
+ }
153
+ lastSuperCat = superCat;
154
+
155
+ const cmds = groups[category];
156
+ const icon = CAT_ICONS[category] || '\uD83D\uDCE6';
157
+ html += '<div class="cmd-category">'
158
+ + '<div class="cmd-category-label">'
159
+ + '<span class="cmd-category-icon">' + icon + '</span>'
160
+ + '<span>' + this._esc(category) + '</span>'
161
+ + '<span class="cmd-category-count">' + cmds.length + '</span>'
162
+ + '</div>';
163
+ for (const cmd of cmds) {
164
+ const hasSubs = cmd.subcommands?.length > 0;
165
+ html += '<div class="cmd-item' + (idx === this.activeIndex ? ' active' : '') + '" data-index="' + idx + '" data-name="' + this._esc(cmd.name) + '">'
166
+ + '<span class="cmd-item-name">' + this._esc(cmd.name) + '</span>'
167
+ + '<span class="cmd-item-desc">' + this._esc(cmd.description) + (hasSubs ? ' \u203A' : '') + '</span></div>';
168
+ idx++;
169
+ }
170
+ html += '</div>';
171
+ }
172
+
173
+ this._render(html);
174
+ }
175
+
176
+ _showSubcommands(text, spaceIdx) {
177
+ const parentCmd = '/' + text.slice(0, spaceIdx);
178
+ const subQuery = text.slice(spaceIdx + 1).toLowerCase();
179
+ const parent = this.allCommands.find(c => c.name === parentCmd);
180
+ if (!parent?.subcommands?.length) { this.hide(); return; }
181
+
182
+ const subs = subQuery
183
+ ? parent.subcommands.filter(s =>
184
+ s.name.toLowerCase().includes(subQuery) ||
185
+ s.description.toLowerCase().includes(subQuery) ||
186
+ (s.category || '').toLowerCase().includes(subQuery))
187
+ : parent.subcommands;
188
+
189
+ if (!subs.length) { this.hide(); return; }
190
+
191
+ this.filteredCommands = subs.map(s => ({
192
+ name: s.name,
193
+ description: s.description,
194
+ category: s.category || 'General',
195
+ fullName: parentCmd + ' ' + s.name,
196
+ }));
197
+ this.subcommandMode = true;
198
+
199
+ let html = '<div class="cmd-palette-header">'
200
+ + '<span>' + this._esc(parentCmd) + '</span>'
201
+ + '<span class="cmd-palette-hint"><kbd>\u2191\u2193</kbd> navigate <kbd>Tab</kbd> select <kbd>Esc</kbd> close</span>'
202
+ + '</div>';
203
+ html += '<div class="cmd-back" data-action="back">\u2190 All commands</div>';
204
+
205
+ const subGroups = {};
206
+ for (const cmd of this.filteredCommands) {
207
+ (subGroups[cmd.category] = subGroups[cmd.category] || []).push(cmd);
208
+ }
209
+
210
+ let idx = 0;
211
+ for (const [section, cmds] of Object.entries(subGroups)) {
212
+ html += '<div class="cmd-category">'
213
+ + '<div class="cmd-category-label">'
214
+ + '<span>' + this._esc(section) + '</span>'
215
+ + '<span class="cmd-category-count">' + cmds.length + '</span>'
216
+ + '</div>';
217
+ for (const cmd of cmds) {
218
+ html += '<div class="cmd-item' + (idx === this.activeIndex ? ' active' : '') + '" data-index="' + idx + '" data-name="' + this._esc(cmd.fullName) + '">'
219
+ + '<span class="cmd-item-name">' + this._esc(cmd.name) + '</span>'
220
+ + '<span class="cmd-item-desc">' + this._esc(cmd.description) + '</span></div>';
221
+ idx++;
222
+ }
223
+ html += '</div>';
224
+ }
225
+
226
+ this._render(html);
227
+
228
+ const backBtn = this.paletteEl.querySelector('.cmd-back');
229
+ if (backBtn) {
230
+ backBtn.addEventListener('click', () => {
231
+ this.inputEl.value = '/';
232
+ this.show('/');
233
+ this.inputEl.focus();
234
+ });
235
+ }
236
+ }
237
+
238
+ _render(html) {
239
+ this.paletteEl.innerHTML = html;
240
+ this.paletteEl.classList.add('visible');
241
+ this.visible = true;
242
+
243
+ this.paletteEl.querySelectorAll('.cmd-item').forEach(item => {
244
+ item.addEventListener('click', () => {
245
+ const name = item.dataset.name;
246
+ if (this.subcommandMode) {
247
+ this.inputEl.value = name + ' ';
248
+ } else {
249
+ this.inputEl.value = name + ' ';
250
+ const parent = this.allCommands.find(c => c.name === name);
251
+ if (parent?.subcommands?.length) {
252
+ this.activeIndex = -1;
253
+ this.show(this.inputEl.value);
254
+ this.sendBtnEl.classList.add('active');
255
+ return;
256
+ }
257
+ }
258
+ this.hide();
259
+ this.inputEl.focus();
260
+ this.sendBtnEl.classList.add('active');
261
+ });
262
+ });
263
+ }
264
+
265
+ hide() {
266
+ this.paletteEl.classList.remove('visible');
267
+ this.visible = false;
268
+ this.activeIndex = -1;
269
+ }
270
+
271
+ handleInput(text) {
272
+ if (text.startsWith('/')) {
273
+ const afterSlash = text.slice(1);
274
+ const spaceIdx = afterSlash.indexOf(' ');
275
+ if (spaceIdx === -1) {
276
+ this.activeIndex = -1;
277
+ this.show(text);
278
+ } else {
279
+ const parentCmd = '/' + afterSlash.slice(0, spaceIdx);
280
+ const parent = this.allCommands.find(c => c.name === parentCmd);
281
+ if (parent?.subcommands?.length) {
282
+ this.activeIndex = -1;
283
+ this.show(text);
284
+ } else {
285
+ this.hide();
286
+ }
287
+ }
288
+ } else {
289
+ this.hide();
290
+ }
291
+ }
292
+
293
+ handleKeydown(e) {
294
+ if (!this.visible) return false;
295
+
296
+ if (e.key === 'ArrowDown') {
297
+ e.preventDefault();
298
+ this.activeIndex = Math.min(this.activeIndex + 1, this.filteredCommands.length - 1);
299
+ this.show(this.inputEl.value.startsWith('/') ? this.inputEl.value : '/' + this.inputEl.value);
300
+ const active = this.paletteEl.querySelector('.cmd-item.active');
301
+ if (active) active.scrollIntoView({ block: 'nearest' });
302
+ return true;
303
+ }
304
+ if (e.key === 'ArrowUp') {
305
+ e.preventDefault();
306
+ this.activeIndex = Math.max(this.activeIndex - 1, 0);
307
+ this.show(this.inputEl.value.startsWith('/') ? this.inputEl.value : '/' + this.inputEl.value);
308
+ const active = this.paletteEl.querySelector('.cmd-item.active');
309
+ if (active) active.scrollIntoView({ block: 'nearest' });
310
+ return true;
311
+ }
312
+ if (e.key === 'Tab') {
313
+ e.preventDefault();
314
+ if (this.activeIndex >= 0 && this.activeIndex < this.filteredCommands.length) {
315
+ const sel = this.filteredCommands[this.activeIndex];
316
+ if (this.subcommandMode) {
317
+ this.inputEl.value = sel.fullName + ' ';
318
+ } else {
319
+ this.inputEl.value = sel.name + ' ';
320
+ const parent = this.allCommands.find(c => c.name === sel.name);
321
+ if (parent?.subcommands?.length) {
322
+ this.activeIndex = -1;
323
+ this.show(this.inputEl.value);
324
+ this.sendBtnEl.classList.add('active');
325
+ return true;
326
+ }
327
+ }
328
+ this.hide();
329
+ this.sendBtnEl.classList.add('active');
330
+ }
331
+ return true;
332
+ }
333
+ if (e.key === 'Enter' && !e.shiftKey) {
334
+ if (this.activeIndex >= 0 && this.activeIndex < this.filteredCommands.length) {
335
+ e.preventDefault();
336
+ const sel = this.filteredCommands[this.activeIndex];
337
+ if (this.subcommandMode) {
338
+ this.inputEl.value = sel.fullName + ' ';
339
+ } else {
340
+ this.inputEl.value = sel.name + ' ';
341
+ }
342
+ this.hide();
343
+ this.sendBtnEl.classList.add('active');
344
+ return true;
345
+ }
346
+ // Palette visible but no selection — close palette, let Enter send normally
347
+ this.hide();
348
+ return false;
349
+ }
350
+ if (e.key === 'Escape') {
351
+ this.hide();
352
+ return true;
353
+ }
354
+ return false;
355
+ }
356
+ }