openclacky 1.1.6 → 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 +37 -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 +108 -6
  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 +519 -15
  35. data/lib/clacky/server/server_master.rb +8 -14
  36. data/lib/clacky/server/session_registry.rb +24 -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 +36 -5
  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 +3 -0
  52. data/lib/clacky/web/i18n.js +242 -24
  53. data/lib/clacky/web/index.html +351 -134
  54. data/lib/clacky/web/mcp.js +328 -0
  55. data/lib/clacky/web/sessions.js +193 -11
  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 +14 -1
  60. data/lib/clacky.rb +4 -0
  61. data/scripts/install.ps1 +23 -11
  62. metadata +30 -10
@@ -171,6 +171,18 @@
171
171
  </div>
172
172
  </div>
173
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>
174
186
  </div>
175
187
  </div>
176
188
 
@@ -207,6 +219,17 @@
207
219
  </div>
208
220
  </div>
209
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>
210
233
  </div>
211
234
  </div>
212
235
 
@@ -289,14 +312,7 @@
289
312
 
290
313
  <!-- Chat panel -->
291
314
  <div id="chat-panel" style="display:none">
292
- <!-- Mobile-only floating back button: shown inside a session on small screens.
293
- Desktop: hidden. All other session info (title, status, dir, delete) is
294
- provided by the bottom #session-info-bar. -->
295
- <button id="btn-back-mobile" class="chat-back-btn chat-back-floating" title="Back">
296
- <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">
297
- <path d="M15 18l-6-6 6-6"/>
298
- </svg>
299
- </button>
315
+
300
316
  <div id="messages"></div>
301
317
  <!-- New message notification banner -->
302
318
  <div id="new-message-banner" class="new-message-banner" style="display:none">
@@ -316,7 +332,7 @@
316
332
  <span id="sib-dir" data-i18n-title="sib.dir.tooltip" title="Click to change directory"></span>
317
333
  <span class="sib-sep sib-sep-after-dir">│</span>
318
334
  <span id="sib-model-wrap">
319
- <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>
320
336
  <div id="sib-model-dropdown" class="sib-model-dropdown" style="display:none"></div>
321
337
  </span>
322
338
  <span class="sib-sep sib-sep-after-model">│</span>
@@ -544,6 +560,19 @@
544
560
  </div>
545
561
  </div>
546
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
+
547
576
  <!-- ── TRASH PANEL — Recently deleted files across all projects ─── -->
548
577
  <div id="trash-panel" style="display:none">
549
578
  <div id="trash-body">
@@ -552,6 +581,13 @@
552
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>
553
582
  </div>
554
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
+
555
591
  <div class="trash-toolbar">
556
592
  <span class="trash-summary" id="trash-summary"></span>
557
593
  <div class="trash-actions">
@@ -587,7 +623,13 @@
587
623
  </div>
588
624
  </div>
589
625
 
590
- <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>
591
633
  </div>
592
634
  </div>
593
635
 
@@ -662,160 +704,330 @@
662
704
  </div>
663
705
  </div>
664
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
+
665
716
  <!-- ── SETTINGS PANEL ──────────────────────────────────────────────── -->
666
717
  <div id="settings-panel" style="display:none">
667
718
  <header id="settings-header">
668
719
  <span data-i18n="settings.title">Settings</span>
669
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
+
670
730
  <div id="settings-body">
671
731
 
672
- <!-- Models section -->
673
- <section class="settings-section">
674
- <div class="settings-section-title">
675
- <span data-i18n="settings.models.title">AI Models</span>
676
- <button id="btn-add-model" class="btn-settings-add" data-i18n="settings.models.add">+ Add Model</button>
677
- </div>
678
- <div id="model-cards"></div>
679
- </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>
680
743
 
681
- <!-- Personalize section -->
682
- <section class="settings-section">
683
- <div class="settings-section-title">
684
- <span data-i18n="settings.personalize.title">Personalize</span>
685
- </div>
686
- <div class="settings-personalize">
687
- <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>
688
- <button id="btn-rerun-onboard" class="btn-settings-action" data-i18n="settings.personalize.btn">✨ Re-run Onboard</button>
689
- </div>
690
- </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>
691
756
 
