llm-switcher 1.6.7 → 1.6.8

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/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog — LLM Switcher
2
2
 
3
+ ## Release 1.6.8 — 2026-10-09
4
+
5
+ - **Dedicated Documentation & User Guide Hub (`#/docs`):** Integrated a full-featured, zero-dependency documentation viewer into the Web UI. Features a sticky scroll-spy table of contents, 1-click code copy with clipboard toast feedback, and 10 comprehensive guides covering edge gateway architecture, cross-platform installation, complete CLI cheatsheet, Blindfold TLS interception & CA safety guarantees, Antigravity CLI (`agy`) relay security, token optimizer interop with the Healer engine, Agent Skill (`SKILL.md`) & MCP server setup, Idle Compaction, Jev Goal Check gate, annotated `config.json` schema, and troubleshooting.
6
+ - **Audit & Documentation Alignment:** Reconciled documentation across README, localized guides, agent skills, and Model Context Protocol definitions to strictly reflect v1.6.8 multi-active routing, zero-mutation invariants, and Bifrost passthrough dynamics.
7
+
3
8
  ## Release 1.6.7 — 2026-10-09
4
9
 
5
10
  - **Jev Goal Check Interception Gate:** Intercept Claude Code stopping condition evaluators (`/goal`) and verify task completion independently using Jev System One typed decision judgments (`complete`, `evidence`, `unfinished`). Fails closed on missing or unverified work, preventing premature completion claims when models merely edit reports rather than implementing fixes.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "llm-switcher",
3
- "version": "1.6.7",
3
+ "version": "1.6.8",
4
4
  "description": "Zero-dependency multi-protocol edge gateway & provider switcher for Claude Code and Codex, with OpenAI-compatible, Anthropic and Vertex upstreams",
5
5
  "keywords": [
6
6
  "llm",
package/tests/ui.test.mjs CHANGED
@@ -115,3 +115,38 @@ test('the 1M switch is gone from the slots, the card and the payload', () => {
115
115
  assert.doesNotMatch(functionBody('collectCurrentSlotValues'), /model1M\s*[,}]/);
116
116
  assert.doesNotMatch(html, /profilePayload\.model1M/);
117
117
  });
118
+
119
+ test('documentation tab is properly integrated with sidebar navigation, sections, and helpers', () => {
120
+ // Sidebar navigation link
121
+ assert.match(html, /<a class="nav-link" href="#\/docs" data-nav="docs"/);
122
+ // View panel exists
123
+ assert.match(html, /<div id="view-docs" class="view-panel"/);
124
+ // Table of Contents navigation exists
125
+ assert.match(html, /<nav class="docs-toc" id="docsToc"/);
126
+
127
+ // All 10 core sections exist
128
+ const expectedSections = [
129
+ 'doc-overview',
130
+ 'doc-install',
131
+ 'doc-cli',
132
+ 'doc-blindfold',
133
+ 'doc-agy',
134
+ 'doc-optimizers',
135
+ 'doc-agent-mcp',
136
+ 'doc-advanced',
137
+ 'doc-config',
138
+ 'doc-faq'
139
+ ];
140
+ for (const s of expectedSections) {
141
+ assert.match(html, new RegExp(`id="${s}"`), `Missing section ${s} in ui.html`);
142
+ assert.match(html, new RegExp(`href="#${s}"`), `Missing TOC link for ${s} in ui.html`);
143
+ }
144
+
145
+ // Hash router handles 'docs'
146
+ assert.match(html, /validRoutes\s*=\s*\[[^\]]*'docs'[^\]]*\]/);
147
+ assert.match(html, /activeRoute === 'docs'/);
148
+
149
+ // Helper functions exist
150
+ assert.match(html, /function copyDocCode\(/);
151
+ assert.match(html, /function initDocsScrollSpy\(/);
152
+ });
package/ui.html CHANGED
@@ -866,6 +866,200 @@
866
866
  .row { padding: 0.75rem 1rem; flex-wrap: wrap; }
867
867
  .pgrid { grid-template-columns: 1fr; }
868
868
  }
869
+
870
+ /* Documentation Tab Styles */
871
+ .docs-container {
872
+ display: grid;
873
+ grid-template-columns: 240px minmax(0, 1fr);
874
+ gap: 1.5rem;
875
+ align-items: start;
876
+ }
877
+ @media (max-width: 980px) {
878
+ .docs-container { grid-template-columns: 1fr; }
879
+ .docs-toc { position: static !important; max-height: none !important; }
880
+ }
881
+ .docs-toc {
882
+ position: sticky;
883
+ top: 5.5rem;
884
+ background: var(--card);
885
+ border: 1px solid var(--line);
886
+ border-radius: 12px;
887
+ padding: 0.85rem;
888
+ max-height: calc(100vh - 7rem);
889
+ overflow-y: auto;
890
+ display: flex;
891
+ flex-direction: column;
892
+ gap: 0.25rem;
893
+ box-shadow: var(--shadow);
894
+ }
895
+ .docs-toc-title {
896
+ font-size: 0.74rem;
897
+ font-weight: 700;
898
+ color: var(--muted);
899
+ text-transform: uppercase;
900
+ letter-spacing: 0.05em;
901
+ padding: 0.25rem 0.5rem 0.5rem;
902
+ border-bottom: 1px solid var(--line);
903
+ margin-bottom: 0.35rem;
904
+ display: flex;
905
+ align-items: center;
906
+ gap: 0.4rem;
907
+ }
908
+ .docs-toc-link {
909
+ display: flex;
910
+ align-items: center;
911
+ gap: 0.5rem;
912
+ padding: 0.4rem 0.6rem;
913
+ border-radius: 6px;
914
+ color: var(--muted);
915
+ text-decoration: none;
916
+ font-size: 0.82rem;
917
+ font-weight: 500;
918
+ transition: background 0.15s, color 0.15s;
919
+ white-space: nowrap;
920
+ overflow: hidden;
921
+ text-overflow: ellipsis;
922
+ }
923
+ .docs-toc-link:hover {
924
+ color: var(--fg);
925
+ background: var(--card2);
926
+ }
927
+ .docs-toc-link.active {
928
+ color: var(--accent);
929
+ background: var(--accent-soft);
930
+ font-weight: 600;
931
+ }
932
+ .docs-content {
933
+ min-width: 0;
934
+ display: flex;
935
+ flex-direction: column;
936
+ gap: 1.25rem;
937
+ }
938
+ .doc-section {
939
+ background: var(--card);
940
+ border: 1px solid var(--line);
941
+ border-radius: 12px;
942
+ padding: 1.35rem 1.5rem;
943
+ scroll-margin-top: 5.5rem;
944
+ }
945
+ .doc-section-h {
946
+ display: flex;
947
+ align-items: center;
948
+ justify-content: space-between;
949
+ gap: 0.75rem;
950
+ padding-bottom: 0.85rem;
951
+ margin-bottom: 1.15rem;
952
+ border-bottom: 1px solid var(--line);
953
+ flex-wrap: wrap;
954
+ }
955
+ .doc-section-h h2 {
956
+ font-size: 1.15rem;
957
+ font-weight: 700;
958
+ color: var(--fg);
959
+ display: flex;
960
+ align-items: center;
961
+ gap: 0.55rem;
962
+ margin: 0;
963
+ }
964
+ .doc-section-h h2 svg {
965
+ width: 20px;
966
+ height: 20px;
967
+ color: var(--accent);
968
+ flex: none;
969
+ }
970
+ .doc-prose {
971
+ font-size: 0.88rem;
972
+ line-height: 1.65;
973
+ color: var(--fg);
974
+ }
975
+ .doc-prose p { margin-bottom: 0.85rem; }
976
+ .doc-prose p:last-child { margin-bottom: 0; }
977
+ .doc-prose h3 {
978
+ font-size: 0.96rem;
979
+ font-weight: 650;
980
+ color: var(--fg);
981
+ margin: 1.25rem 0 0.5rem;
982
+ display: flex;
983
+ align-items: center;
984
+ gap: 0.45rem;
985
+ }
986
+ .doc-prose ul, .doc-prose ol {
987
+ margin: 0.4rem 0 0.85rem 1.4rem;
988
+ }
989
+ .doc-prose li {
990
+ margin-bottom: 0.35rem;
991
+ }
992
+ .doc-codebox {
993
+ background: var(--bg);
994
+ border: 1px solid var(--line);
995
+ border-radius: 8px;
996
+ margin: 0.75rem 0 1rem;
997
+ overflow: hidden;
998
+ }
999
+ .doc-codebox-bar {
1000
+ display: flex;
1001
+ align-items: center;
1002
+ justify-content: space-between;
1003
+ padding: 0.35rem 0.75rem;
1004
+ background: var(--card2);
1005
+ border-bottom: 1px solid var(--line);
1006
+ font-size: 0.75rem;
1007
+ color: var(--muted);
1008
+ font-family: var(--font-mono);
1009
+ }
1010
+ .doc-codebox pre {
1011
+ margin: 0;
1012
+ padding: 0.85rem 1rem;
1013
+ overflow-x: auto;
1014
+ font-family: var(--font-mono);
1015
+ font-size: 0.82rem;
1016
+ line-height: 1.5;
1017
+ color: var(--fg);
1018
+ }
1019
+ .doc-callout {
1020
+ border-left: 3px solid;
1021
+ border-radius: 0 8px 8px 0;
1022
+ padding: 0.75rem 1rem;
1023
+ margin: 0.85rem 0;
1024
+ font-size: 0.84rem;
1025
+ line-height: 1.55;
1026
+ background: var(--card2);
1027
+ }
1028
+ .doc-callout.info {
1029
+ border-color: var(--accent);
1030
+ background: var(--accent-soft);
1031
+ }
1032
+ .doc-callout.warning {
1033
+ border-color: var(--warn);
1034
+ background: var(--warn-soft);
1035
+ }
1036
+ .doc-callout.success {
1037
+ border-color: var(--ok);
1038
+ background: var(--ok-soft);
1039
+ }
1040
+ .doc-callout.danger {
1041
+ border-color: var(--err);
1042
+ background: var(--err-soft);
1043
+ }
1044
+ .doc-table {
1045
+ width: 100%;
1046
+ border-collapse: collapse;
1047
+ margin: 0.85rem 0 1.15rem;
1048
+ font-size: 0.82rem;
1049
+ }
1050
+ .doc-table th, .doc-table td {
1051
+ border: 1px solid var(--line);
1052
+ padding: 0.55rem 0.75rem;
1053
+ text-align: left;
1054
+ }
1055
+ .doc-table th {
1056
+ background: var(--card2);
1057
+ font-weight: 600;
1058
+ color: var(--muted);
1059
+ }
1060
+ .doc-table tr:hover td {
1061
+ background: rgba(255, 255, 255, 0.02);
1062
+ }
869
1063
  </style>
