openclacky 1.1.5 → 1.2.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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -0
  3. data/CODE_OF_CONDUCT.md +1 -1
  4. data/CONTRIBUTING.md +92 -0
  5. data/README.md +10 -0
  6. data/README_CN.md +10 -0
  7. data/ROADMAP.md +29 -0
  8. data/docs/billing-system.md +340 -0
  9. data/docs/mcp-architecture.md +114 -0
  10. data/docs/mcp.example.json +22 -0
  11. data/lib/clacky/agent/cost_tracker.rb +37 -0
  12. data/lib/clacky/agent/llm_caller.rb +0 -1
  13. data/lib/clacky/agent/session_serializer.rb +2 -11
  14. data/lib/clacky/agent/skill_manager.rb +73 -26
  15. data/lib/clacky/agent/system_prompt_builder.rb +0 -5
  16. data/lib/clacky/agent/time_machine.rb +6 -0
  17. data/lib/clacky/agent.rb +26 -1
  18. data/lib/clacky/agent_config.rb +9 -19
  19. data/lib/clacky/billing/billing_record.rb +67 -0
  20. data/lib/clacky/billing/billing_store.rb +193 -0
  21. data/lib/clacky/cli.rb +113 -63
  22. data/lib/clacky/default_skills/browser-setup/SKILL.md +26 -4
  23. data/lib/clacky/default_skills/mcp-manager/SKILL.md +343 -0
  24. data/lib/clacky/idle_compression_timer.rb +4 -2
  25. data/lib/clacky/mcp/client.rb +204 -0
  26. data/lib/clacky/mcp/http_transport.rb +155 -0
  27. data/lib/clacky/mcp/registry.rb +229 -0
  28. data/lib/clacky/mcp/skill_provider.rb +75 -0
  29. data/lib/clacky/mcp/stdio_transport.rb +112 -0
  30. data/lib/clacky/mcp/transport.rb +23 -0
  31. data/lib/clacky/mcp/virtual_skill.rb +131 -0
  32. data/lib/clacky/message_history.rb +0 -1
  33. data/lib/clacky/server/channel/adapters/weixin/adapter.rb +2 -35
  34. data/lib/clacky/server/http_server.rb +521 -17
  35. data/lib/clacky/server/server_master.rb +8 -14
  36. data/lib/clacky/server/session_registry.rb +30 -2
  37. data/lib/clacky/server/web_ui_controller.rb +4 -0
  38. data/lib/clacky/session_manager.rb +41 -12
  39. data/lib/clacky/skill.rb +1 -5
  40. data/lib/clacky/skill_loader.rb +41 -6
  41. data/lib/clacky/tools/browser.rb +217 -38
  42. data/lib/clacky/tools/trash_manager.rb +154 -3
  43. data/lib/clacky/ui2/components/command_suggestions.rb +6 -2
  44. data/lib/clacky/ui_interface.rb +1 -0
  45. data/lib/clacky/utils/model_pricing.rb +11 -7
  46. data/lib/clacky/utils/trash_directory.rb +37 -6
  47. data/lib/clacky/version.rb +1 -1
  48. data/lib/clacky/web/app.css +2907 -1764
  49. data/lib/clacky/web/app.js +84 -10
  50. data/lib/clacky/web/billing.js +275 -0
  51. data/lib/clacky/web/brand.js +51 -40
  52. data/lib/clacky/web/i18n.js +248 -24
  53. data/lib/clacky/web/index.html +362 -134
  54. data/lib/clacky/web/mcp.js +328 -0
  55. data/lib/clacky/web/sessions.js +299 -18
  56. data/lib/clacky/web/settings.js +686 -174
  57. data/lib/clacky/web/sidebar.js +2 -0
  58. data/lib/clacky/web/trash.js +323 -60
  59. data/lib/clacky/web/ws-dispatcher.js +15 -2
  60. data/lib/clacky.rb +4 -0
  61. data/scripts/install.ps1 +23 -11
  62. metadata +30 -10
@@ -119,6 +119,17 @@
119
119
  </div>
120
120
  </div>
121
121
 
122
+ <!-- Cron view header (hidden by default, shown when viewing cron sessions) -->
123
+ <div id="cron-view-header" class="sidebar-divider" style="display:none">
124
+ <button id="btn-cron-back" class="btn-icon-sm" title="Back" aria-label="Back to session list">
125
+ <svg width="14" height="14" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
126
+ <path d="M10 3L5 8l5 5" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
127
+ </svg>
128
+ </button>
129
+ <span data-i18n="sessions.cronGroup">Scheduled Tasks</span>
130
+ <span></span><!-- spacer -->
131
+ </div>
132
+
122
133
  <!-- Unified session list (all sources + profiles, newest first) -->