692
- <!-- Browser section -->
693
- <section class="settings-section">
694
- <div class="settings-section-title">
695
- <span data-i18n="settings.browser.title">Browser</span>
696
- </div>
697
- <div class="settings-personalize">
698
- <p class="settings-personalize-desc" id="browser-status-desc" data-i18n="settings.browser.desc">Connect your browser to enable browser automation.</p>
699
- <div class="settings-browser-actions">
700
- <label class="toggle-switch" id="browser-toggle-wrap" style="display:none">
701
- <input type="checkbox" id="browser-toggle-input">
702
- <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>
703
785
  </label>
704
- <button id="btn-browser-setup" class="btn-settings-action" data-i18n="settings.browser.btn">🌐 Configure Browser</button>
705
786
  </div>
706
- </div>
707
- </section>
787
+ </section>
788
+ </div>
708
789
 
709
- <!-- Language section -->
710
- <section class="settings-section" id="language-section">
711
- <div class="settings-section-title">
712
- <span data-i18n="settings.lang.title">Language</span>
713
- </div>
714
- <div class="settings-lang-btns">
715
- <button id="settings-btn-lang-en" class="settings-lang-btn" data-lang="en" data-i18n="settings.lang.en">English</button>
716
- <button id="settings-btn-lang-zh" class="settings-lang-btn" data-lang="zh" data-i18n="settings.lang.zh">中文</button>
717
- </div>
718
- </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>
719
802
 
720
- <!-- Font Size section -->
721
- <section class="settings-section" id="font-size-section">
722
- <div class="settings-section-title">
723
- <span data-i18n="settings.fontSize.title">Font Size</span>
724
- </div>
725
- <div class="settings-lang-btns">
726
- <button id="settings-btn-font-small" class="settings-lang-btn" data-font="small" data-i18n="settings.fontSize.small">小</button>
727
- <button id="settings-btn-font-medium" class="settings-lang-btn" data-font="medium" data-i18n="settings.fontSize.medium">中</button>
728
- <button id="settings-btn-font-large" class="settings-lang-btn" data-font="large" data-i18n="settings.fontSize.large">大</button>
729
- </div>
730
- </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>
731
819
 
732
- <!-- Brand & License section -->
733
- <section class="settings-section" id="brand-license-section">
734
- <div class="settings-section-title">
735
- <span data-i18n="settings.brand.title">Brand &amp; License</span>
736
- </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>
737
825
 
738
- <!-- Current brand status (shown when already activated) -->
739
- <div id="brand-status-card" class="brand-status-card" style="display:none">
740
- <div class="brand-status-main">
741
- <div class="brand-status-fields">
742
- <div class="brand-status-row">
743
- <span class="brand-status-label" data-i18n="settings.brand.label.brand">Brand</span>
744
- <span class="brand-status-value" id="brand-status-name">—</span>
745
- </div>
746
- <div class="brand-status-row">
747
- <span class="brand-status-label" data-i18n="settings.brand.label.status">Status</span>
748
- <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>
749
850
  </div>
750
- <div class="brand-status-row">
751
- <span class="brand-status-label" data-i18n="settings.brand.label.expires">Expires</span>
752
- <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>
753
855
  </div>
754
- <div class="brand-status-row" id="brand-status-homepage-row" style="display:none">
755
- <span class="brand-status-label" data-i18n="settings.brand.label.homepage">Homepage</span>
756
- <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>
757
867
  </div>
758
- <div class="brand-status-actions">
759
- <button id="btn-rebind-license" class="btn-settings-action" data-i18n="settings.brand.btn.change">Change Serial Number</button>
760
- <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>
761
880
  </div>
762
881
  </div>