870
1064
  </head>
871
1065
  <body>
@@ -926,6 +1120,10 @@
926
1120
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="9"/><path d="m9 12 2 2 4-4"/></svg>
927
1121
  System Doctor
928
1122
  </a>
1123
+ <a class="nav-link" href="#/docs" data-nav="docs">
1124
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1-2.5-2.5Z"/><path d="M6 6h10M6 10h10M6 14h7"/></svg>
1125
+ Docs & Guide
1126
+ </a>
929
1127
  </nav>
930
1128
 
931
1129
  <div class="spacer"></div>
@@ -1536,6 +1734,682 @@
1536
1734
  </div>
1537
1735
  </div>
1538
1736
  </div>
1737
+
1738
+ <!-- VIEW 7: Documentation & Guide -->
1739
+ <div id="view-docs" class="view-panel" style="display:none;">
1740
+ <div class="docs-container">
1741
+ <!-- Sticky Quick Index TOC -->
1742
+ <nav class="docs-toc" id="docsToc" aria-label="Documentation Index">
1743
+ <div class="docs-toc-title">
1744
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1-2.5-2.5Z"/><path d="M6 6h10M6 10h10"/></svg>
1745
+ <span>Mục Lục Hướng Dẫn</span>
1746
+ </div>
1747
+ <a class="docs-toc-link active" href="#doc-overview">1. Tổng Quan & Kiến Trúc</a>
1748
+ <a class="docs-toc-link" href="#doc-install">2. Hướng Dẫn Cài Đặt</a>
1749
+ <a class="docs-toc-link" href="#doc-cli">3. Lệnh CLI & Vận Hành</a>
1750
+ <a class="docs-toc-link" href="#doc-blindfold">4. Blindfold Mode & CA</a>
1751
+ <a class="docs-toc-link" href="#doc-agy">5. Antigravity CLI (agy)</a>
1752
+ <a class="docs-toc-link" href="#doc-optimizers">6. Tối Ưu Token & Healer</a>
1753
+ <a class="docs-toc-link" href="#doc-agent-mcp">7. Agent Skill & MCP Server</a>
1754
+ <a class="docs-toc-link" href="#doc-advanced">8. Compaction & Goal Check</a>
1755
+ <a class="docs-toc-link" href="#doc-config">9. Cấu Hình config.json</a>
1756
+ <a class="docs-toc-link" href="#doc-faq">10. Xử Lý Sự Cố (FAQ)</a>
1757
+ </nav>
1758
+
1759
+ <!-- Main Documentation Content -->
1760
+ <div class="docs-content">
1761
+ <!-- 1. OVERVIEW & ARCHITECTURE -->
1762
+ <section class="doc-section" id="doc-overview">
1763
+ <div class="doc-section-h">
1764
+ <h2>
1765
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/></svg>
1766
+ 1. Tổng Quan & Kiến Trúc Cốt Lõi
1767
+ </h2>
1768
+ <span class="badge" style="background:var(--accent-soft); color:var(--accent); font-weight:600;">v1.6.8 · Zero-Dependency</span>
1769
+ </div>
1770
+ <div class="doc-prose">
1771
+ <p>
1772
+ <strong>LLM Switcher</strong> là cổng ngõ biên (Edge Gateway) và bộ chuyển đổi đa giao thức LLM siêu nhẹ, hoàn toàn <strong>Zero-Dependency</strong> (xây dựng 100% bằng thư viện chuẩn của Node.js, không cần chạy <code>npm install</code>, khởi động &lt;50ms). Hệ thống đóng vai trò cầu nối thông minh và trong suốt giữa các AI Coding CLI hàng đầu (<strong>Claude Code</strong>, <strong>OpenAI Codex</strong>, <strong>Antigravity CLI <code>agy</code></strong>) với mọi nhà cung cấp mô hình LLM (OpenAI-compatible, Anthropic Native, Vertex Native, Google Code Assist).
1773
+ </p>
1774
+
1775
+ <div class="doc-callout info">
1776
+ <strong>Tại sao bạn cần LLM Switcher?</strong> Các proxy trung gian thông thường thường làm biến dạng giao thức phản hồi: làm mất khối suy luận <code>thinking_delta</code>, cắt vụn arguments của tool calls, làm lệch cache hoặc khiến Claude Code / Codex hiện banner cảnh báo "custom API endpoint". LLM Switcher chạy ngay trên máy cục bộ (<code>127.0.0.1:3456</code> và <code>:3457</code>), thực hiện <strong>100% Native Emulation</strong> và tự chữa lành (Self-Healing) payload mà tuyệt đối không sửa file cấu hình gốc của công cụ.
1777
+ </div>
1778
+
1779
+ <h3>Kiến Trúc Luồng Dữ Liệu</h3>
1780
+ <div class="doc-codebox">
1781
+ <div class="doc-codebox-bar"><span>Topology · Luồng Xử Lý Yêu Cầu</span></div>
1782
+ <pre>[Claude Code / Codex / agy CLI]
1783
+ │ (1. Khởi chạy qua Launcher Shim: nạp HTTPS_PROXY & CA in-memory)
1784
+ ▼
1785
+ [Blindfold Interceptor :3457] ──(TLS giải mã nội bộ cho anthropic/openai)
1786
+ │ (2. Strip credential client, chuyển tiếp /v1/messages, /v1/responses)
1787
+ ▼
1788
+ [LLM Switcher Edge Gateway :3456]
1789
+ │ ├── Healer Engine (Tự sửa schema rỗng {}, cứu tool_result mồ côi, gộp lượt)
1790
+ │ ├── Protocol IR Engine (Chuyển đổi 2 chiều Anthropic &lt;=&gt; OpenAI &lt;=&gt; Vertex)
1791
+ │ └── Bifrost Engine (Chuyển tiếp nguyên vẹn nếu gọi đúng model của hãng qua intact)
1792
+ ▼
1793
+ [Upstream Provider: intact (Khuyên dùng) / 9Router / Vertex / DeepSeek / Direct]</pre>
1794
+ </div>
1795
+
1796
+ <h3>4 Trụ Cột Đột Phá Của Hệ Thống</h3>
1797
+ <ul>
1798
+ <li><strong>100% Native Emulation:</strong> Chuyển đổi mọi phản hồi từ upstream thành luồng Anthropic SSE chuẩn (<code>thinking_delta</code> + <code>tool_use</code>) cho Claude Code, và Responses API events chuẩn cho Codex.</li>
1799
+ <li><strong>Zero Config Mutation:</strong> Tuyệt đối không đọc/ghi <code>~/.claude/settings.json</code> hay <code>~/.codex/config.toml</code>. Không bao giờ xuất hiện banner cảnh báo của nhà cung cấp.</li>
1800
+ <li><strong>Cửa Sổ Context Theo Model Thực Tế:</strong> Tự động phát hiện cửa sổ ngữ cảnh chính thức từ upstream (ví dụ 1M tokens), truyền đúng <code>max_context_window</code> và <code>context_window</code> trong <code>/v1/models</code> để Codex tự tin lập kế hoạch và nén lịch sử chính xác.</li>
1801
+ <li><strong>Bifrost Passthrough:</strong> Khi bạn dùng tài khoản của chính nhà cung cấp đó trên pool của <a href="https://github.com/louisphamdev/intact" target="_blank" style="color:var(--accent);">intact</a>, request sẽ đi xuyên qua nguyên vẹn 100% byte-for-byte, bỏ qua bộ dịch giao thức và Healer, chỉ thay đổi API key an toàn.</li>
1802
+ </ul>
1803
+ </div>
1804
+ </section>
1805
+
1806
+ <!-- 2. INSTALLATION GUIDE -->
1807
+ <section class="doc-section" id="doc-install">
1808
+ <div class="doc-section-h">
1809
+ <h2>
1810
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg>
1811
+ 2. Hướng Dẫn Cài Đặt Đa Nền Tảng
1812
+ </h2>
1813
+ <span class="badge" style="background:var(--ok-soft); color:var(--ok); font-weight:600;">Linux · macOS · Windows</span>
1814
+ </div>
1815
+ <div class="doc-prose">
1816
+ <p>
1817
+ <strong>Yêu cầu hệ thống:</strong> Node.js phiên bản <strong>18.17 trở lên</strong>. Gateway không yêu cầu bất kỳ thư viện npm phụ thuộc nào bên ngoài.
1818
+ </p>
1819
+
1820
+ <h3>Cách 1: Cài Đặt Toàn Cục Qua npm (Khuyên dùng)</h3>
1821
+ <div class="doc-codebox">
1822
+ <div class="doc-codebox-bar">
1823
+ <span>Terminal · npm global install</span>
1824
+ <button type="button" class="btn btn-outline btn-sm" onclick="copyDocCode(this, 'npm install -g llm-switcher\nmkdir -p -m 700 ~/.llm-switcher\ncp &quot;$(npm root -g)/llm-switcher/config.example.json&quot; ~/.llm-switcher/config.json')">Copy</button>
1825
+ </div>
1826
+ <pre>npm install -g llm-switcher
1827
+
1828
+ # Khởi tạo thư mục dữ liệu và sao chép cấu hình mẫu:
1829
+ mkdir -p -m 700 ~/.llm-switcher
1830
+ cp "$(npm root -g)/llm-switcher/config.example.json" ~/.llm-switcher/config.json</pre>
1831
+ </div>
1832
+ <p>
1833
+ <em>Ghi chú:</em> Khi cài đặt qua npm, file cấu hình <code>config.json</code>, khóa token <code>admin.token</code> và các file khởi chạy được lưu trữ an toàn trong <code>~/.llm-switcher</code>. Khi bạn chạy lệnh nâng cấp (update), cấu hình của bạn được giữ nguyên vẹn.
1834
+ </p>
1835
+
1836
+ <h3>Cách 2: Cài Đặt Từ Git Clone (Dành cho Developer)</h3>
1837
+ <div class="doc-codebox">
1838
+ <div class="doc-codebox-bar">
1839
+ <span>Terminal · Git Checkout</span>
1840
+ <button type="button" class="btn btn-outline btn-sm" onclick="copyDocCode(this, 'git clone https://github.com/louisphamdev/llm-switcher.git\ncd llm-switcher\ncp config.example.json config.json')">Copy</button>
1841
+ </div>
1842
+ <pre>git clone https://github.com/louisphamdev/llm-switcher.git
1843
+ cd llm-switcher
1844
+
1845
+ # Sao chép file cấu hình (config.json được gitignore bảo vệ an toàn):
1846
+ cp config.example.json config.json</pre>
1847
+ </div>
1848
+
1849
+ <h3>Cài Đặt Launcher Shims (Bắt buộc để định tuyến trong suốt)</h3>
1850
+ <p>
1851
+ Lệnh <code>switch shim install</code> tạo các wrapper siêu nhẹ trong <code>~/.llm-switcher/bin</code> để tiêm biến môi trường vào tiến trình CLI mà không can thiệp vào shell hay file cài đặt của hệ thống.
1852
+ </p>
1853
+ <div class="doc-codebox">
1854
+ <div class="doc-codebox-bar">
1855
+ <span>Terminal · Cấu hình PATH</span>
1856
+ <button type="button" class="btn btn-outline btn-sm" onclick="copyDocCode(this, 'switch shim install\n# Linux / macOS: thêm vào ~/.bashrc hoặc ~/.zshrc:\nexport PATH=&quot;$HOME/.llm-switcher/bin:$PATH&quot;')">Copy</button>
1857
+ </div>
1858
+ <pre>switch shim install
1859
+
1860
+ # Linux & macOS: Thêm dòng sau vào ~/.bashrc hoặc ~/.zshrc rồi mở terminal mới:
1861
+ export PATH="$HOME/.llm-switcher/bin:$PATH"
1862
+
1863
+ # Windows (Command Prompt): Đặt đường dẫn sau lên trước trong PATH User:
1864
+ %USERPROFILE%\.llm-switcher\bin</pre>
1865
+ </div>
1866
+
1867
+ <h3>Cài Đặt Dịch Vụ Nền Tự Khởi Động (OS Background Service)</h3>
1868
+ <p>
1869
+ Tự động đăng ký dịch vụ chạy ngầm cùng hệ điều hành: <code>systemd</code> (Linux), <code>launchd</code> (macOS) hoặc <code>Task Scheduler</code> (Windows).
1870
+ </p>
1871
+ <div class="doc-codebox">
1872
+ <div class="doc-codebox-bar">
1873
+ <span>Terminal · Service Install</span>
1874
+ <button type="button" class="btn btn-outline btn-sm" onclick="copyDocCode(this, 'switch service install --autoupdate')">Copy</button>
1875
+ </div>
1876
+ <pre># Cài đặt dịch vụ chạy ngầm kèm cờ tự động cập nhật bản mới khi đăng nhập:
1877
+ switch service install --autoupdate
1878
+
1879
+ # Kiểm tra trạng thái hoặc gỡ bỏ khi không dùng:
1880
+ switch service uninstall</pre>
1881
+ </div>
1882
+ <div class="doc-callout info">
1883
+ <strong>Mẹo cho máy chủ Linux (SSH):</strong> Để dịch vụ systemd user service tiếp tục chạy cả khi bạn ngắt kết nối SSH, hãy chạy lệnh: <code>loginctl enable-linger $USER</code> một lần duy nhất.
1884
+ </div>
1885
+ </div>
1886
+ </section>
1887
+
1888
+ <!-- 3. CLI & OPERATION GUIDE -->
1889
+ <section class="doc-section" id="doc-cli">
1890
+ <div class="doc-section-h">
1891
+ <h2>
1892
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>
1893
+ 3. Hướng Dẫn Sử Dụng & Bảng Tra Cứu Lệnh CLI
1894
+ </h2>
1895
+ <span class="badge" style="background:var(--card2); color:var(--muted);">Cheatsheet</span>
1896
+ </div>
1897
+ <div class="doc-prose">
1898
+ <p>
1899
+ CLI <code>switch</code> cung cấp giao diện quản trị đồng nhất trên toàn bộ các hệ điều hành:
1900
+ </p>
1901
+ <table class="doc-table">
1902
+ <thead>
1903
+ <tr>
1904
+ <th style="width:38%;">Lệnh CLI</th>
1905
+ <th>Mô tả chức năng chi tiết</th>
1906
+ </tr>
1907
+ </thead>
1908
+ <tbody>
1909
+ <tr>
1910
+ <td><code>switch on [profile]</code></td>
1911
+ <td>Khởi động Gateway và kích hoạt profile (mặc định nếu không truyền tên).</td>
1912
+ </tr>
1913
+ <tr>
1914
+ <td><code>switch off</code></td>
1915
+ <td>Dừng hoàn toàn Gateway và khôi phục các công cụ về endpoint chính hãng.</td>
1916
+ </tr>
1917
+ <tr>
1918
+ <td><code>switch off claude | codex | agy</code></td>
1919
+ <td>Tắt chuyển đổi cho một công cụ cụ thể; các công cụ còn lại vẫn tiếp tục hoạt động.</td>
1920
+ </tr>
1921
+ <tr>
1922
+ <td><code>switch ui</code></td>
1923
+ <td>Mở giao diện Web Dashboard trên trình duyệt mặc định kèm access token an toàn.</td>
1924
+ </tr>
1925
+ <tr>
1926
+ <td><code>switch status</code></td>
1927
+ <td>Hiển thị trạng thái hoạt động và cấu hình routing của cả 3 công cụ CLI.</td>
1928
+ </tr>
1929
+ <tr>
1930
+ <td><code>switch &lt;profile&gt;</code></td>
1931
+ <td>Kích hoạt cùng một profile cho cả Claude Code và Codex.</td>
1932
+ </tr>
1933
+ <tr>
1934
+ <td><code>switch claude &lt;profile&gt;</code></td>
1935
+ <td>Kích hoạt profile chỉ định riêng cho <strong>Claude Code</strong>.</td>
1936
+ </tr>
1937
+ <tr>
1938
+ <td><code>switch codex &lt;profile&gt;</code></td>
1939
+ <td>Kích hoạt profile chỉ định riêng cho <strong>OpenAI Codex</strong>.</td>
1940
+ </tr>
1941
+ <tr>
1942
+ <td><code>switch agy &lt;profile&gt;</code></td>
1943
+ <td>Kích hoạt profile chỉ định riêng cho <strong>Antigravity CLI</strong>.</td>
1944
+ </tr>
1945
+ <tr>
1946
+ <td><code>switch doctor</code></td>
1947
+ <td>Quét chẩn đoán: kiểm tra loopback binding, xung đột port, rò rỉ biến môi trường và phiên CLI đang chạy.</td>
1948
+ </tr>
1949
+ <tr>
1950
+ <td><code>switch update</code></td>
1951
+ <td>Tải và nâng cấp lên bản phát hành mới nhất từ npm/git, khởi động lại gateway không làm rớt phiên.</td>
1952
+ </tr>
1953
+ <tr>
1954
+ <td><code>switch port &lt;số&gt;</code></td>
1955
+ <td>Đổi cổng lắng nghe của Gateway (mặc định: 3456) và tự động khởi động lại service.</td>
1956
+ </tr>
1957
+ <tr>
1958
+ <td><code>switch shim status</code></td>
1959
+ <td>Kiểm tra tình trạng các launcher shim và phát hiện phiên CLI chạy lọt ra ngoài.</td>
1960
+ </tr>
1961
+ <tr>
1962
+ <td><code>switch plugin install</code></td>
1963
+ <td>Cài đặt thông báo trạng thái định tuyến bên trong Claude Code, Codex và agy.</td>
1964
+ </tr>
1965
+ <tr>
1966
+ <td><code>switch compact [on|off]</code></td>
1967
+ <td>Bật / tắt tính năng nén lịch sử hội thoại khi idle lâu ngày.</td>
1968
+ </tr>
1969
+ </tbody>
1970
+ </table>
1971
+
1972
+ <div class="doc-callout warning">
1973
+ <strong>Quy tắc quan trọng về môi trường:</strong> Tuyệt đối <em>không</em> thêm <code>source env.sh</code> hay <code>call env.cmd</code> vào file rc của shell. Các biến định tuyến được tự động tiêm độc lập vào từng phiên CLI thông qua các wrapper shim trong <code>~/.llm-switcher/bin</code>.
1974
+ </div>
1975
+ </div>
1976
+ </section>
1977
+
1978
+ <!-- 4. BLINDFOLD MODE & CA SECURITY -->
1979
+ <section class="doc-section" id="doc-blindfold">
1980
+ <div class="doc-section-h">
1981
+ <h2>
1982
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
1983
+ 4. Blindfold Mode & Bảo Mật Chứng Chỉ CA
1984
+ </h2>
1985
+ <span class="badge" style="background:var(--mint); color:#042f2e; font-weight:600;">Port :3457 · Zero-Warning</span>
1986
+ </div>
1987
+ <div class="doc-prose">
1988
+ <p>
1989
+ Trên Codex CLI, khi bạn ghi đè Base URL theo cách thông thường, giao diện <code>/model</code> sẽ hiển thị một dòng cảnh báo phiền toái: <em>"base URL is overridden to http://... Selecting models may not be supported..."</em>.
1990
+ </p>
1991
+ <p>
1992
+ <strong>Blindfold Mode</strong> giải quyết triệt để vấn đề này. Codex và Claude Code tiếp tục gửi request đến endpoint chính hãng, trong khi LLM Switcher đánh chặn ở tầng mạng thông qua proxy trung gian <code>HTTPS_PROXY=http://127.0.0.1:3457</code>.
1993
+ </p>
1994
+
1995
+ <h3>Bảng Định Tuyến Cố Định Của Interceptor (:3457)</h3>
1996
+ <table class="doc-table">
1997
+ <thead>
1998
+ <tr>
1999
+ <th>Domain Đích (CONNECT Host)</th>
2000
+ <th>Đường Dẫn Bị Đánh Chặn (Về Gateway :3456)</th>
2001
+ <th>Đường Dẫn Còn Lại (Tunnel Ra Internet Thật)</th>
2002
+ </tr>
2003
+ </thead>
2004
+ <tbody>
2005
+ <tr>
2006
+ <td><code>api.anthropic.com</code></td>
2007
+ <td><code>/v1/messages</code>, <code>/v1/messages/*</code></td>
2008
+ <td>Đăng nhập OAuth, telemetry, billing (giữ nguyên vẹn)</td>
2009
+ </tr>
2010
+ <tr>
2011
+ <td><code>api.openai.com</code></td>
2012
+ <td><code>/v1/responses</code>, <code>/v1/models</code></td>
2013
+ <td>Tài khoản, đăng nhập, thanh toán (giữ nguyên vẹn)</td>
2014
+ </tr>
2015
+ <tr>
2016
+ <td><code>chatgpt.com</code></td>
2017
+ <td><code>/backend-api/codex/*</code> (được chuẩn hóa thành <code>/v1</code>)</td>
2018
+ <td>Mọi request web và session khác (giữ nguyên vẹn)</td>
2019
+ </tr>
2020
+ <tr>
2021
+ <td><em>Mọi domain khác (Google, GitHub, Web)</em></td>
2022
+ <td><em>Không chặn</em></td>
2023
+ <td>Tunnel thẳng ra Internet dạng raw TCP, không đọc dữ liệu</td>
2024
+ </tr>
2025
+ </tbody>
2026
+ </table>
2027
+
2028
+ <h3>🔒 Cam Kết An Toàn & Cô Lập Chứng Chỉ CA</h3>
2029
+ <ul>
2030
+ <li><strong>Tự sinh 100% tại máy cục bộ:</strong> Cặp khóa <code>ca.pem</code> và <code>ca.key</code> được tự động tạo lần đầu bằng thư viện crypto chuẩn của Node.js. Private key được bảo vệ với quyền nghiêm ngặt <code>0600</code>.</li>
2031
+ <li><strong>Không can thiệp vào Hệ Điều Hành:</strong> Tuyệt đối <em>không</em> cài chứng chỉ vào Windows Certificate Store, macOS Keychain hay Linux <code>/etc/ssl/certs</code>. Bạn <strong>không cần quyền root hay Administrator</strong>.</li>
2032
+ <li><strong>Chỉ tin cậy trong phạm vi tiến trình (Process-Scoped):</strong> Chứng chỉ chỉ được tiêm vào bộ nhớ RAM của <code>claude</code> (qua <code>NODE_EXTRA_CA_CERTS</code>) và <code>codex</code> (qua <code>CODEX_CA_CERTIFICATE</code>). Trình duyệt web và ứng dụng khác hoàn toàn không tin cậy chứng chỉ này.</li>
2033
+ <li><strong>Ràng buộc tên miền mật mã học (X.509 Name Constraints):</strong> CA được cấu hình bắt buộc chỉ cho phép ký duy nhất cho 3 domain: <code>api.anthropic.com</code>, <code>api.openai.com</code>, và <code>chatgpt.com</code>. Ngay cả khi lộ private key, mọi kết nối tới ngân hàng, Google, GitHub đều bị từ chối ngay lập tức.</li>
2034
+ <li><strong>Bảo toàn chứng chỉ VPN / Doanh nghiệp:</strong> Nếu máy bạn có sẵn CA công ty trong <code>NODE_EXTRA_CA_CERTS</code>, script <code>ensure-ca-bundle.mjs</code> sẽ tự động gộp cả 2 chứng chỉ vào một bundle tạm thời, không bao giờ làm đứt mạng nội bộ.</li>
2035
+ </ul>
2036
+ </div>
2037
+ </section>
2038
+
2039
+ <!-- 5. ANTIGRAVITY CLI (AGY) -->
2040
+ <section class="doc-section" id="doc-agy">
2041
+ <div class="doc-section-h">
2042
+ <h2>
2043
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="12 2 2 7 12 12 22 7 12 2"/><polyline points="2 17 12 22 22 17"/><polyline points="2 12 12 17 22 12"/></svg>
2044
+ 5. Cấu Hình Antigravity CLI (agy) & Tiếp Sức An Toàn
2045
+ </h2>
2046
+ <span class="badge" style="background:var(--warn-soft); color:var(--warn); font-weight:600;">CLOUD_CODE_URL · Relay Proof</span>
2047
+ </div>
2048
+ <div class="doc-prose">
2049
+ <p>
2050
+ <strong>Antigravity CLI (<code>agy</code>)</strong> là client dòng lệnh của Google Antigravity. agy đọc biến môi trường <code>CLOUD_CODE_URL</code> và gửi các cuộc gọi Code Assist tới địa chỉ đó. Nhờ vậy, agy không cần thiết lập proxy mạng và không cần chứng chỉ TLS.
2051
+ </p>
2052
+
2053
+ <h3>Quy Trình Thiết Lập Cho agy</h3>
2054
+ <ol>
2055
+ <li>Đăng nhập agy bằng tài khoản Google của bạn (agy cần phiên đăng nhập này cho các tác vụ nội bộ).</li>
2056
+ <li>Tạo một profile có <code>"tool": "agy"</code> trong tab Routes (hoặc dùng template có sẵn <strong>agy on intact</strong>).</li>
2057
+ <li>Kích hoạt định tuyến và khởi chạy:
2058
+ <div class="doc-codebox">
2059
+ <div class="doc-codebox-bar">
2060
+ <span>Terminal · agy setup</span>
2061
+ <button type="button" class="btn btn-outline btn-sm" onclick="copyDocCode(this, 'switch shim install\nswitch agy &lt;profile&gt;\nagy')">Copy</button>
2062
+ </div>
2063
+ <pre>switch shim install
2064
+ switch agy &lt;profile&gt;
2065
+ agy</pre>
2066
+ </div>
2067
+ </li>
2068
+ </ol>
2069
+
2070
+ <h3>Phân Luồng Yêu Cầu Của agy</h3>
2071
+ <table class="doc-table">
2072
+ <thead>
2073
+ <tr>
2074
+ <th>Loại Cuộc Gọi</th>
2075
+ <th>Đích Đến Xử Lý</th>
2076
+ <th>Ghi Chú Xác Thực</th>
2077
+ </tr>
2078
+ </thead>
2079
+ <tbody>
2080
+ <tr>
2081
+ <td>Agent turn (<code>requestType: "agent"</code>)</td>
2082
+ <td>Profile của agy trên Switcher (Bifrost intact hoặc convert sang upstream)</td>
2083
+ <td>Mang API key của profile, không bao giờ mang token Google</td>
2084
+ </tr>
2085
+ <tr>
2086
+ <td>Checkpoint summary, model list, quota, đăng nhập</td>
2087
+ <td>Google Code Assist chính thức</td>
2088
+ <td>Giữ nguyên vẹn token và headers Google của bạn</td>
2089
+ </tr>
2090
+ <tr>
2091
+ <td>Mọi cuộc gọi khi agy đang tắt (<code>switch off agy</code>)</td>
2092
+ <td>Google Code Assist chính thức</td>
2093
+ <td>Gọi thẳng trực tiếp</td>
2094
+ </tr>
2095
+ </tbody>
2096
+ </table>
2097
+
2098
+ <div class="doc-callout info">
2099
+ <strong>Bảo vệ Token bằng Relay (<code>agy-relay.mjs</code>):</strong> Do agy gửi token xác thực dạng cleartext tới <code>CLOUD_CODE_URL</code>, shim của Switcher khởi chạy agy đằng sau một relay loopback chuyên dụng. Relay này bắt buộc Gateway phải cung cấp bằng chứng mã hóa dựa trên <code>gateway.secret</code> trước khi chuyển token. Nếu gateway chưa chứng minh được danh tính, agy sẽ nhận mã lỗi 502 và token không bao giờ bị phát tán.
2100
+ </div>
2101
+ </div>
2102
+ </section>
2103
+
2104
+ <!-- 6. TOKEN OPTIMIZERS & HEALER ENGINE -->
2105
+ <section class="doc-section" id="doc-optimizers">
2106
+ <div class="doc-section-h">
2107
+ <h2>
2108
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m12 14 4-4"/><path d="M3.34 19a10 10 0 1 1 17.32 0"/></svg>
2109
+ 6. Tối Ưu Token & Healer Engine
2110
+ </h2>
2111
+ <span class="badge" style="background:var(--ok-soft); color:var(--ok); font-weight:600;">Headroom · RTK · Ponytail</span>
2112
+ </div>
2113
+ <div class="doc-prose">
2114
+ <p>
2115
+ Các công cụ cắt tỉa prompt như <strong>Headroom</strong>, <strong>RTK (Rust Token Killer)</strong> và <strong>Ponytail</strong> cố gắng giảm input tokens bằng cách xóa bớt lịch sử hội thoại hoặc dòng lệnh. Tuy nhiên, hành động này thường <strong>phá vỡ đồ thị tin nhắn</strong>:
2116
+ </p>
2117
+ <ul>
2118
+ <li><strong>Orphaned <code>tool_result</code>:</strong> Optimizer xóa mất lượt <code>assistant</code> chứa <code>tool_use</code> nhưng lại giữ lại lượt <code>user</code> chứa <code>tool_result</code> $\implies$ Anthropic lập tức văng lỗi <code>HTTP 400 (tool_use_id does not correspond to any tool_use)</code>.</li>
2119
+ <li><strong>Consecutive <code>user</code> turns:</strong> Xóa các lượt trung gian khiến 2 lượt <code>user</code> đứng liền nhau $\implies$ Vi phạm quy tắc luân phiên role của Anthropic gây crash <code>HTTP 400</code>.</li>
2120
+ <li><strong>Reasoning Suppression:</strong> Tự ý gọt bỏ tham số <code>thinking</code> hoặc budget để tiết kiệm token $\implies$ Khiến các model tư duy sâu (Opus, Gemini Flash) trở nên nông cạn, trả lời ngắn cũn và lỗi code.</li>
2121
+ </ul>
2122
+
2123
+ <h3>Mô Hình Phân Tầng Bắt Buộc</h3>
2124
+ <div class="doc-codebox">
2125
+ <div class="doc-codebox-bar"><span>Kiến trúc tầng bảo vệ</span></div>
2126
+ <pre>[CLI Agent] ──> [Optimizer Proxy: Headroom / RTK] ──> [LLM Switcher :3456] ──> [Internet / Upstream]
2127
+ │
2128
+ ├── 1. Tự chữa lành tool_result mồ côi thành text
2129
+ ├── 2. Tự gộp các lượt cùng role liên tiếp
2130
+ ├── 3. Tự phục hồi tham số suy luận thinking
2131
+ └── 4. Giữ nguyên 100% tracing headers (x-rtk-*)</pre>
2132
+ </div>
2133
+
2134
+ <h3>Ma Trận Bảo Vệ Của Healer Engine</h3>
2135
+ <table class="doc-table">
2136
+ <thead>
2137
+ <tr>
2138
+ <th>Tình Huống Lỗi Do Optimizer Gây Ra</th>
2139
+ <th>Gọi Trực Tiếp Upstream</th>
2140
+ <th>Đi Qua LLM Switcher (Healer Engine)</th>
2141
+ </tr>
2142
+ </thead>
2143
+ <tbody>
2144
+ <tr>
2145
+ <td>Lượt <code>tool_result</code> bị mồ côi</td>
2146
+ <td>❌ <strong>HTTP 400 Crash</strong></td>
2147
+ <td>✅ <strong>HTTP 200 OK:</strong> Tự chuyển thành text block <code>[Tool Result (id)]</code></td>
2148
+ </tr>
2149
+ <tr>
2150
+ <td>Hai lượt <code>user</code> đứng liền kề</td>
2151
+ <td>❌ <strong>HTTP 400 Crash</strong></td>
2152
+ <td>✅ <strong>HTTP 200 OK:</strong> Tự gộp nội dung thành một lượt hợp lệ</td>
2153
+ </tr>
2154
+ <tr>
2155
+ <td>Tham số <code>thinking</code> bị optimizer cắt</td>
2156
+ <td>⚠️ <strong>Suy luận hời hợt</strong></td>
2157
+ <td>✅ <strong>HTTP 200 OK:</strong> Tự phát hiện reasoning model và bù lại thinking budget</td>
2158
+ </tr>
2159
+ <tr>
2160
+ <td>Schema rỗng <code>{}</code> của Gemini/Vertex</td>
2161
+ <td>❌ <strong>HTTP 400 Schema Error</strong></td>
2162
+ <td>✅ <strong>HTTP 200 OK:</strong> Tự bổ sung type string tương thích</td>
2163
+ </tr>
2164
+ </tbody>
2165
+ </table>
2166
+
2167
+ <h3>Cấu Hình Chuỗi Cho Các Optimizer</h3>
2168
+ <p>
2169
+ <strong>Headroom (headroom.yaml hoặc biến môi trường):</strong>
2170
+ </p>
2171
+ <div class="doc-codebox">
2172
+ <div class="doc-codebox-bar"><span>headroom config</span></div>
2173
+ <pre>ANTHROPIC_BASE_URL="http://127.0.0.1:3456"
2174
+ OPENAI_BASE_URL="http://127.0.0.1:3456/v1"</pre>
2175
+ </div>
2176
+ <p>
2177
+ <strong>RTK (Rust Token Killer):</strong>
2178
+ </p>
2179
+ <div class="doc-codebox">
2180
+ <div class="doc-codebox-bar"><span>rtk config</span></div>
2181
+ <pre>export UPSTREAM_URL="http://127.0.0.1:3456"</pre>
2182
+ </div>
2183
+ </div>
2184
+ </section>
2185
+
2186
+ <!-- 7. AGENT SKILL & MCP SERVER -->
2187
+ <section class="doc-section" id="doc-agent-mcp">
2188
+ <div class="doc-section-h">
2189
+ <h2>
2190
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2a10 10 0 1 0 10 10H12V2z"/><path d="M12 12 2.1 10.5"/></svg>
2191
+ 7. Tích Hợp Agent Skill & MCP Server
2192
+ </h2>
2193
+ <span class="badge" style="background:var(--accent-soft); color:var(--accent); font-weight:600;">Control-Plane Assets</span>
2194
+ </div>
2195
+ <div class="doc-prose">
2196
+ <p>
2197
+ Để đảm bảo các AI Agent tự hành (Claude Code, Cursor, Opencode, Windsurf) không bao giờ tự ý sửa đổi file cấu hình hệ thống hoặc bypass Gateway, LLM Switcher cung cấp 2 tài nguyên chuẩn hóa:
2198
+ </p>
2199
+
2200
+ <h3>1. Agent Skill (<code>skills/llm-switcher/SKILL.md</code>)</h3>
2201
+ <p>
2202
+ Tập luật bắt buộc dạy cho AI Agent:
2203
+ </p>
2204
+ <ul>
2205
+ <li>Mọi request LLM và proxy nén token phải trỏ về <code>http://127.0.0.1:3456</code>.</li>
2206
+ <li>Tuyệt đối không sửa <code>~/.claude/settings.json</code> hay <code>~/.codex/config.toml</code>.</li>
2207
+ <li>Không bao giờ hướng dẫn sub-agent chạy <code>source env.sh</code>.</li>
2208
+ </ul>
2209
+ <div class="doc-codebox">
2210
+ <div class="doc-codebox-bar">
2211
+ <span>Terminal · Cài đặt Skill</span>
2212
+ <button type="button" class="btn btn-outline btn-sm" onclick="copyDocCode(this, 'cp -r skills/llm-switcher ~/.config/opencode/skills/\ncp -r skills/llm-switcher ~/.claude/skills/')">Copy</button>
2213
+ </div>
2214
+ <pre># Cài đặt cho Opencode:
2215
+ cp -r skills/llm-switcher ~/.config/opencode/skills/
2216
+
2217
+ # Cài đặt cho Claude Code:
2218
+ cp -r skills/llm-switcher ~/.claude/skills/</pre>
2219
+ </div>
2220
+
2221
+ <h3>2. MCP Server (<code>mcp.mjs</code>)</h3>
2222
+ <p>
2223
+ Server đạt chuẩn Model Context Protocol hoạt động qua <code>stdio JSON-RPC 2.0</code> (không cần dependencies). Thêm vào cấu hình MCP (ví dụ <code>opencode.jsonc</code> hoặc <code>claude_desktop_config.json</code>):
2224
+ </p>
2225
+ <div class="doc-codebox">
2226
+ <div class="doc-codebox-bar">
2227
+ <span>opencode.jsonc / claude_desktop_config.json</span>
2228
+ <button type="button" class="btn btn-outline btn-sm" onclick="copyDocCode(this, '{\n &quot;mcp&quot;: {\n &quot;llm-switcher&quot;: {\n &quot;type&quot;: &quot;local&quot;,\n &quot;command&quot;: [&quot;node&quot;, &quot;/path/to/llm-switcher/mcp.mjs&quot;],\n &quot;enabled&quot;: true\n }\n }\n}')">Copy</button>
2229
+ </div>
2230
+ <pre>{
2231
+ "mcp": {
2232
+ "llm-switcher": {
2233
+ "type": "local",
2234
+ "command": ["node", "/path/to/llm-switcher/mcp.mjs"],
2235
+ "enabled": true
2236
+ }
2237
+ }
2238
+ }</pre>
2239
+ </div>
2240
+
2241
+ <h3>5 Công Cụ MCP Có Sẵn</h3>
2242
+ <ul>
2243
+ <li><code>switcher_status</code>: Kiểm tra trạng thái cổng Gateway và profile đang kích hoạt cho từng tool.</li>
2244
+ <li><code>switcher_audit</code>: Quét toàn diện môi trường máy trạm để phát hiện các request gọi thẳng ra ngoài hoặc tool nén token cấu hình sai.</li>
2245
+ <li><code>switcher_switch_profile</code>: Đổi profile linh hoạt theo từng CLI (<code>claude</code>, <code>codex</code>, <code>agy</code>).</li>
2246
+ <li><code>switcher_recent_logs</code>: Xem ring buffer các request gần nhất, độ trễ, số token và kết quả bóc tách reasoning block.</li>
2247
+ <li><code>switcher_models</code>: Lấy danh mục mô hình mới nhất từ Claude Code và Codex, hỗ trợ đồng bộ động từ upstream.</li>
2248
+ </ul>
2249
+ </div>
2250
+ </section>
2251
+
2252
+ <!-- 8. ADVANCED FEATURES: COMPACTION & GOAL CHECK -->
2253
+ <section class="doc-section" id="doc-advanced">
2254
+ <div class="doc-section-h">
2255
+ <h2>
2256
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
2257
+ 8. Tính Năng Nâng Cao: Compaction & Goal Check
2258
+ </h2>
2259
+ <span class="badge" style="background:var(--card2); color:var(--muted);">Pro Features</span>
2260
+ </div>
2261
+ <div class="doc-prose">
2262
+ <h3>A. Idle Compaction (Nén Prefix Khi Nhàn Rỗi)</h3>
2263
+ <p>
2264
+ Khi một phiên làm việc dừng lại quá lâu (&gt;15 phút mặc định), prompt cache của các nhà cung cấp LLM (Anthropic: 5 phút, OpenAI: 30 phút, Gemini: 1 giờ) sẽ hết hạn (cold cache). Request tiếp theo sẽ phải thanh toán toàn bộ chi phí token input ban đầu mà không được hưởng giảm giá cache.
2265
+ </p>
2266
+ <p>
2267
+ <strong>Idle Compaction</strong> phát hiện thời gian nhàn rỗi và tự động nén gọn prefix lịch sử cũ, lưu cache xác thực trong <code>STATE_DIR/idle-compact</code> mà không chạm vào file JSONL của Claude hay SQLite của Codex.
2268
+ </p>
2269
+ <div class="doc-codebox">
2270
+ <div class="doc-codebox-bar"><span>Terminal · Quản trị Compaction</span></div>
2271
+ <pre>switch compact on # Kích hoạt nén cho Claude Code
2272
+ switch compact codex on # Kích hoạt nén cho Codex
2273
+ switch compact idle 20 # Đặt ngưỡng nhàn rỗi là 20 phút (1-1440)
2274
+ switch compact status # Kiểm tra cấu hình hiện tại</pre>
2275
+ </div>
2276
+
2277
+ <h3>B. Goal Check: Cơ Chế Veto Độc Lập Bằng Jev System One</h3>
2278
+ <p>
2279
+ Trong Claude Code, khi dùng lệnh <code>/goal</code>, mô hình evaluator đọc transcript và có nguy cơ bị đánh lừa: coi việc <em>sửa báo cáo (rút finding)</em> là <em>đã sửa code thật</em> và vội vàng đánh dấu hoàn thành (False Completion).
2280
+ </p>
2281
+ <p>
2282
+ <strong>Goal Check</strong> chặn phản hồi của evaluator trước khi gửi về client, đồng thời gọi mô hình quyết định <strong>Jev (System One)</strong> chấm điểm trên 3 trục độc lập:
2283
+ </p>
2284
+ <ul>
2285
+ <li><code>complete</code> $\ge 0.90$: Công việc theo condition đã hoàn thành thực sự.</li>
2286
+ <li><code>evidence</code> $\ge 0.85$: Có tool-result trực tiếp chứng minh kết quả thực thi (chạy test, compile...).</li>
2287
+ <li><code>unfinished</code> $\le 0.10$: Không còn công việc dang dở hoặc lỗi chưa xử lý.</li>
2288
+ </ul>
2289
+ <div class="doc-callout success">
2290
+ <strong>Nguyên tắc Fail-Closed:</strong> Gateway chỉ trả về <code>ok: true</code> khi cả evaluator gốc lẫn Jev đồng thuận. Bất kỳ lỗi mạng, timeout hoặc thiếu evidence đều bị Veto (<code>ok: false</code>) để bảo vệ chất lượng công việc. Cấu hình tại tab <strong>Goal Check</strong> trên thanh điều hướng.
2291
+ </div>
2292
+ </div>
2293
+ </section>
2294
+
2295
+ <!-- 9. CONFIG.JSON REFERENCE -->
2296
+ <section class="doc-section" id="doc-config">
2297
+ <div class="doc-section-h">
2298
+ <h2>
2299
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><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"/></svg>
2300
+ 9. Bảng Cấu Trúc Cấu Hình (config.json)
2301
+ </h2>
2302
+ <span class="badge" style="background:var(--code); color:#042f2e; font-weight:600;">JSON Schema</span>
2303
+ </div>
2304
+ <div class="doc-prose">
2305
+ <p>
2306
+ File <code>config.json</code> nằm trong thư mục dữ liệu (<code>~/.llm-switcher/config.json</code> cho npm, hoặc thư mục gốc repo cho git clone):
2307
+ </p>
2308
+ <div class="doc-codebox">
2309
+ <div class="doc-codebox-bar">
2310
+ <span>config.json</span>
2311
+ <button type="button" class="btn btn-outline btn-sm" onclick="copyDocCode(this, document.getElementById('code-config-sample').textContent)">Copy</button>
2312
+ </div>
2313
+ <pre id="code-config-sample">{
2314
+ "port": 3456, // Cổng chính của LLM Switcher Edge Gateway
2315
+ "blindfold": { "port": 3457 }, // Cổng của Blindfold TLS Interceptor proxy
2316
+ "activeProfiles": {
2317
+ "claude": "intact-claude", // Profile kích hoạt cho Claude Code (/v1/messages)
2318
+ "codex": "intact-codex", // Profile kích hoạt cho Codex (/v1/responses)
2319
+ "agy": "agy-intact" // Profile kích hoạt cho Antigravity CLI
2320
+ },
2321
+ "profiles": {
2322
+ "intact-claude": {
2323
+ "name": "Claude via intact",
2324
+ "tool": "claude", // claude | codex | agy | null
2325
+ "mode": "convert", // convert | hybrid | direct
2326
+ "outFormat": "openai-chat", // openai-chat | anthropic | vertex
2327
+ "baseURL": "https://intact.example.com/v1",
2328
+ "apiKey": "sk-...",
2329
+ "thinkingMode": "auto", // auto | native | off
2330
+ "thinkingEffort": "auto", // auto | none | low | medium | high | max
2331
+ "defaultModels": {
2332
+ "opus": "ag/claude-opus-4-6-thinking",
2333
+ "sonnet": "ag/gemini-3.7-flash",
2334
+ "haiku": "ag/gemini-3.6-flash-medium"
2335
+ }
2336
+ },
2337
+ "intact-codex": {
2338
+ "name": "Codex via intact",
2339
+ "tool": "codex",
2340
+ "mode": "convert",
2341
+ "outFormat": "openai-chat",
2342
+ "baseURL": "https://intact.example.com/v1",
2343
+ "apiKey": "sk-...",
2344
+ "publicModels": ["gpt-5.6-sol", "gpt-5.6-terra"],
2345
+ "defaultModels": {
2346
+ "main": "gemini-3.8-flash",
2347
+ "review": "gemini-3.7-flash-medium",
2348
+ "subagent": "gemini-3.6-flash-low"
2349
+ }
2350
+ },
2351
+ "agy-intact": {
2352
+ "name": "agy on intact",
2353
+ "tool": "agy",
2354
+ "mode": "convert",
2355
+ "outFormat": "openai-chat",
2356
+ "baseURL": "https://intact.example.com/v1",
2357
+ "apiKey": "sk-...",
2358
+ "defaultModels": { "main": "antigravity/*" } // * giữ nguyên model của agy (Bifrost)
2359
+ }
2360
+ },
2361
+ "debug": false
2362
+ }</pre>
2363
+ </div>
2364
+ </div>
2365
+ </section>
2366
+
2367
+ <!-- 10. TROUBLESHOOTING & FAQ -->
2368
+ <section class="doc-section" id="doc-faq">
2369
+ <div class="doc-section-h">
2370
+ <h2>
2371
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
2372
+ 10. Xử Lý Sự Cố & Câu Hỏi Thường Gặp (FAQ)
2373
+ </h2>
2374
+ <span class="badge" style="background:var(--warn-soft); color:var(--warn); font-weight:600;">Troubleshooting</span>
2375
+ </div>
2376
+ <div class="doc-prose">
2377
+ <h3>Q1: Lỗi EADDRINUSE: Cổng 3456 hoặc 3457 đã bị tiến trình khác chiếm dụng?</h3>
2378
+ <p>
2379
+ Chạy lệnh <code>switch doctor</code> để phát hiện tiến trình đang giữ cổng. Bạn có thể đổi cổng gateway sang cổng khác bất kỳ bằng lệnh: <code>switch port 8080</code> (gateway sẽ tự động cập nhật và khởi động lại).
2380
+ </p>
2381
+
2382
+ <h3>Q2: Claude Code hiển thị banner "Custom API endpoint configured"?</h3>
2383
+ <p>
2384
+ Nguyên nhân do trước đây bạn đã cấu hình <code>ANTHROPIC_BASE_URL</code> trực tiếp vào <code>~/.claude/settings.json</code>. Hãy chạy <code>switch doctor</code>: Switcher sẽ phát hiện và dọn sạch các trường thừa này để trả về trạng thái Zero-Mutation trong suốt.
2385
+ </p>
2386
+
2387
+ <h3>Q3: Lệnh <code>claude --resume</code> trong cửa sổ terminal mới không đi qua Gateway?</h3>
2388
+ <p>
2389
+ Điều này xảy ra khi thư mục shims <code>~/.llm-switcher/bin</code> chưa được đặt lên đầu biến <code>PATH</code> của bạn. Hãy đảm bảo dòng <code>export PATH="$HOME/.llm-switcher/bin:$PATH"</code> có mặt trong <code>~/.bashrc</code> hoặc <code>~/.zshrc</code> và mở một terminal mới.
2390
+ </p>
2391
+
2392
+ <h3>Q4: Claude Code Remote Control (<code>claude rc</code>) có dùng được với Switcher không?</h3>
2393
+ <p>
2394
+ Một phiên Remote Control chạy qua 2 hop:
2395
+ </p>
2396
+ <ul>
2397
+ <li><strong>Session bridge (<code>api.anthropic.com/v1/code/sessions/*</code>):</strong> Chạy trực tiếp qua Anthropic để đồng bộ phiên và kiểm tra gói Max/Pro.</li>
2398
+ <li><strong>Inference:</strong> Các cuộc gọi model của agent mới đi qua LLM Switcher.</li>
2399
+ </ul>
2400
+ <p>
2401
+ <em>Lưu ý:</em> Bạn cần duy trì tài khoản Claude còn quota cho hop bridge; nếu tài khoản hết quota, phiên sẽ bị ngắt ngay tại bridge trước khi request model kịp đến Gateway.
2402
+ </p>
2403
+
2404
+ <h3>Q5: Trên Windows, làm thế nào để tạo chứng chỉ cho Blindfold Mode?</h3>
2405
+ <p>
2406
+ LLM Switcher tự động tạo chứng chỉ bằng Node.js crypto khi bạn kích hoạt tool. Nếu muốn tạo thủ công bằng OpenSSL, hãy chạy lệnh <code>bash blindfold/make-certs.sh</code> trong <strong>Git Bash</strong> (không chạy trong PowerShell vì lệnh <code>New-SelfSignedCertificate</code> thiếu thuộc tính <code>serverAuth</code>).
2407
+ </p>
2408
+ </div>
2409
+ </section>
2410
+ </div>
2411
+ </div>
2412
+ </div>
1539
2413
  </div>
1540
2414
  </div>
1541
2415
 
@@ -1875,7 +2749,7 @@
1875
2749
  function handleRoute() {
1876
2750
  const hash = window.location.hash || '#/routes';
1877
2751
  const route = hash.replace(/^#\/?/, '').split('/')[0] || 'routes';
1878
- const validRoutes = ['routes', 'compact', 'goal-check', 'models', 'logs', 'doctor'];
2752
+ const validRoutes = ['routes', 'compact', 'goal-check', 'models', 'logs', 'doctor', 'docs'];
1879
2753
  const activeRoute = validRoutes.includes(route) ? route : 'routes';
1880
2754
 
1881
2755
  // Update active nav link
@@ -1900,6 +2774,7 @@
1900
2774
  if (activeRoute === 'goal-check') loadGoalCheck();
1901
2775
  if (activeRoute === 'models') renderCatalogView();
1902
2776
  if (activeRoute === 'logs') loadInspectorLogs();
2777
+ if (activeRoute === 'docs') initDocsScrollSpy();
1903
2778
  }
1904
2779
  window.addEventListener('hashchange', handleRoute);
1905
2780
 
@@ -1966,7 +2841,55 @@
1966
2841
  actionsEl.innerHTML = `
1967
2842
  <button class="btn btn-outline" onclick="loadStatus()">Re-check Status</button>
1968
2843
  `;
2844
+ } else if (route === 'docs') {
2845
+ titleEl.textContent = 'Documentation & User Guide';
2846
+ subEl.textContent = 'Tài liệu hướng dẫn cài đặt, cấu hình, lệnh CLI, MCP, Skill và tối ưu hóa hệ thống chi tiết.';
2847
+ actionsEl.innerHTML = `
2848
+ <button class="btn btn-outline btn-sm" onclick="copyDocCode(this, 'npm install -g llm-switcher &amp;&amp; switch on')">Copy Quick Start</button>
2849
+ <button class="btn btn-primary btn-sm" onclick="window.open('https://github.com/louisphamdev/llm-switcher', '_blank')">GitHub Repository</button>
2850
+ `;
2851
+ }
2852
+ }
2853
+
2854
+ // Docs Helpers
2855
+ function copyDocCode(btn, codeText) {
2856
+ if (!navigator.clipboard) {
2857
+ showToast('Trình duyệt không hỗ trợ sao chép tự động', true);
2858
+ return;
1969
2859
  }
2860
+ navigator.clipboard.writeText(codeText).then(() => {
2861
+ showToast('Đã sao chép vào bộ nhớ tạm!');
2862
+ if (btn) {
2863
+ const originalText = btn.textContent;
2864
+ btn.textContent = '✓ Copied';
2865
+ setTimeout(() => { btn.textContent = originalText; }, 2000);
2866
+ }
2867
+ }, () => {
2868
+ showToast('Sao chép thất bại', true);
2869
+ });
2870
+ }
2871
+
2872
+ let docsObserver = null;
2873
+ function initDocsScrollSpy() {
2874
+ const tocLinks = document.querySelectorAll('#docsToc .docs-toc-link');
2875
+ if (!tocLinks.length) return;
2876
+ const sections = document.querySelectorAll('#view-docs .doc-section');
2877
+ if (docsObserver) docsObserver.disconnect();
2878
+ docsObserver = new IntersectionObserver((entries) => {
2879
+ entries.forEach(entry => {
2880
+ if (entry.isIntersecting) {
2881
+ const id = entry.target.id;
2882
+ tocLinks.forEach(link => {
2883
+ if (link.getAttribute('href') === '#' + id) {
2884
+ link.classList.add('active');
2885
+ } else {
2886
+ link.classList.remove('active');
2887
+ }
2888
+ });
2889
+ }
2890
+ });
2891
+ }, { rootMargin: '-10% 0px -75% 0px' });
2892
+ sections.forEach(s => docsObserver.observe(s));
1970
2893
  }
1971
2894
 
1972
2895
  // Modal Tabs