123
134
  <div id="session-list"></div>
124
135
  <!-- Load more button rendered dynamically by Sessions.renderList() -->
@@ -160,6 +171,18 @@
160
171
  </div>
161
172
  </div>
162
173
  </div>
174
+ <div id="mcp-sidebar-item" class="task-item task-item-summary">
175
+ <div class="task-row">
176
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="task-icon">
177
+ <rect x="3" y="4" width="18" height="6" rx="1"/>
178
+ <rect x="3" y="14" width="18" height="6" rx="1"/>
179
+ <path d="M8 7h.01M8 17h.01"/>
180
+ </svg>
181
+ <div class="task-info">
182
+ <span class="task-name" id="mcp-sidebar-label" data-i18n="sidebar.mcp">MCP Servers</span>
183
+ </div>
184
+ </div>
185
+ </div>
163
186
  </div>
164
187
  </div>
165
188
 
@@ -196,6 +219,17 @@
196
219
  </div>
197
220
  </div>
198
221
  </div>
222
+ <div id="billing-sidebar-item" class="task-item task-item-summary">
223
+ <div class="task-row">
224
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="task-icon">
225
+ <line x1="12" y1="1" x2="12" y2="23"/>
226
+ <path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/>
227
+ </svg>
228
+ <div class="task-info">
229
+ <span class="task-name" id="billing-sidebar-label" data-i18n="sidebar.billing">Billing</span>
230
+ </div>
231
+ </div>
232
+ </div>
199
233
  </div>
200
234
  </div>
201
235
 
@@ -278,14 +312,7 @@
278
312
 
279
313
  <!-- Chat panel -->
280
314
  <div id="chat-panel" style="display:none">
281
- <!-- Mobile-only floating back button: shown inside a session on small screens.
282
- Desktop: hidden. All other session info (title, status, dir, delete) is
283
- provided by the bottom #session-info-bar. -->
284
- <button id="btn-back-mobile" class="chat-back-btn chat-back-floating" title="Back">
285
- <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
286
- <path d="M15 18l-6-6 6-6"/>
287
- </svg>
288
- </button>
315
+
289
316
  <div id="messages"></div>
290
317
  <!-- New message notification banner -->
291
318
  <div id="new-message-banner" class="new-message-banner" style="display:none">
@@ -305,7 +332,7 @@
305
332
  <span id="sib-dir" data-i18n-title="sib.dir.tooltip" title="Click to change directory"></span>
306
333
  <span class="sib-sep sib-sep-after-dir">│</span>
307
334
  <span id="sib-model-wrap">
308
- <span id="sib-model" class="sib-model-clickable" data-i18n-title="sib.model.tooltip" title="Click to switch model"></span>
335
+ <span id="sib-model" class="sib-model-clickable" title="Click to switch model"></span>
309
336
  <div id="sib-model-dropdown" class="sib-model-dropdown" style="display:none"></div>
310
337
  </span>
311
338
  <span class="sib-sep sib-sep-after-model">│</span>
@@ -533,6 +560,19 @@
533
560
  </div>
534
561
  </div>
535
562
 
563
+ <!-- ── MCP PANEL — read-only view of MCP servers from ~/.clacky/mcp.json ── -->
564
+ <div id="mcp-panel" style="display:none">
565
+ <div id="mcp-body">
566
+ <div class="channels-page-header">
567
+ <h2 class="channels-page-title" data-i18n="mcp.title">MCP Servers</h2>
568
+ <p class="channels-page-subtitle" data-i18n="mcp.subtitle">Standard Model Context Protocol servers (Claude Desktop / Cursor compatible). Edit ~/.clacky/mcp.json to add or remove servers.</p>
569
+ </div>
570
+
571
+ <div id="mcp-status" class="mcp-status"></div>
572
+ <div id="mcp-list"></div>
573
+ </div>
574
+ </div>
575
+
536
576
  <!-- ── TRASH PANEL — Recently deleted files across all projects ─── -->
537
577
  <div id="trash-panel" style="display:none">
538
578
  <div id="trash-body">
@@ -541,6 +581,13 @@
541
581
  <p class="channels-page-subtitle" data-i18n="trash.subtitle">Files the agent moved to trash across all projects. Recall them back to where they were, or clear the ones you don't need.</p>
542
582
  </div>
543
583
 
584
+ <div id="trash-tabs">
585
+ <div class="trash-tabs-left">
586
+ <button class="trash-tab active" data-tab="file-trash" id="tab-file-trash" data-i18n="trash.tabFile">文件回收</button>
587
+ <button class="trash-tab" data-tab="session-trash" id="tab-session-trash" data-i18n="trash.tabSession">会话回收</button>
588
+ </div>
589
+ </div>
590
+
544
591
  <div class="trash-toolbar">