763
- <!-- Support contact (shown when support_contact is available) -->
764
- <div id="brand-support-contact" class="brand-support-contact" style="display:none">
765
- <span class="brand-support-contact-label" data-i18n="settings.brand.label.supportContact">Support</span>
766
- <a id="brand-support-contact-link" class="brand-support-contact-value" href="#" target="_blank" rel="noopener noreferrer"></a>
767
- </div>
882
+ </div>
768
883
 
769
- <!-- Support QR code (shown when support_qr_url is available) -->
770
- <div id="brand-support-qr" class="brand-support-qr" style="display:none">
771
- <button class="brand-support-qr-btn" id="brand-support-qr-btn" title="Click to enlarge" aria-label="View QR code larger">
772
- <img id="brand-support-qr-img" src="" alt="Support QR Code" class="brand-support-qr-img">
773
- <span class="brand-support-qr-zoom-hint">
774
- <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>
775
- Click to enlarge
776
- </span>
777
- </button>
778
- <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>
779
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>
780
904
 
781
- <!-- QR code lightbox modal -->
782
- <div id="qr-lightbox" class="qr-lightbox" style="display:none" role="dialog" aria-modal="true" aria-label="QR Code enlarged view">
783
- <div class="qr-lightbox-backdrop" id="qr-lightbox-backdrop"></div>
784
- <div class="qr-lightbox-content">
785
- <img id="qr-lightbox-img" src="" alt="Support QR Code" class="qr-lightbox-img">
786
- <p class="qr-lightbox-label" id="qr-lightbox-label">Tech Support</p>
787
- <p class="qr-lightbox-hint" data-i18n="settings.brand.label.qrHint">Scan with your phone camera</p>
788
- <button class="qr-lightbox-close" id="qr-lightbox-close" aria-label="Close">
789
- <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>
790
- </button>
791
- </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>
792
923
  </div>
793
924
  </div>
794
- </div>
925
+ </section>
795
926
 
796
- <!-- Activation form (shown when not yet activated) -->
797
- <div id="brand-activate-form" class="brand-activate-form">
798
- <p class="brand-activate-desc" data-i18n="settings.brand.desc">
799
- Have a serial number from a brand partner? Enter it below to activate branded mode.
800
- </p>
801
- <div class="brand-activate-fields">
802
- <input id="settings-license-key" type="text" class="field-input"
803
- placeholder="XXXXXXXX-XXXXXXXX-XXXXXXXX-XXXXXXXX-XXXXXXXX"
804
- autocomplete="off" spellcheck="false">
805
- <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>
806
931
  </div>
807
- <div id="settings-activate-result" class="model-test-result"></div>
808
- <!-- "Get a serial number" helper row — only rendered when the
809
- brand has a homepage_url configured (set via Brand.refreshGetSerialLink).
810
- Hidden by default so unbranded / no-homepage installs don't see a dead link. -->
811
- <div id="brand-get-serial" class="brand-get-serial" style="display:none">
812
- <span class="brand-get-serial-hint" data-i18n="settings.brand.getSerial.hint">Don't have one yet?</span>
813
- <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>
814
945
  </div>
815
- </div>
946
+ </section>
947
+ </div>
816
948
 
817
- </section>
949
+ </div>
950
+ </div>
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>
818
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>
819
1031
  </div>
820
1032
  </div>
821
1033
 
@@ -1003,6 +1215,9 @@
1003
1215
  </div>
1004
1216
  </div>
1005
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
+
1006
1221
  <!-- Rename Session Modal -->
1007
1222
  <div id="rename-modal-overlay" class="modal-overlay" style="display:none">
1008
1223
  <div class="modal-box sm">
@@ -1039,7 +1254,9 @@
1039
1254
  <script src="/tasks.js"></script>
1040
1255
  <script src="/skills.js"></script>
1041
1256
  <script src="/channels.js"></script>
1257
+ <script src="/mcp.js"></script>
1042
1258
  <script src="/settings.js"></script>
1259
+ <script src="/billing.js"></script>
1043
1260
  <script src="/onboard.js"></script>
1044
1261
  <script src="/brand.js"></script>
1045
1262
  <script src="/creator.js"></script>