claudeck 1.4.0 → 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 +6 -6
- package/package.json +1 -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/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/events.js +11 -0
- package/public/js/core/plugin-loader.js +96 -11
- package/public/js/core/store.js +11 -0
- 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/model-selector.js +1 -0
- 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/memory-optimizer.js +17 -13
- package/server/routes/marketplace.js +316 -0
- package/server/ws-handler.js +22 -15
- package/server.js +18 -0
- 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/README.md
CHANGED
|
@@ -252,16 +252,15 @@ See [CONFIGURATION.md](docs/CONFIGURATION.md) for the full guide.
|
|
|
252
252
|
|
|
253
253
|
## Plugins
|
|
254
254
|
|
|
255
|
-
Claudeck includes
|
|
255
|
+
Claudeck includes 3 built-in plugins, a community marketplace, and supports user plugins via `~/.claudeck/plugins/`:
|
|
256
256
|
|
|
257
257
|
| Plugin | Description |
|
|
258
258
|
|--------|-------------|
|
|
259
|
-
| **
|
|
259
|
+
| **Claude Editor** | Edit CLAUDE.md project instructions in-app |
|
|
260
260
|
| **Linear** | Linear issue tracking with team management |
|
|
261
261
|
| **Repos** | Repository management with tree view |
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
| **Games** | Tic-tac-toe and Sudoku |
|
|
262
|
+
|
|
263
|
+
**Community Marketplace** — browse, install, and update community plugins directly from the Plugin Marketplace tab. Plugins like Tasks, Event Stream, Sudoku, and Tic-Tac-Toe are available from the [marketplace](https://github.com/hamedafarag/claudeck-marketplace).
|
|
265
264
|
|
|
266
265
|
**Create your own** — drop files in `~/.claudeck/plugins/<name>/` (persists across upgrades) with `client.js` and optionally `server.js`, `client.css`, `config.json`. No fork needed. See [CONFIGURATION.md](docs/CONFIGURATION.md#user-plugins) for details.
|
|
267
266
|
|
|
@@ -280,6 +279,7 @@ npx skills add https://github.com/hamedafarag/claudeck-skills
|
|
|
280
279
|
| Document | Description |
|
|
281
280
|
|----------|-------------|
|
|
282
281
|
| [DOCUMENTATION.md](docs/DOCUMENTATION.md) | Full feature docs, API reference, database schema |
|
|
282
|
+
| [TAB-SDK.md](docs/TAB-SDK.md) | Plugin SDK reference — ctx API, events, state, CSS tokens, examples |
|
|
283
283
|
| [CONFIGURATION.md](docs/CONFIGURATION.md) | User data directory, config files, plugin system |
|
|
284
284
|
| [AGENT-ARCHITECTURE.md](docs/AGENT-ARCHITECTURE.md) | How agents, chains, DAGs, and orchestrator work |
|
|
285
285
|
| [PLAN-sqlite-adapter.md](docs/PLAN-sqlite-adapter.md) | Database adapter pattern, async interface, multi-DB roadmap |
|
|
@@ -305,7 +305,7 @@ npm run test:perf # WebSocket performance benchmarks
|
|
|
305
305
|
| **panels/** | 150+ | 35% |
|
|
306
306
|
| **server/** | 1,350+ | 95% |
|
|
307
307
|
|
|
308
|
-
|
|
308
|
+
20 Web Components in `public/js/components/` — each is a self-contained Custom Element (Light DOM) that owns its HTML, testable with zero mocks.
|
|
309
309
|
|
|
310
310
|
### Performance Benchmarks
|
|
311
311
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claudeck",
|
|
3
|
-
"version": "1.4.
|
|
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",
|
|
@@ -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/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/main.js
CHANGED
|
@@ -18,6 +18,7 @@ import './components/permission-modal.js';
|
|
|
18
18
|
import './components/linear-create-modal.js';
|
|
19
19
|
import './components/telegram-modal.js';
|
|
20
20
|
import './components/mcp-modal.js';
|
|
21
|
+
import './components/settings-modal.js';
|
|
21
22
|
import './components/add-project-modal.js';
|
|
22
23
|
import './components/status-bar.js';
|
|
23
24
|
|
|
@@ -6,6 +6,7 @@ import { renderMarkdown, highlightCodeBlocks, addCopyButtons } from '../ui/forma
|
|
|
6
6
|
import * as api from '../core/api.js';
|
|
7
7
|
import { getSelectedModel } from '../ui/model-selector.js';
|
|
8
8
|
import { $ } from '../core/dom.js';
|
|
9
|
+
import { getSetting } from '../components/settings-modal.js';
|
|
9
10
|
|
|
10
11
|
const SESSIONS_KEY = 'claudeck-bot-sessions';
|
|
11
12
|
let panel, messagesDiv, inputEl, sendBtn, stopBtn, settingsOverlay, promptTextarea;
|
|
@@ -433,12 +434,27 @@ function newBotSession() {
|
|
|
433
434
|
showBotWhaly();
|
|
434
435
|
}
|
|
435
436
|
|
|
437
|
+
// ── Visibility ──────────────────────────────────────────
|
|
438
|
+
|
|
439
|
+
let bubble; // reference set in createBotDOM
|
|
440
|
+
|
|
441
|
+
function setBotVisible(visible) {
|
|
442
|
+
if (!bubble) return;
|
|
443
|
+
bubble.style.display = visible ? '' : 'none';
|
|
444
|
+
if (!visible && panel) closePanel();
|
|
445
|
+
}
|
|
446
|
+
|
|
436
447
|
// ── Init ────────────────────────────────────────────────
|
|
437
448
|
|
|
438
449
|
function init() {
|
|
439
450
|
createBotDOM();
|
|
451
|
+
bubble = document.querySelector('.bot-bubble');
|
|
440
452
|
on('ws:message', handleBotWsMessage);
|
|
441
453
|
fetchSystemPrompt();
|
|
454
|
+
|
|
455
|
+
// Respect enable/disable setting
|
|
456
|
+
setBotVisible(getSetting('assistantBot', true));
|
|
457
|
+
window.addEventListener('setting:assistantBot', (e) => setBotVisible(e.detail));
|
|
442
458
|
}
|
|
443
459
|
|
|
444
460
|
// Run on import
|
|
@@ -147,7 +147,7 @@ registerTab({
|
|
|
147
147
|
<li>Existing shortcuts (e.g. <code>openRightPanel('my-tab')</code>) work automatically</li>
|
|
148
148
|
</ul>
|
|
149
149
|
|
|
150
|
-
<div class="callout">See <code>plugins/
|
|
150
|
+
<div class="callout">See <code>plugins/claude-editor/client.js</code> for a complete working example of a plugin tab.</div>
|
|
151
151
|
`,
|
|
152
152
|
});
|
|
153
153
|
|
|
@@ -301,7 +301,7 @@ registerDocSection({
|
|
|
301
301
|
<li>Import paths: use absolute paths (e.g. <code>/js/ui/tab-sdk.js</code>)</li>
|
|
302
302
|
</ul>
|
|
303
303
|
|
|
304
|
-
<div class="callout">When in doubt, look at <code>plugins/
|
|
304
|
+
<div class="callout">When in doubt, look at <code>plugins/claude-editor/</code> or <code>plugins/repos/</code> as reference implementations. For full-stack with server routes, see <code>plugins/linear/</code>. More examples are available in the <a href="https://github.com/hamedafarag/claudeck-marketplace" target="_blank">marketplace repo</a>.</div>
|
|
305
305
|
`,
|
|
306
306
|
});
|
|
307
307
|
|