545
592
  <span class="trash-summary" id="trash-summary"></span>
546
593
  <div class="trash-actions">
@@ -576,7 +623,13 @@
576
623
  </div>
577
624
  </div>
578
625
 
579
- <div id="trash-list"></div>
626
+ <div id="trash-tab-file" class="trash-tab-content">
627
+ <div id="trash-list"></div>
628
+ </div>
629
+
630
+ <div id="trash-tab-session" class="trash-tab-content" style="display:none">
631
+ <div id="trash-session-list"></div>
632
+ </div>
580
633
  </div>
581
634
  </div>
582
635
 
@@ -651,160 +704,330 @@
651
704
  </div>
652
705
  </div>
653
706
 
707
+ <!-- ── BILLING PANEL ───────────────────────────────────────────────── -->
708
+ <div id="billing-panel" style="display:none">
709
+ <div id="billing-body">
710
+ <div id="billing-content">
711
+ <!-- Content loaded dynamically by billing.js -->
712
+ </div>
713
+ </div>
714
+ </div>
715
+
654
716
  <!-- ── SETTINGS PANEL ──────────────────────────────────────────────── -->
655
717
  <div id="settings-panel" style="display:none">
656
718
  <header id="settings-header">
657
719
  <span data-i18n="settings.title">Settings</span>
658
720
  </header>
721
+
722
+ <!-- Settings Tabs -->
723
+ <div id="settings-tabs">
724
+ <button class="settings-tab active" data-tab="models" data-i18n="settings.tabs.models">Models</button>
725
+ <button class="settings-tab" data-tab="ui" data-i18n="settings.tabs.ui">UI</button>
726
+ <button class="settings-tab" data-tab="general" data-i18n="settings.tabs.general">General</button>
727
+ <button class="settings-tab" data-tab="about" data-i18n="settings.tabs.about">About</button>
728
+ </div>
729
+
659
730
  <div id="settings-body">
660
731
 
661
- <!-- Models section -->
662
- <section class="settings-section">
663
- <div class="settings-section-title">
664
- <span data-i18n="settings.models.title">AI Models</span>
665
- <button id="btn-add-model" class="btn-settings-add" data-i18n="settings.models.add">+ Add Model</button>
666
- </div>
667
- <div id="model-cards"></div>
668
- </section>
732
+ <!-- ══ Tab: Models ══ -->
733
+ <div class="settings-tab-content active" data-tab-content="models">
734
+ <!-- Models section -->
735
+ <section class="settings-section">
736
+ <div class="settings-section-title">
737
+ <span data-i18n="settings.models.title">AI Models</span>
738
+ <button id="btn-add-model" class="btn-settings-add" data-i18n="settings.models.add">+ Add Model</button>
739
+ </div>
740
+ <div id="model-cards"></div>
741
+ </section>
742
+ </div>
669
743
 
670
- <!-- Personalize section -->
671
- <section class="settings-section">
672
- <div class="settings-section-title">
673
- <span data-i18n="settings.personalize.title">Personalize</span>
674
- </div>
675
- <div class="settings-personalize">
676
- <p class="settings-personalize-desc" data-i18n="settings.personalize.desc">Re-run the onboarding to update your assistant's personality and user profile (SOUL.md &amp; USER.md).</p>
677
- <button id="btn-rerun-onboard" class="btn-settings-action" data-i18n="settings.personalize.btn">✨ Re-run Onboard</button>
678
- </div>
679
- </section>
744
+ <!-- ══ Tab: UI ══ -->
745
+ <div class="settings-tab-content" data-tab-content="ui" style="display:none">
746
+ <!-- Language section -->
747
+ <section class="settings-section" id="language-section">
748
+ <div class="settings-section-title">
749
+ <span data-i18n="settings.lang.title">Language</span>
750
+ </div>
751
+ <div class="settings-lang-btns">
752
+ <button id="settings-btn-lang-en" class="settings-lang-btn" data-lang="en" data-i18n="settings.lang.en">English</button>
753
+ <button id="settings-btn-lang-zh" class="settings-lang-btn" data-lang="zh" data-i18n="settings.lang.zh">中文</button>
754
+ </div>
755
+ </section>
680
756
 
