claudeck 1.3.1 → 1.4.1
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.
- package/README.md +13 -9
- package/db/sqlite.js +1697 -0
- package/db.js +3 -1645
- package/package.json +2 -1
- package/plugins/claude-editor/manifest.json +10 -0
- package/plugins/linear/manifest.json +10 -0
- package/plugins/repos/manifest.json +10 -0
- package/public/css/ui/messages.css +25 -0
- package/public/css/ui/right-panel.css +207 -0
- package/public/css/ui/settings.css +75 -0
- package/public/index.html +7 -0
- package/public/js/components/settings-modal.js +65 -0
- package/public/js/core/api.js +23 -6
- package/public/js/core/events.js +11 -0
- package/public/js/core/plugin-loader.js +96 -11
- package/public/js/core/store.js +11 -0
- package/public/js/core/ws.js +12 -0
- package/public/js/features/chat.js +4 -0
- package/public/js/features/sessions.js +102 -10
- package/public/js/main.js +1 -0
- package/public/js/panels/assistant-bot.js +16 -0
- package/public/js/panels/dev-docs.js +2 -2
- package/public/js/panels/memory.js +1 -0
- package/public/js/ui/context-gauge.js +10 -1
- package/public/js/ui/header-dropdowns.js +30 -0
- package/public/js/ui/input-meta.js +13 -6
- package/public/js/ui/max-turns.js +6 -3
- package/public/js/ui/messages.js +42 -0
- package/public/js/ui/model-selector.js +1 -0
- package/public/js/ui/parallel.js +2 -4
- package/public/js/ui/permissions.js +1 -0
- package/public/js/ui/tab-sdk.js +395 -176
- package/public/style.css +1 -0
- package/server/agent-loop.js +26 -26
- package/server/memory-extractor.js +4 -4
- package/server/memory-injector.js +11 -11
- package/server/memory-optimizer.js +19 -15
- package/server/notification-logger.js +5 -5
- package/server/orchestrator.js +15 -15
- package/server/push-sender.js +2 -2
- package/server/routes/agents.js +2 -2
- package/server/routes/marketplace.js +316 -0
- package/server/routes/memory.js +20 -20
- package/server/routes/messages.js +41 -10
- package/server/routes/notifications.js +20 -20
- package/server/routes/sessions.js +17 -17
- package/server/routes/stats.js +37 -37
- package/server/routes/worktrees.js +9 -9
- package/server/summarizer.js +3 -3
- package/server/ws-handler.js +163 -58
- package/server.js +20 -2
- package/plugins/event-stream/client.css +0 -207
- package/plugins/event-stream/client.js +0 -271
- package/plugins/sudoku/client.css +0 -196
- package/plugins/sudoku/client.js +0 -329
- package/plugins/tasks/client.css +0 -414
- package/plugins/tasks/client.js +0 -394
- package/plugins/tasks/server.js +0 -116
- package/plugins/tic-tac-toe/client.css +0 -167
- package/plugins/tic-tac-toe/client.js +0 -241
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claudeck",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A browser-based UI for Claude Code — chat, run workflows, manage MCP servers, track costs, and orchestrate autonomous agents from a local web interface. Installable as a PWA.",
|
|
6
6
|
"main": "server.js",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"cli.js",
|
|
12
12
|
"server.js",
|
|
13
13
|
"db.js",
|
|
14
|
+
"db/",
|
|
14
15
|
"server/",
|
|
15
16
|
"public/",
|
|
16
17
|
"config/",
|
|
@@ -25,6 +25,31 @@
|
|
|
25
25
|
gap: 18px;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/* ── Load-more indicator (lazy loading) ─────────────── */
|
|
29
|
+
.load-more-indicator {
|
|
30
|
+
display: flex;
|
|
31
|
+
align-items: center;
|
|
32
|
+
justify-content: center;
|
|
33
|
+
gap: 8px;
|
|
34
|
+
padding: 10px 0;
|
|
35
|
+
color: var(--text-muted);
|
|
36
|
+
font-size: 0.8rem;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.load-more-spinner {
|
|
40
|
+
display: inline-block;
|
|
41
|
+
width: 14px;
|
|
42
|
+
height: 14px;
|
|
43
|
+
border: 2px solid var(--border);
|
|
44
|
+
border-top-color: var(--accent);
|
|
45
|
+
border-radius: 50%;
|
|
46
|
+
animation: load-more-spin 0.6s linear infinite;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@keyframes load-more-spin {
|
|
50
|
+
to { transform: rotate(360deg); }
|
|
51
|
+
}
|
|
52
|
+
|
|
28
53
|
.messages:empty::after {
|
|
29
54
|
content: "";
|
|
30
55
|
display: none;
|
|
@@ -486,3 +486,210 @@
|
|
|
486
486
|
filter: brightness(1.1);
|
|
487
487
|
box-shadow: var(--glow-strong);
|
|
488
488
|
}
|
|
489
|
+
|
|
490
|
+
/* ── Marketplace tabs ── */
|
|
491
|
+
|
|
492
|
+
.marketplace-tabs {
|
|
493
|
+
display: flex;
|
|
494
|
+
gap: 4px;
|
|
495
|
+
margin-top: 12px;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
.marketplace-tab {
|
|
499
|
+
padding: 6px 16px;
|
|
500
|
+
border-radius: var(--radius-md);
|
|
501
|
+
font-size: 12px;
|
|
502
|
+
font-weight: 500;
|
|
503
|
+
font-family: var(--font-sans);
|
|
504
|
+
cursor: pointer;
|
|
505
|
+
transition: all 0.2s var(--ease-smooth);
|
|
506
|
+
border: 1px solid transparent;
|
|
507
|
+
background: none;
|
|
508
|
+
color: var(--text-dim);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.marketplace-tab:hover {
|
|
512
|
+
color: var(--text);
|
|
513
|
+
background: var(--bg-tertiary);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
.marketplace-tab.active {
|
|
517
|
+
color: var(--accent);
|
|
518
|
+
background: var(--accent-dim);
|
|
519
|
+
border-color: var(--accent);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.marketplace-tab-content {
|
|
523
|
+
flex: 1;
|
|
524
|
+
overflow-y: auto;
|
|
525
|
+
display: flex;
|
|
526
|
+
flex-direction: column;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
.marketplace-tab-content::-webkit-scrollbar {
|
|
530
|
+
width: 4px;
|
|
531
|
+
}
|
|
532
|
+
.marketplace-tab-content::-webkit-scrollbar-thumb {
|
|
533
|
+
background: var(--border);
|
|
534
|
+
border-radius: 2px;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
.marketplace-tab-content > .marketplace-subtitle {
|
|
538
|
+
padding: 12px 16px 4px;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
.marketplace-tab-content > .marketplace-list {
|
|
542
|
+
flex: 1;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
.marketplace-tab-content > .marketplace-empty {
|
|
546
|
+
flex: 1;
|
|
547
|
+
display: flex;
|
|
548
|
+
flex-direction: column;
|
|
549
|
+
align-items: center;
|
|
550
|
+
justify-content: center;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
.marketplace-tab-content > .marketplace-empty a {
|
|
554
|
+
color: var(--accent);
|
|
555
|
+
text-decoration: none;
|
|
556
|
+
margin-top: 8px;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
.marketplace-tab-content > .marketplace-empty a:hover {
|
|
560
|
+
text-decoration: underline;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/* ── Community plugin items ── */
|
|
564
|
+
|
|
565
|
+
.marketplace-community-item {
|
|
566
|
+
cursor: default;
|
|
567
|
+
padding: 12px;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
.marketplace-item-meta {
|
|
571
|
+
display: flex;
|
|
572
|
+
gap: 8px;
|
|
573
|
+
margin-top: 4px;
|
|
574
|
+
font-size: 10px;
|
|
575
|
+
color: var(--text-dim);
|
|
576
|
+
font-family: var(--font-mono);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
.marketplace-author {
|
|
580
|
+
opacity: 0.7;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
.marketplace-version {
|
|
584
|
+
opacity: 0.5;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
.marketplace-version-old {
|
|
588
|
+
opacity: 0.4;
|
|
589
|
+
text-decoration: line-through;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
.marketplace-source {
|
|
593
|
+
font-size: 9px;
|
|
594
|
+
font-family: var(--font-display);
|
|
595
|
+
padding: 1px 6px;
|
|
596
|
+
border-radius: 4px;
|
|
597
|
+
text-transform: uppercase;
|
|
598
|
+
letter-spacing: 0.06em;
|
|
599
|
+
vertical-align: middle;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
.marketplace-source.community {
|
|
603
|
+
color: var(--purple);
|
|
604
|
+
background: color-mix(in srgb, var(--purple) 15%, transparent);
|
|
605
|
+
border: 1px solid color-mix(in srgb, var(--purple) 30%, transparent);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
.marketplace-source.server {
|
|
609
|
+
color: var(--orange, #f0a040);
|
|
610
|
+
background: color-mix(in srgb, var(--orange, #f0a040) 15%, transparent);
|
|
611
|
+
border: 1px solid color-mix(in srgb, var(--orange, #f0a040) 30%, transparent);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/* ── Action buttons (install / uninstall / update) ── */
|
|
615
|
+
|
|
616
|
+
.marketplace-item-actions {
|
|
617
|
+
flex-shrink: 0;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
.marketplace-action-btn {
|
|
621
|
+
padding: 5px 14px;
|
|
622
|
+
border-radius: var(--radius-md);
|
|
623
|
+
font-size: 11px;
|
|
624
|
+
font-weight: 500;
|
|
625
|
+
font-family: var(--font-sans);
|
|
626
|
+
cursor: pointer;
|
|
627
|
+
transition: all 0.2s var(--ease-smooth);
|
|
628
|
+
border: 1px solid var(--border);
|
|
629
|
+
white-space: nowrap;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
.marketplace-action-btn:disabled {
|
|
633
|
+
opacity: 0.5;
|
|
634
|
+
cursor: not-allowed;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
.marketplace-install-btn {
|
|
638
|
+
background: var(--accent);
|
|
639
|
+
color: #fff;
|
|
640
|
+
border-color: var(--accent);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
.marketplace-install-btn:hover:not(:disabled) {
|
|
644
|
+
filter: brightness(1.1);
|
|
645
|
+
box-shadow: var(--glow);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
.marketplace-uninstall-btn {
|
|
649
|
+
background: none;
|
|
650
|
+
color: var(--text-dim);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
.marketplace-uninstall-btn:hover:not(:disabled) {
|
|
654
|
+
color: var(--red, #e54);
|
|
655
|
+
border-color: var(--red, #e54);
|
|
656
|
+
background: color-mix(in srgb, var(--red, #e54) 10%, transparent);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
.marketplace-update-btn {
|
|
660
|
+
background: var(--purple);
|
|
661
|
+
color: #fff;
|
|
662
|
+
border-color: var(--purple);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
.marketplace-update-btn:hover:not(:disabled) {
|
|
666
|
+
filter: brightness(1.1);
|
|
667
|
+
box-shadow: 0 0 8px color-mix(in srgb, var(--purple) 40%, transparent);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/* ── Loading state ── */
|
|
671
|
+
|
|
672
|
+
.marketplace-loading {
|
|
673
|
+
display: flex;
|
|
674
|
+
flex-direction: column;
|
|
675
|
+
align-items: center;
|
|
676
|
+
justify-content: center;
|
|
677
|
+
gap: 12px;
|
|
678
|
+
padding: 48px 16px;
|
|
679
|
+
color: var(--text-dim);
|
|
680
|
+
font-size: 12px;
|
|
681
|
+
font-family: var(--font-sans);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
.marketplace-spinner {
|
|
685
|
+
width: 24px;
|
|
686
|
+
height: 24px;
|
|
687
|
+
border: 2px solid var(--border);
|
|
688
|
+
border-top-color: var(--accent);
|
|
689
|
+
border-radius: 50%;
|
|
690
|
+
animation: spin 0.8s linear infinite;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
@keyframes spin {
|
|
694
|
+
to { transform: rotate(360deg); }
|
|
695
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
.settings-modal {
|
|
2
|
+
width: 380px;
|
|
3
|
+
max-width: 90vw;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.settings-list {
|
|
7
|
+
padding: 16px;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.settings-row {
|
|
11
|
+
display: flex;
|
|
12
|
+
align-items: center;
|
|
13
|
+
justify-content: space-between;
|
|
14
|
+
gap: 12px;
|
|
15
|
+
padding: 10px 0;
|
|
16
|
+
cursor: pointer;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.settings-row + .settings-row {
|
|
20
|
+
border-top: 1px solid var(--border);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.settings-label {
|
|
24
|
+
display: flex;
|
|
25
|
+
flex-direction: column;
|
|
26
|
+
gap: 2px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.settings-label strong {
|
|
30
|
+
font-size: 13px;
|
|
31
|
+
color: var(--text-primary);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.settings-label small {
|
|
35
|
+
font-size: 11px;
|
|
36
|
+
color: var(--text-secondary);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.settings-toggle {
|
|
40
|
+
appearance: none;
|
|
41
|
+
width: 36px;
|
|
42
|
+
height: 20px;
|
|
43
|
+
background: var(--bg-tertiary);
|
|
44
|
+
border-radius: 10px;
|
|
45
|
+
position: relative;
|
|
46
|
+
cursor: pointer;
|
|
47
|
+
flex-shrink: 0;
|
|
48
|
+
transition: background 0.2s;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.settings-toggle:focus-visible {
|
|
52
|
+
outline: 2px solid var(--accent);
|
|
53
|
+
outline-offset: 2px;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.settings-toggle::after {
|
|
57
|
+
content: '';
|
|
58
|
+
position: absolute;
|
|
59
|
+
top: 2px;
|
|
60
|
+
left: 2px;
|
|
61
|
+
width: 16px;
|
|
62
|
+
height: 16px;
|
|
63
|
+
border-radius: 50%;
|
|
64
|
+
background: var(--text-secondary);
|
|
65
|
+
transition: transform 0.2s, background 0.2s;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.settings-toggle:checked {
|
|
69
|
+
background: var(--accent);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.settings-toggle:checked::after {
|
|
73
|
+
transform: translateX(16px);
|
|
74
|
+
background: #fff;
|
|
75
|
+
}
|
package/public/index.html
CHANGED
|
@@ -166,6 +166,12 @@
|
|
|
166
166
|
<span id="telegram-label">Telegram</span>
|
|
167
167
|
</button>
|
|
168
168
|
<div style="height:1px;background:var(--border);margin:4px 8px;"></div>
|
|
169
|
+
<button class="header-dropdown-item" id="settings-btn" title="Settings">
|
|
170
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
171
|
+
<line x1="4" y1="21" x2="4" y2="14"/><line x1="4" y1="10" x2="4" y2="3"/><line x1="12" y1="21" x2="12" y2="12"/><line x1="12" y1="8" x2="12" y2="3"/><line x1="20" y1="21" x2="20" y2="16"/><line x1="20" y1="12" x2="20" y2="3"/><line x1="1" y1="14" x2="7" y2="14"/><line x1="9" y1="8" x2="15" y2="8"/><line x1="17" y1="16" x2="23" y2="16"/>
|
|
172
|
+
</svg>
|
|
173
|
+
<span>Settings</span>
|
|
174
|
+
</button>
|
|
169
175
|
<button class="header-dropdown-item" id="dev-docs-btn" title="Developer Documentation">
|
|
170
176
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
171
177
|
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>
|
|
@@ -519,6 +525,7 @@
|
|
|
519
525
|
<claudeck-linear-create></claudeck-linear-create>
|
|
520
526
|
<claudeck-telegram-modal></claudeck-telegram-modal>
|
|
521
527
|
<claudeck-mcp-modal></claudeck-mcp-modal>
|
|
528
|
+
<claudeck-settings-modal></claudeck-settings-modal>
|
|
522
529
|
<claudeck-add-project></claudeck-add-project>
|
|
523
530
|
|
|
524
531
|
<!-- Status Bar (Web Component) -->
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Web Component: Settings Modal
|
|
2
|
+
const SETTINGS_KEY = 'claudeck-settings';
|
|
3
|
+
|
|
4
|
+
function loadSettings() {
|
|
5
|
+
try { return JSON.parse(localStorage.getItem(SETTINGS_KEY) || '{}'); } catch { return {}; }
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function saveSettings(s) {
|
|
9
|
+
localStorage.setItem(SETTINGS_KEY, JSON.stringify(s));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function getSetting(key, fallback = true) {
|
|
13
|
+
const s = loadSettings();
|
|
14
|
+
return s[key] !== undefined ? s[key] : fallback;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
class ClaudeckSettingsModal extends HTMLElement {
|
|
18
|
+
connectedCallback() {
|
|
19
|
+
this.innerHTML = `
|
|
20
|
+
<div id="settings-modal" class="modal-overlay hidden">
|
|
21
|
+
<div class="modal settings-modal">
|
|
22
|
+
<div class="modal-header">
|
|
23
|
+
<h3>Settings</h3>
|
|
24
|
+
<button id="settings-modal-close" class="modal-close">×</button>
|
|
25
|
+
</div>
|
|
26
|
+
<div class="settings-list">
|
|
27
|
+
<label class="settings-row">
|
|
28
|
+
<span class="settings-label">
|
|
29
|
+
<strong>Assistant Bot</strong>
|
|
30
|
+
<small>Show the floating assistant bot bubble</small>
|
|
31
|
+
</span>
|
|
32
|
+
<input type="checkbox" id="setting-assistant-bot" class="settings-toggle">
|
|
33
|
+
</label>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
`;
|
|
38
|
+
|
|
39
|
+
const overlay = this.querySelector('#settings-modal');
|
|
40
|
+
const closeBtn = this.querySelector('#settings-modal-close');
|
|
41
|
+
const botToggle = this.querySelector('#setting-assistant-bot');
|
|
42
|
+
|
|
43
|
+
// Init toggle state
|
|
44
|
+
botToggle.checked = getSetting('assistantBot', true);
|
|
45
|
+
|
|
46
|
+
closeBtn.addEventListener('click', () => overlay.classList.add('hidden'));
|
|
47
|
+
overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.classList.add('hidden'); });
|
|
48
|
+
|
|
49
|
+
botToggle.addEventListener('change', () => {
|
|
50
|
+
const s = loadSettings();
|
|
51
|
+
s.assistantBot = botToggle.checked;
|
|
52
|
+
saveSettings(s);
|
|
53
|
+
// Dispatch event so the bot module can react
|
|
54
|
+
window.dispatchEvent(new CustomEvent('setting:assistantBot', { detail: botToggle.checked }));
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Open from settings button
|
|
58
|
+
document.getElementById('settings-btn')?.addEventListener('click', () => {
|
|
59
|
+
botToggle.checked = getSetting('assistantBot', true);
|
|
60
|
+
overlay.classList.remove('hidden');
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
customElements.define('claudeck-settings-modal', ClaudeckSettingsModal);
|
package/public/js/core/api.js
CHANGED
|
@@ -36,18 +36,35 @@ export async function fetchActiveSessionIds() {
|
|
|
36
36
|
return data.activeSessionIds || [];
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
const
|
|
39
|
+
function _appendPaginationParams(url, { limit, before } = {}) {
|
|
40
|
+
const params = new URLSearchParams();
|
|
41
|
+
if (limit) params.set("limit", limit);
|
|
42
|
+
if (before) params.set("before", before);
|
|
43
|
+
const qs = params.toString();
|
|
44
|
+
return qs ? `${url}?${qs}` : url;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function fetchMessages(sessionId, opts) {
|
|
48
|
+
const url = _appendPaginationParams(
|
|
49
|
+
`/api/sessions/${encodeURIComponent(sessionId)}/messages`, opts
|
|
50
|
+
);
|
|
51
|
+
const res = await fetch(url);
|
|
41
52
|
return res.json();
|
|
42
53
|
}
|
|
43
54
|
|
|
44
|
-
export async function fetchMessagesByChatId(sessionId, chatId) {
|
|
45
|
-
const
|
|
55
|
+
export async function fetchMessagesByChatId(sessionId, chatId, opts) {
|
|
56
|
+
const url = _appendPaginationParams(
|
|
57
|
+
`/api/sessions/${encodeURIComponent(sessionId)}/messages/${encodeURIComponent(chatId)}`, opts
|
|
58
|
+
);
|
|
59
|
+
const res = await fetch(url);
|
|
46
60
|
return res.json();
|
|
47
61
|
}
|
|
48
62
|
|
|
49
|
-
export async function fetchSingleMessages(sessionId) {
|
|
50
|
-
const
|
|
63
|
+
export async function fetchSingleMessages(sessionId, opts) {
|
|
64
|
+
const url = _appendPaginationParams(
|
|
65
|
+
`/api/sessions/${encodeURIComponent(sessionId)}/messages-single`, opts
|
|
66
|
+
);
|
|
67
|
+
const res = await fetch(url);
|
|
51
68
|
return res.json();
|
|
52
69
|
}
|
|
53
70
|
|
package/public/js/core/events.js
CHANGED
|
@@ -5,6 +5,17 @@ export function emit(event, data) {
|
|
|
5
5
|
(bus[event] || []).forEach((fn) => fn(data));
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
/** Subscribe to an event. Returns an unsubscribe function. */
|
|
8
9
|
export function on(event, fn) {
|
|
9
10
|
(bus[event] ||= []).push(fn);
|
|
11
|
+
return () => {
|
|
12
|
+
const arr = bus[event];
|
|
13
|
+
if (arr) bus[event] = arr.filter(f => f !== fn);
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Remove a specific listener for an event. */
|
|
18
|
+
export function off(event, fn) {
|
|
19
|
+
const arr = bus[event];
|
|
20
|
+
if (arr) bus[event] = arr.filter(f => f !== fn);
|
|
10
21
|
}
|
|
@@ -12,21 +12,13 @@
|
|
|
12
12
|
const STORAGE_KEY = 'claudeck-enabled-plugins';
|
|
13
13
|
const ORDER_KEY = 'claudeck-plugin-order';
|
|
14
14
|
let availablePlugins = [];
|
|
15
|
+
let marketplaceRegistry = null;
|
|
15
16
|
const loadedPlugins = new Set();
|
|
16
17
|
|
|
17
18
|
/** Maps plugin file name → tab ID registered by that plugin */
|
|
18
19
|
const pluginTabIds = new Map();
|
|
19
20
|
|
|
20
|
-
/**
|
|
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
|
-
};
|
|
21
|
+
/** Fallback meta for plugins without manifest.json */
|
|
30
22
|
const defaultMeta = { description: 'A tab-sdk plugin', icon: '🧩', order: 100 };
|
|
31
23
|
|
|
32
24
|
export function getAvailablePlugins() {
|
|
@@ -45,7 +37,15 @@ export function setEnabledPluginNames(names) {
|
|
|
45
37
|
}
|
|
46
38
|
|
|
47
39
|
export function getPluginMeta(name) {
|
|
48
|
-
|
|
40
|
+
const plugin = availablePlugins.find(p => p.name === name);
|
|
41
|
+
if (plugin?.manifest) {
|
|
42
|
+
return {
|
|
43
|
+
description: plugin.manifest.description || defaultMeta.description,
|
|
44
|
+
icon: plugin.manifest.icon || defaultMeta.icon,
|
|
45
|
+
order: defaultMeta.order,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return defaultMeta;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
export function getPluginOrder() {
|
|
@@ -151,3 +151,88 @@ export async function loadPlugins() {
|
|
|
151
151
|
console.error('Plugin loader error:', err);
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
|
+
|
|
155
|
+
// ── Marketplace ─────────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
export function getMarketplaceRegistry() {
|
|
158
|
+
return marketplaceRegistry;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Fetch the community plugin registry from the server (which proxies GitHub) */
|
|
162
|
+
export async function fetchMarketplace(refresh = false) {
|
|
163
|
+
try {
|
|
164
|
+
const url = refresh ? '/api/marketplace?refresh=true' : '/api/marketplace';
|
|
165
|
+
const res = await fetch(url);
|
|
166
|
+
if (!res.ok) { console.warn('Marketplace fetch failed:', res.status); return null; }
|
|
167
|
+
marketplaceRegistry = await res.json();
|
|
168
|
+
return marketplaceRegistry;
|
|
169
|
+
} catch (err) {
|
|
170
|
+
console.error('Marketplace error:', err);
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Install a community plugin and auto-enable it */
|
|
176
|
+
export async function installMarketplacePlugin(plugin) {
|
|
177
|
+
const res = await fetch('/api/marketplace/install', {
|
|
178
|
+
method: 'POST',
|
|
179
|
+
headers: { 'Content-Type': 'application/json' },
|
|
180
|
+
body: JSON.stringify({ id: plugin.id, repo: plugin.repo, source: plugin.source }),
|
|
181
|
+
});
|
|
182
|
+
if (!res.ok) {
|
|
183
|
+
const err = await res.json().catch(() => ({ error: 'Install failed' }));
|
|
184
|
+
throw new Error(err.error);
|
|
185
|
+
}
|
|
186
|
+
const result = await res.json();
|
|
187
|
+
|
|
188
|
+
// Refresh local plugins list to include the newly installed plugin
|
|
189
|
+
const pluginsRes = await fetch('/api/plugins');
|
|
190
|
+
if (pluginsRes.ok) {
|
|
191
|
+
availablePlugins = await pluginsRes.json();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Auto-enable the newly installed plugin
|
|
195
|
+
const enabled = getEnabledPluginNames();
|
|
196
|
+
if (!enabled.includes(plugin.id)) {
|
|
197
|
+
enabled.push(plugin.id);
|
|
198
|
+
setEnabledPluginNames(enabled);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Load the plugin immediately
|
|
202
|
+
await loadPluginByName(plugin.id);
|
|
203
|
+
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Uninstall a community plugin */
|
|
208
|
+
export async function uninstallMarketplacePlugin(id) {
|
|
209
|
+
const res = await fetch('/api/marketplace/uninstall', {
|
|
210
|
+
method: 'POST',
|
|
211
|
+
headers: { 'Content-Type': 'application/json' },
|
|
212
|
+
body: JSON.stringify({ id }),
|
|
213
|
+
});
|
|
214
|
+
if (!res.ok) {
|
|
215
|
+
const err = await res.json().catch(() => ({ error: 'Uninstall failed' }));
|
|
216
|
+
throw new Error(err.error);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Remove from enabled list
|
|
220
|
+
const enabled = getEnabledPluginNames().filter(n => n !== id);
|
|
221
|
+
setEnabledPluginNames(enabled);
|
|
222
|
+
|
|
223
|
+
// Clean up CSS link from DOM
|
|
224
|
+
const cssLink = document.head.querySelector(`link[data-plugin="${id}"]`);
|
|
225
|
+
if (cssLink) cssLink.remove();
|
|
226
|
+
|
|
227
|
+
// Clear loaded/tab tracking state
|
|
228
|
+
loadedPlugins.delete(id);
|
|
229
|
+
pluginTabIds.delete(id);
|
|
230
|
+
|
|
231
|
+
// Refresh local plugins list
|
|
232
|
+
const pluginsRes = await fetch('/api/plugins');
|
|
233
|
+
if (pluginsRes.ok) {
|
|
234
|
+
availablePlugins = await pluginsRes.json();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return await res.json();
|
|
238
|
+
}
|
package/public/js/core/store.js
CHANGED
|
@@ -30,8 +30,19 @@ export function setState(key, val) {
|
|
|
30
30
|
emit(key, val);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/** Subscribe to state changes for a key. Returns an unsubscribe function. */
|
|
33
34
|
export function on(key, fn) {
|
|
34
35
|
(listeners[key] ||= []).push(fn);
|
|
36
|
+
return () => {
|
|
37
|
+
const arr = listeners[key];
|
|
38
|
+
if (arr) listeners[key] = arr.filter(f => f !== fn);
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Remove a specific listener for a key. */
|
|
43
|
+
export function off(key, fn) {
|
|
44
|
+
const arr = listeners[key];
|
|
45
|
+
if (arr) listeners[key] = arr.filter(f => f !== fn);
|
|
35
46
|
}
|
|
36
47
|
|
|
37
48
|
function emit(key, val) {
|
package/public/js/core/ws.js
CHANGED
|
@@ -3,6 +3,12 @@ import { $ } from './dom.js';
|
|
|
3
3
|
import { getState, setState } from './store.js';
|
|
4
4
|
import { emit } from './events.js';
|
|
5
5
|
|
|
6
|
+
export function subscribeToSession(sessionId) {
|
|
7
|
+
const ws = getState("ws");
|
|
8
|
+
if (!ws || ws.readyState !== 1 || !sessionId) return;
|
|
9
|
+
ws.send(JSON.stringify({ type: "subscribe", sessionId }));
|
|
10
|
+
}
|
|
11
|
+
|
|
6
12
|
let backoffAttempt = 0;
|
|
7
13
|
let hasConnectedBefore = false;
|
|
8
14
|
|
|
@@ -37,6 +43,12 @@ export function connectWebSocket() {
|
|
|
37
43
|
hasConnectedBefore = true;
|
|
38
44
|
emit("ws:connected");
|
|
39
45
|
}
|
|
46
|
+
|
|
47
|
+
// Subscribe to current session for multi-client broadcast
|
|
48
|
+
const currentSession = getState("sessionId");
|
|
49
|
+
if (currentSession) {
|
|
50
|
+
ws.send(JSON.stringify({ type: "subscribe", sessionId: currentSession }));
|
|
51
|
+
}
|
|
40
52
|
};
|
|
41
53
|
|
|
42
54
|
ws.onmessage = (event) => {
|