681
- <!-- Browser section -->
682
- <section class="settings-section">
683
- <div class="settings-section-title">
684
- <span data-i18n="settings.browser.title">Browser</span>
685
- </div>
686
- <div class="settings-personalize">
687
- <p class="settings-personalize-desc" id="browser-status-desc" data-i18n="settings.browser.desc">Connect your browser to enable browser automation.</p>
688
- <div class="settings-browser-actions">
689
- <label class="toggle-switch" id="browser-toggle-wrap" style="display:none">
690
- <input type="checkbox" id="browser-toggle-input">
691
- <span class="toggle-slider"></span>
757
+ <!-- Font Size section -->
758
+ <section class="settings-section" id="font-size-section">
759
+ <div class="settings-section-title">
760
+ <span data-i18n="settings.fontSize.title">Font Size</span>
761
+ </div>
762
+ <div class="settings-lang-btns">
763
+ <button id="settings-btn-font-small" class="settings-lang-btn" data-font="small" data-i18n="settings.fontSize.small">小</button>
764
+ <button id="settings-btn-font-medium" class="settings-lang-btn" data-font="medium" data-i18n="settings.fontSize.medium">中</button>
765
+ <button id="settings-btn-font-large" class="settings-lang-btn" data-font="large" data-i18n="settings.fontSize.large">大</button>
766
+ </div>
767
+ </section>
768
+
769
+ <!-- Currency section -->
770
+ <section class="settings-section" id="currency-section">
771
+ <div class="settings-section-title">
772
+ <span data-i18n="settings.currency.title">Currency</span>
773
+ </div>
774
+ <div class="settings-lang-btns">
775
+ <button id="settings-btn-currency-usd" class="settings-lang-btn" data-currency="USD">$ USD</button>
776
+ <button id="settings-btn-currency-cny" class="settings-lang-btn" data-currency="CNY">¥ CNY</button>
777
+ </div>
778
+ <div class="settings-exchange-rate" id="exchange-rate-section" style="display: none;">
779
+ <label class="settings-exchange-rate-label">
780
+ <span data-i18n="settings.currency.exchangeRate">Exchange Rate (1 USD =)</span>
781
+ <div class="settings-exchange-rate-input-wrapper">
782
+ <input type="number" id="settings-exchange-rate" class="settings-exchange-rate-input" step="0.0001" min="0.0001" placeholder="6.7944">
783
+ <span class="settings-exchange-rate-unit">CNY</span>
784
+ </div>
692
785
  </label>
693
- <button id="btn-browser-setup" class="btn-settings-action" data-i18n="settings.browser.btn">🌐 Configure Browser</button>
694
786
  </div>
695
- </div>
696
- </section>
787
+ </section>
788
+ </div>
697
789
 
698
- <!-- Language section -->
699
- <section class="settings-section" id="language-section">
700
- <div class="settings-section-title">
701
- <span data-i18n="settings.lang.title">Language</span>
702
- </div>
703
- <div class="settings-lang-btns">
704
- <button id="settings-btn-lang-en" class="settings-lang-btn" data-lang="en" data-i18n="settings.lang.en">English</button>
705
- <button id="settings-btn-lang-zh" class="settings-lang-btn" data-lang="zh" data-i18n="settings.lang.zh">中文</button>
706
- </div>
707
- </section>
790
+ <!-- ══ Tab: General ══ -->
791
+ <div class="settings-tab-content" data-tab-content="general" style="display:none">
792
+ <!-- Personalize section -->
793
+ <section class="settings-section">
794
+ <div class="settings-section-title">
795
+ <span data-i18n="settings.personalize.title">Personalize</span>
796
+ </div>
797
+ <div class="settings-personalize">
798
+ <p class="settings-personalize-desc" data-i18n="settings.personalize.desc">Re-run the onboarding to update your assistant's personality and user profile (SOUL.md &amp; USER.md).</p>
799
+ <button id="btn-rerun-onboard" class="btn-settings-action" data-i18n="settings.personalize.btn">✨ Re-run Onboard</button>
800
+ </div>
801
+ </section>
708
802
 
709
- <!-- Font Size section -->
710
- <section class="settings-section" id="font-size-section">
711
- <div class="settings-section-title">
712
- <span data-i18n="settings.fontSize.title">Font Size</span>
713
- </div>
714
- <div class="settings-lang-btns">
715
- <button id="settings-btn-font-small" class="settings-lang-btn" data-font="small" data-i18n="settings.fontSize.small">小</button>
716
- <button id="settings-btn-font-medium" class="settings-lang-btn" data-font="medium" data-i18n="settings.fontSize.medium">中</button>
717
- <button id="settings-btn-font-large" class="settings-lang-btn" data-font="large" data-i18n="settings.fontSize.large">大</button>
718
- </div>
719
- </section>
803
+ <!-- Browser section -->
804
+ <section class="settings-section">
805
+ <div class="settings-section-title">
806
+ <span data-i18n="settings.browser.title">Browser</span>
807
+ </div>
808
+ <div class="settings-personalize">
809
+ <p class="settings-personalize-desc" id="browser-status-desc" data-i18n="settings.browser.desc">Connect your browser to enable browser automation.</p>
810
+ <div class="settings-browser-actions">
811
+ <label class="toggle-switch" id="browser-toggle-wrap" style="display:none">
812
+ <input type="checkbox" id="browser-toggle-input">
813
+ <span class="toggle-slider"></span>
814
+ </label>
815
+ <button id="btn-browser-setup" class="btn-settings-action" data-i18n="settings.browser.btn">🌐 Configure Browser</button>
816
+ </div>
817
+ </div>
818
+ </section>
720
819
 
721
- <!-- Brand & License section -->
722
- <section class="settings-section" id="brand-license-section">
723
- <div class="settings-section-title">
724
- <span data-i18n="settings.brand.title">Brand &amp; License</span>
725
- </div>
820
+ <!-- Brand & License section -->
821
+ <section class="settings-section" id="brand-license-section">
822
+ <div class="settings-section-title">
823
+ <span data-i18n="settings.brand.title">Brand &amp; License</span>
824
+ </div>
726
825
 
727
- <!-- Current brand status (shown when already activated) -->
728
- <div id="brand-status-card" class="brand-status-card" style="display:none">
729
- <div class="brand-status-main">
730
- <div class="brand-status-fields">
731
- <div class="brand-status-row">
732
- <span class="brand-status-label" data-i18n="settings.brand.label.brand">Brand</span>
733
- <span class="brand-status-value" id="brand-status-name">—</span>
734
- </div>
735
- <div class="brand-status-row">
736
- <span class="brand-status-label" data-i18n="settings.brand.label.status">Status</span>
737
- <span class="brand-status-value" id="brand-status-badge">—</span>
826
+ <!-- Current brand status (shown when already activated) -->
827
+ <div id="brand-status-card" class="brand-status-card" style="display:none">
828
+ <div class="brand-status-main">
829
+ <div class="brand-status-fields">
830
+ <div class="brand-status-row">
831
+ <span class="brand-status-label" data-i18n="settings.brand.label.brand">Brand</span>
832
+ <span class="brand-status-value" id="brand-status-name">—</span>
833
+ </div>
834
+ <div class="brand-status-row">
835
+ <span class="brand-status-label" data-i18n="settings.brand.label.status">Status</span>
836
+ <span class="brand-status-value" id="brand-status-badge">—</span>
837
+ </div>
838
+ <div class="brand-status-row">
839
+ <span class="brand-status-label" data-i18n="settings.brand.label.expires">Expires</span>
840
+ <span class="brand-status-value" id="brand-status-expires">—</span>
841
+ </div>
842
+ <div class="brand-status-row" id="brand-status-homepage-row" style="display:none">
843
+ <span class="brand-status-label" data-i18n="settings.brand.label.homepage">Homepage</span>
844
+ <a id="brand-status-homepage" class="brand-status-value brand-status-link" href="#" target="_blank" rel="noopener noreferrer">—</a>
845
+ </div>
846
+ <div class="brand-status-actions">
847
+ <button id="btn-rebind-license" class="btn-settings-action" data-i18n="settings.brand.btn.change">Change Serial Number</button>
848
+ <button id="btn-unbind-license" class="btn-settings-action btn-settings-action-danger" data-i18n="settings.brand.btn.unbind">Unbind License</button>
849
+ </div>
738
850
  </div>
739
- <div class="brand-status-row">
740
- <span class="brand-status-label" data-i18n="settings.brand.label.expires">Expires</span>
741
- <span class="brand-status-value" id="brand-status-expires">—</span>
851
+ <!-- Support contact (shown when support_contact is available) -->
852
+ <div id="brand-support-contact" class="brand-support-contact" style="display:none">
853
+ <span class="brand-support-contact-label" data-i18n="settings.brand.label.supportContact">Support</span>
854
+ <a id="brand-support-contact-link" class="brand-support-contact-value" href="#" target="_blank" rel="noopener noreferrer"></a>
742
855
  </div>
743
- <div class="brand-status-row" id="brand-status-homepage-row" style="display:none">
744
- <span class="brand-status-label" data-i18n="settings.brand.label.homepage">Homepage</span>
745
- <a id="brand-status-homepage" class="brand-status-value brand-status-link" href="#" target="_blank" rel="noopener noreferrer">—</a>
856
+
857
+ <!-- Support QR code (shown when support_qr_url is available) -->
858
+ <div id="brand-support-qr" class="brand-support-qr" style="display:none">
859
+ <button class="brand-support-qr-btn" id="brand-support-qr-btn" title="Click to enlarge" aria-label="View QR code larger">
860
+ <img id="brand-support-qr-img" src="" alt="Support QR Code" class="brand-support-qr-img">
861
+ <span class="brand-support-qr-zoom-hint">
862
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><line x1="11" y1="8" x2="11" y2="14"/><line x1="8" y1="11" x2="14" y2="11"/></svg>
863
+ Click to enlarge
864
+ </span>
865
+ </button>
866
+ <span class="brand-support-qr-label" data-i18n="settings.brand.label.supportQr">Tech Support</span>
746
867
  </div>
747
- <div class="brand-status-actions">
748
- <button id="btn-rebind-license" class="btn-settings-action" data-i18n="settings.brand.btn.change">Change Serial Number</button>
749
- <button id="btn-unbind-license" class="btn-settings-action btn-settings-action-danger" data-i18n="settings.brand.btn.unbind">Unbind License</button>
868
+
869
+ <!-- QR code lightbox modal -->
870
+ <div id="qr-lightbox" class="qr-lightbox" style="display:none" role="dialog" aria-modal="true" aria-label="QR Code enlarged view">
871
+ <div class="qr-lightbox-backdrop" id="qr-lightbox-backdrop"></div>
872
+ <div class="qr-lightbox-content">
873
+ <img id="qr-lightbox-img" src="" alt="Support QR Code" class="qr-lightbox-img">
874
+ <p class="qr-lightbox-label" id="qr-lightbox-label">Tech Support</p>
875
+ <p class="qr-lightbox-hint" data-i18n="settings.brand.label.qrHint">Scan with your phone camera</p>
876
+ <button class="qr-lightbox-close" id="qr-lightbox-close" aria-label="Close">
877
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
878
+ </button>
879
+ </div>
750
880
  </div>
751
881
  </div>
752
- <!-- Support contact (shown when support_contact is available) -->
753
- <div id="brand-support-contact" class="brand-support-contact" style="display:none">
754
- <span class="brand-support-contact-label" data-i18n="settings.brand.label.supportContact">Support</span>
755
- <a id="brand-support-contact-link" class="brand-support-contact-value" href="#" target="_blank" rel="noopener noreferrer"></a>
756
- </div>
882
+ </div>
757
883
 
758
- <!-- Support QR code (shown when support_qr_url is available) -->
759
- <div id="brand-support-qr" class="brand-support-qr" style="display:none">
760
- <button class="brand-support-qr-btn" id="brand-support-qr-btn" title="Click to enlarge" aria-label="View QR code larger">
761
- <img id="brand-support-qr-img" src="" alt="Support QR Code" class="brand-support-qr-img">
762
- <span class="brand-support-qr-zoom-hint">
763
- <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><line x1="11" y1="8" x2="11" y2="14"/><line x1="8" y1="11" x2="14" y2="11"/></svg>
764
- Click to enlarge
765
- </span>
766
- </button>
767
- <span class="brand-support-qr-label" data-i18n="settings.brand.label.supportQr">Tech Support</span>
884
+ <!-- Activation form (shown when not yet activated) -->
885
+ <div id="brand-activate-form" class="brand-activate-form">
886
+ <p class="brand-activate-desc" data-i18n="settings.brand.desc">
887
+ Have a serial number from a brand partner? Enter it below to activate branded mode.
888
+ </p>
889
+ <div class="brand-activate-fields">
890
+ <input id="settings-license-key" type="text" class="field-input"
891
+ placeholder="XXXXXXXX-XXXXXXXX-XXXXXXXX-XXXXXXXX-XXXXXXXX"
892
+ autocomplete="off" spellcheck="false">
893
+ <button id="btn-settings-activate" class="btn-primary" data-i18n="settings.brand.btn.activate">Activate</button>
768
894
  </div>
895
+ <div id="settings-activate-result" class="model-test-result"></div>
896
+ <!-- "Get a serial number" helper row — only rendered when the
897
+ brand has a homepage_url configured (set via Brand.refreshGetSerialLink).
898
+ Hidden by default so unbranded / no-homepage installs don't see a dead link. -->
899
+ <div id="brand-get-serial" class="brand-get-serial" style="display:none">
900
+ <span class="brand-get-serial-hint" data-i18n="settings.brand.getSerial.hint">Don't have one yet?</span>
901
+ <button type="button" id="btn-get-serial" class="btn-link-inline" data-i18n="settings.brand.btn.getSerial">Get a Serial Number</button>
902
+ </div>
903
+ </div>
769
904
 
770
- <!-- QR code lightbox modal -->
771
- <div id="qr-lightbox" class="qr-lightbox" style="display:none" role="dialog" aria-modal="true" aria-label="QR Code enlarged view">
772
- <div class="qr-lightbox-backdrop" id="qr-lightbox-backdrop"></div>
773
- <div class="qr-lightbox-content">
774
- <img id="qr-lightbox-img" src="" alt="Support QR Code" class="qr-lightbox-img">
775
- <p class="qr-lightbox-label" id="qr-lightbox-label">Tech Support</p>
776
- <p class="qr-lightbox-hint" data-i18n="settings.brand.label.qrHint">Scan with your phone camera</p>
777
- <button class="qr-lightbox-close" id="qr-lightbox-close" aria-label="Close">
778
- <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
779
- </button>
780
- </div>
905
+ </section>
906
+ </div><!-- end Tab: General -->
907
+
908
+ <!-- ══ Tab: About ══ -->
909
+ <div class="settings-tab-content" data-tab-content="about" style="display:none">
910
+ <!-- Version section -->
911
+ <section class="settings-section">
912
+ <div class="settings-section-title">
913
+ <span data-i18n="settings.about.version">Version</span>
914
+ </div>
915
+ <div class="settings-about-info">
916
+ <div class="about-item">
917
+ <span class="about-label" data-i18n="settings.about.appName">Application</span>
918
+ <span class="about-value" id="about-app-name">OpenClacky</span>
919
+ </div>
920
+ <div class="about-item">
921
+ <span class="about-label" data-i18n="settings.about.versionLabel">Version</span>
922
+ <span class="about-value" id="about-version">—</span>
781
923
  </div>
782
924
  </div>
783
- </div>
925
+ </section>
784
926
 
785
- <!-- Activation form (shown when not yet activated) -->
786
- <div id="brand-activate-form" class="brand-activate-form">
787
- <p class="brand-activate-desc" data-i18n="settings.brand.desc">
788
- Have a serial number from a brand partner? Enter it below to activate branded mode.
789
- </p>
790
- <div class="brand-activate-fields">
791
- <input id="settings-license-key" type="text" class="field-input"
792
- placeholder="XXXXXXXX-XXXXXXXX-XXXXXXXX-XXXXXXXX-XXXXXXXX"
793
- autocomplete="off" spellcheck="false">
794
- <button id="btn-settings-activate" class="btn-primary" data-i18n="settings.brand.btn.activate">Activate</button>
927
+ <!-- Links section -->
928
+ <section class="settings-section">
929
+ <div class="settings-section-title">
930
+ <span data-i18n="settings.about.links">Links</span>
795
931
  </div>
796
- <div id="settings-activate-result" class="model-test-result"></div>
797
- <!-- "Get a serial number" helper row — only rendered when the
798
- brand has a homepage_url configured (set via Brand.refreshGetSerialLink).
799
- Hidden by default so unbranded / no-homepage installs don't see a dead link. -->
800
- <div id="brand-get-serial" class="brand-get-serial" style="display:none">
801
- <span class="brand-get-serial-hint" data-i18n="settings.brand.getSerial.hint">Don't have one yet?</span>
802
- <button type="button" id="btn-get-serial" class="btn-link-inline" data-i18n="settings.brand.btn.getSerial">Get a Serial Number</button>
932
+ <div class="settings-about-links">
933
+ <a href="https://github.com/clacky-ai/openclacky" target="_blank" rel="noopener noreferrer" class="about-link">
934
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/></svg>
935
+ <span>GitHub</span>
936
+ </a>
937
+ <a href="https://www.openclacky.com/" target="_blank" rel="noopener noreferrer" class="about-link">
938
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>
939
+ <span data-i18n="settings.about.website">Website</span>
940
+ </a>
941
+ <a href="https://www.openclacky.com/docs/what-is-openclacky" target="_blank" rel="noopener noreferrer" class="about-link">
942
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>
943
+ <span data-i18n="settings.about.docs">Documentation</span>
944
+ </a>
803
945
  </div>
804
- </div>
946
+ </section>
947
+ </div>
805
948
 
806
- </section>
949
+ </div>
950
+ </div>
807
951
 
952
+ <!-- ── MODEL EDIT MODAL ──────────────────────────────────────────────── -->
953
+ <div id="model-edit-modal" class="modal-overlay" style="display:none">
954
+ <div class="modal-content model-edit-modal-content">
955
+ <div class="modal-header">
956
+ <h3 id="model-modal-title" data-i18n="settings.models.modal.add">Add Model</h3>
957
+ <button class="modal-close" id="model-modal-close" aria-label="Close">
958
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
959
+ </button>
960
+ </div>
961
+ <div class="modal-body">
962
+ <input type="hidden" id="model-modal-index" value="-1">
963
+
964
+ <label class="model-field" id="model-modal-provider-field">
965
+ <span class="field-label" data-i18n="settings.models.field.provider">Provider</span>
966
+ <div class="custom-select-wrapper" id="model-modal-provider-wrapper">
967
+ <div class="custom-select-trigger" tabindex="0" id="model-modal-provider-trigger">
968
+ <span class="custom-select-value placeholder" id="model-modal-provider-value" data-i18n="settings.models.placeholder.provider">Select Provider</span>
969
+ <svg class="custom-select-arrow" fill="none" stroke="currentColor" viewBox="0 0 24 24">
970
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
971
+ </svg>
972
+ </div>
973
+ <div class="custom-select-dropdown" id="model-modal-provider-dropdown"></div>
974
+ </div>
975
+ <div id="model-modal-promo-hint" class="provider-promo-hint" data-index="modal"></div>
976
+ </label>
977
+
978
+ <label class="model-field">
979
+ <span class="field-label" data-i18n="settings.models.field.model">Model</span>
980
+ <div class="model-name-combobox" id="model-modal-model-combobox">
981
+ <input type="text" id="model-modal-model" class="field-input model-name-input" placeholder="gpt-4o" autocomplete="off">
982
+ <button class="model-name-dropdown-btn" type="button" id="model-modal-model-dropdown-btn">
983
+ <svg width="12" height="12" fill="none" stroke="currentColor" viewBox="0 0 24 24">
984
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
985
+ </svg>
986
+ </button>
987
+ <div class="model-name-dropdown" id="model-modal-model-dropdown" style="display:none"></div>
988
+ </div>
989
+ </label>
990
+
991
+ <label class="model-field">
992
+ <span class="field-label" data-i18n="settings.models.field.baseurl">Base URL</span>
993
+ <div class="base-url-combobox" id="model-modal-baseurl-combobox">
994
+ <input type="text" id="model-modal-baseurl" class="field-input base-url-input" placeholder="https://api.openai.com/v1" autocomplete="off">
995
+ <button class="base-url-dropdown-btn" type="button" id="model-modal-baseurl-dropdown-btn">
996
+ <svg width="12" height="12" fill="none" stroke="currentColor" viewBox="0 0 24 24">
997
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
998
+ </svg>
999
+ </button>
1000
+ <div class="base-url-dropdown" id="model-modal-baseurl-dropdown" style="display:none"></div>
1001
+ </div>
1002
+ </label>
1003
+
1004
+ <label class="model-field">
1005
+ <span class="field-label">
1006
+ <span data-i18n="settings.models.field.apikey">API Key</span>
1007
+ <a class="get-apikey-link" id="model-modal-apikey-link" href="#" target="_blank" rel="noopener" style="display:none;margin-left:0.5rem;font-size:0.75rem;color:var(--accent,#6366f1);text-decoration:none;opacity:0.85;" data-i18n="settings.models.field.getApiKey">How to get →</a>
1008
+ </span>
1009
+ <div class="field-input-row">
1010
+ <input type="password" id="model-modal-apikey" class="field-input api-key-input" placeholder="sk-..." autocomplete="off">
1011
+ <button class="btn-toggle-key" id="model-modal-toggle-key" type="button" title="Show/hide key">
1012
+ <svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1013
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
1014
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path>
1015
+ </svg>
1016
+ </button>
1017
+ </div>
1018
+ </label>
1019
+
1020
+ <div id="model-modal-test-result" class="model-test-result"></div>
1021
+
1022
+ <label class="model-field model-field-checkbox" id="model-modal-default-field">
1023
+ <input type="checkbox" id="model-modal-set-default">
1024
+ <span data-i18n="settings.models.field.setDefault">Set as default model</span>
1025
+ </label>
1026
+ </div>
1027
+ <div class="modal-footer">
1028
+ <button id="model-modal-cancel" class="btn-secondary" data-i18n="settings.models.btn.cancel">Cancel</button>
1029
+ <button id="model-modal-save" class="btn-primary" data-i18n="settings.models.btn.save">Save</button>
1030
+ </div>
808
1031
  </div>
809
1032
  </div>
810
1033
 
@@ -992,6 +1215,9 @@
992
1215
  </div>
993
1216
  </div>
994
1217
 
1218
+ <!-- Toast stack — non-blocking notifications anchored bottom-right -->
1219
+ <div id="toast-stack" class="toast-stack" aria-live="polite" aria-atomic="true"></div>
1220
+
995
1221
  <!-- Rename Session Modal -->
996
1222
  <div id="rename-modal-overlay" class="modal-overlay" style="display:none">
997
1223
  <div class="modal-box sm">
@@ -1028,7 +1254,9 @@
1028
1254
  <script src="/tasks.js"></script>
1029
1255
  <script src="/skills.js"></script>
1030
1256
  <script src="/channels.js"></script>
1257
+ <script src="/mcp.js"></script>
1031
1258
  <script src="/settings.js"></script>
1259
+ <script src="/billing.js"></script>
1032
1260
  <script src="/onboard.js"></script>
1033
1261
  <script src="/brand.js"></script>
1034
1262
  <script src="/creator.js"></script>