Repository navigation
Expand file tree
/
Copy pathtokenlesscli-tutorial.html
More file actions
493 lines (452 loc) · 42.5 KB
/
Copy pathtokenlesscli-tutorial.html
File metadata and controls
493 lines (452 loc) · 42.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Tokenless CLI — Tutorial (EN/FA/DE)</title>
<style>
:root{
--bg:#0f1115; --panel:#171a21; --panel2:#1e222b; --border:#2b3140;
--text:#e6e9ef; --muted:#8a93a6; --accent:#5b8cff; --accent2:#7c6cff;
--ok:#4ade80; --warn:#fbbf24; --err:#ff6b6b; --blue:#2563eb;
--mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--text);font:15px/1.75 system-ui,-apple-system,Segoe UI,Roboto,Vazirmatn,Noto Naskh Arabic,sans-serif}
body.fa{font-family:Vazirmatn,Noto Naskh Arabic,system-ui,sans-serif}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
code{font-family:var(--mono);background:#0d0f13;border:1px solid var(--border);border-radius:6px;padding:1px 6px;font-size:13px}
pre{background:#0d0f13;border:1px solid var(--border);border-radius:12px;padding:16px;overflow-x:auto;font-size:13px;line-height:1.6}
pre code{background:none;border:none;padding:0}
.wrap{max-width:960px;margin:0 auto;padding:0 20px 100px}
.topbar{position:sticky;top:0;z-index:30;background:rgba(23,26,33,.94);backdrop-filter:blur(12px);border-bottom:1px solid var(--border);display:flex;align-items:center;gap:12px;flex-wrap:wrap;padding:12px 20px}
.brand{font-weight:800;font-size:16px;margin-right:auto;display:flex;flex-direction:column;line-height:1.2}
.brand small{font-weight:400;font-size:11px;color:var(--muted)}
.lang-toggle{display:flex;gap:4px;background:var(--panel2);border:1px solid var(--border);border-radius:999px;padding:3px}
.lang-toggle button{border:none;background:transparent;color:var(--muted);padding:6px 14px;border-radius:999px;font-size:12px;cursor:pointer;transition:.15s}
.lang-toggle button.active{background:var(--blue);color:#fff;font-weight:700}
.lang-toggle button:hover:not(.active){background:var(--border);color:var(--text)}
.pill{font-size:12px;border:1px solid var(--border);background:var(--panel2);border-radius:999px;padding:5px 12px;text-decoration:none;color:var(--text)}
.pill:hover{border-color:var(--accent);color:var(--accent)}
.hero{background:linear-gradient(135deg,rgba(91,140,255,.15),rgba(124,108,255,.10)),var(--panel);border:1px solid var(--border);border-radius:20px;padding:28px;margin:24px 0 20px}
.hero h1{margin:0 0 10px;font-size:28px;line-height:1.3}
.hero p{margin:0;color:var(--muted);max-width:70ch;line-height:1.7}
.badges{display:flex;flex-wrap:wrap;gap:8px;margin-top:16px}
.badge{font-size:12px;border:1px solid var(--border);background:var(--panel2);border-radius:999px;padding:4px 12px}
.badge.ok{border-color:#1e5c3a;color:var(--ok)}
.badge.info{border-color:#26497f;color:#a8c4ff}
.toc{background:var(--panel);border:1px solid var(--border);border-radius:16px;padding:18px 22px;margin:22px 0 28px}
.toc h2{margin:0 0 12px;font-size:13px;color:var(--muted);letter-spacing:.5px;text-transform:uppercase}
.toc ol{margin:0;padding-inline-start:20px;columns:2;column-gap:28px}
@media (max-width:700px){.toc ol{columns:1}}
.toc li{margin:5px 0}
.toc a{color:var(--text);border-bottom:1px dashed var(--border)}
.toc a:hover{color:var(--accent);border-color:var(--accent)}
h2.sec{font-size:21px;margin:50px 0 14px;padding-left:16px;border-left:4px solid var(--accent);line-height:1.3}
html[dir="rtl"] h2.sec{border-left:none;border-right:4px solid var(--accent);padding-left:0;padding-right:16px}
.card{background:var(--panel);border:1px solid var(--border);border-radius:16px;padding:18px 20px;margin:14px 0}
.card.highlight{border-color:var(--blue);background:linear-gradient(90deg,rgba(37,99,235,.10),var(--panel))}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:12px}
.step{display:flex;gap:14px;margin:20px 0;align-items:flex-start}
.num{width:34px;height:34px;border-radius:50%;background:var(--blue);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:800;flex-shrink:0;font-size:13px}
.diagram{background:var(--panel);border:1px solid var(--border);border-radius:16px;padding:18px;margin:18px 0;overflow-x:auto}
.diagram svg{width:100%;height:auto;max-width:860px}
table{width:100%;border-collapse:collapse;margin:14px 0;background:var(--panel);border:1px solid var(--border);border-radius:12px;overflow:hidden}
th,td{padding:10px 12px;text-align:left;border-bottom:1px solid var(--border);font-size:13px}
th{background:var(--panel2);color:var(--muted);font-weight:600}
html[dir="rtl"] th,html[dir="rtl"] td{text-align:right}
.links{display:flex;flex-wrap:wrap;gap:8px;margin:10px 0}
.links a{font-size:12px;background:var(--panel2);border:1px solid var(--border);padding:6px 12px;border-radius:999px}
.links a:hover{border-color:var(--accent);background:var(--panel)}
.lang{display:none}
.lang.active{display:block;animation:fade .2s ease}
@keyframes fade{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}
.note{font-size:13px;color:var(--muted);background:rgba(37,99,235,.08);border-left:3px solid var(--blue);padding:10px 14px;border-radius:8px;margin:12px 0}
html[dir="rtl"] .note{border-left:none;border-right:3px solid var(--blue)}
</style>
</head>
<body>
<div class="topbar">
<div class="brand">
<span id="brandTitle">Tokenless CLI — Tutorial</span>
<small id="brandSub">EN is default · FA · DE · with diagrams & links</small>
</div>
<div class="lang-toggle" id="langToggle">
<button data-lang="en" class="active">🇬🇧 EN</button>
<button data-lang="fa">🇮🇷 FA</button>
<button data-lang="de">🇩🇪 DE</button>
</div>
<a class="pill" href="/" id="chatLink">💬 Chat UI</a>
<a class="pill" href="https://github.com/Tabasiarash/tokenless-cli" target="_blank" id="repoLink">⭐ GitHub</a>
</div>
<div class="wrap">
<!-- HERO - will be translated via JS -->
<div class="hero">
<h1 id="heroTitle">What is Tokenless CLI?</h1>
<p id="heroDesc">Free Colab GPU for opencode & Gemini CLI — zero API keys, zero token costs. Notebook exposes Ollama via cloudflared tunnel, wizard writes config, you code.</p>
<div class="badges" id="badges">
<span class="badge ok">✔ 81 tests</span>
<span class="badge info">🔗 cloudflared</span>
<span class="badge">🧠 qwen2.5 / qwen3</span>
<span class="badge">🎨 EN / FA / DE</span>
</div>
<div class="links" id="heroLinks">
<a href="https://colab.research.google.com/" target="_blank">Colab</a>
<a href="https://ollama.com/" target="_blank">Ollama</a>
<a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/" target="_blank">cloudflared</a>
<a href="https://opencode.ai/" target="_blank">opencode</a>
<a href="https://github.com/google-gemini/gemini-cli" target="_blank">Gemini CLI</a>
<a href="https://docs.litellm.ai/" target="_blank">LiteLLM</a>
</div>
</div>
<!-- ENGLISH -->
<div class="lang lang-en active" id="content-en">
<div class="toc">
<h2>Contents — everything changes with language</h2>
<ol>
<li><a href="#en-what">What is it?</a></li>
<li><a href="#en-how">How it works (diagram)</a></li>
<li><a href="#en-parts">Project parts</a></li>
<li><a href="#en-prereq">Prerequisites</a></li>
<li><a href="#en-quick">Quick start (3 min)</a></li>
<li><a href="#en-tutorial">Step-by-step tutorial (5 steps)</a></li>
<li><a href="#en-paths">3 usage paths</a></li>
<li><a href="#en-models">Model tiers</a></li>
<li><a href="#en-files">File locations</a></li>
<li><a href="#en-limits">Session limits</a></li>
<li><a href="#en-security">Security</a></li>
<li><a href="#en-troubleshoot">Troubleshooting</a></li>
<li><a href="#en-faq">FAQ</a></li>
<li><a href="#en-check">Checklist</a></li>
</ol>
</div>
<h2 class="sec" id="en-what">1 — What is Tokenless CLI?</h2>
<div class="card highlight">
<p><strong>Tokenless CLI</strong> gives you <strong>free Colab GPU</strong> for <code>opencode</code> and <code>Gemini CLI</code> without API keys.</p>
<ul>
<li>Colab notebook installs <a href="https://ollama.com/" target="_blank">Ollama</a> + <a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/" target="_blank">cloudflared</a> quick tunnel</li>
<li>Local wizard checks tunnel, writes config, smoke-tests — GUI (<code>wizard_gui.py</code>) or CLI (<code>wizard.py</code>)</li>
<li>Web chat <code>web/chat.html</code> talks directly to tunnel — no local server needed, streaming</li>
<li>Gemini path: local <a href="https://docs.litellm.ai/" target="_blank">LiteLLM</a> bridge on <code>:4000</code> → Colab tunnel</li>
</ul>
<div class="links">
<a href="https://github.com/Tabasiarash/tokenless-cli/blob/main/colab_ollama.ipynb" target="_blank">📓 Open notebook</a>
<a href="https://github.com/Tabasiarash/tokenless-cli/blob/main/wizard_gui.py" target="_blank">🖥️ GUI wizard</a>
<a href="https://github.com/Tabasiarash/tokenless-cli/blob/main/web/chat.html" target="_blank">💬 Chat UI</a>
</div>
</div>
<h2 class="sec" id="en-how">2 — How it works</h2>
<div class="diagram">
<svg viewBox="0 0 880 200" xmlns="http://www.w3.org/2000/svg">
<defs><marker id="a" viewBox="0 0 10 10" refX="6" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" fill="#2b3140"/></marker></defs>
<rect x="10" y="20" width="160" height="70" rx="12" fill="#1e222b" stroke="#2b3140"/><text x="90" y="48" text-anchor="middle" fill="#e6e9ef" font-size="13" font-weight="700">💻 Your Laptop</text><text x="90" y="68" text-anchor="middle" fill="#8a93a6" font-size="11">wizard + opencode</text>
<rect x="200" y="20" width="160" height="70" rx="12" fill="#1e222b" stroke="#5b8cff"/><text x="280" y="48" text-anchor="middle" fill="#5b8cff" font-size="13" font-weight="700">🔗 cloudflared</text><text x="280" y="68" text-anchor="middle" fill="#8a93a6" font-size="11">https://xxx.trycloudflare.com</text>
<rect x="390" y="20" width="160" height="70" rx="12" fill="#1e222b" stroke="#4ade80"/><text x="470" y="48" text-anchor="middle" fill="#4ade80" font-size="13" font-weight="700">⚡ Colab GPU</text><text x="470" y="68" text-anchor="middle" fill="#8a93a6" font-size="11">Ollama + model</text>
<rect x="580" y="20" width="160" height="70" rx="12" fill="#1e222b" stroke="#fbbf24"/><text x="660" y="48" text-anchor="middle" fill="#fbbf24" font-size="13" font-weight="700">🌉 LiteLLM :4000</text><text x="660" y="68" text-anchor="middle" fill="#8a93a6" font-size="11">gemini → ollama</text>
<path d="M170 55 L200 55" stroke="#2b3140" stroke-width="2" marker-end="url(#a)"/><path d="M360 55 L390 55" stroke="#2b3140" stroke-width="2" marker-end="url(#a)"/><path d="M550 55 L580 55" stroke="#2b3140" stroke-width="2" marker-end="url(#a)"/>
<rect x="10" y="115" width="840" height="70" rx="12" fill="#171a21" stroke="#2b3140"/><text x="20" y="135" fill="#8a93a6" font-size="11">Flow: 1. Colab runs Ollama → 2. cloudflared exposes public URL → 3. Wizard writes ~/.config/opencode/opencode.jsonc → 4. opencode / chat / gemini use it → 5. Keep-alive pings every 5 min (prevents idle)</text><text x="20" y="155" fill="#8a93a6" font-size="11">Links: </text><text x="60" y="155" fill="#5b8cff" font-size="11">Colab notebook → cloudflared docs → Ollama docs → opencode.ai → Gemini CLI → LiteLLM docs</text>
</svg>
</div>
<div class="note">💡 The tunnel URL is like a password — public & unauthenticated. Don't share it. See <a href="#en-security">Security</a>.</div>
<h2 class="sec" id="en-parts">3 — Project parts</h2>
<table>
<tr><th>File</th><th>Role</th><th>Link</th></tr>
<tr><td><code>colab_ollama.ipynb</code></td><td>Installs Ollama, pulls model, starts tunnel + keep-alive</td><td><a href="https://github.com/Tabasiarash/tokenless-cli/blob/main/colab_ollama.ipynb" target="_blank">notebook</a></td></tr>
<tr><td><code>wizard.py</code> / <code>wizard_gui.py</code></td><td>CLI / GUI wizard: paste URL, check, write config, smoke test</td><td><a href="https://github.com/Tabasiarash/tokenless-cli/blob/main/wizard_gui.py" target="_blank">GUI</a></td></tr>
<tr><td><code>wizard_gui_i18n.py</code></td><td>Multilingual GUI (EN/FA/DE) with toggle — this edition</td><td><a href="/home/user/wizard_gui_i18n.py" target="_blank">file</a></td></tr>
<tr><td><code>web/chat.html</code></td><td>Browser chat, direct fetch, streaming</td><td><a href="https://github.com/Tabasiarash/tokenless-cli/blob/main/web/chat.html" target="_blank">chat</a></td></tr>
<tr><td><code>tokenless-chat-i18n.html</code></td><td>Multilingual chat (EN/FA/DE) matching GUI</td><td><a href="/" target="_blank">live</a></td></tr>
<tr><td><code>gemini_bridge.py</code></td><td>LiteLLM proxy config + launcher</td><td><a href="https://github.com/Tabasiarash/tokenless-cli/blob/main/gemini_bridge.py" target="_blank">bridge</a></td></tr>
</table>
<h2 class="sec" id="en-prereq">4 — Prerequisites</h2>
<div class="grid">
<div class="card"><strong>Colab</strong><br>Free T4 works for 14b (~9GB). Pro L4/A100 for 30b (~19GB, 256K). Keep tab open.<br><a href="https://colab.research.google.com/" target="_blank">→ colab.research.google.com</a></div>
<div class="card"><strong>Local</strong><br>Python 3.10+, <a href="https://opencode.ai/" target="_blank">opencode</a>, optional <code>npm i -g @google/gemini-cli</code> + <code>pip install 'litellm[proxy]'</code></div>
<div class="card"><strong>Network</strong><br>Tunnel is public. Treat URL like password. Stop Colab when done.</div>
</div>
<h2 class="sec" id="en-quick">5 — Quick start (3 min)</h2>
<div class="card highlight">
<ol>
<li>Open <a href="https://github.com/Tabasiarash/tokenless-cli/blob/main/colab_ollama.ipynb" target="_blank">colab_ollama.ipynb</a> → Run all → copy <code>https://xxxx.trycloudflare.com</code></li>
<li>Run <code>python3 wizard_gui_i18n.py</code> → paste URL → <strong>Check tunnel</strong> → <strong>Write config</strong></li>
<li>Click <strong>Smoke test</strong> → <strong>Open browser chat</strong> → start coding</li>
</ol>
</div>
<h2 class="sec" id="en-tutorial">6 — Step-by-step tutorial (5 steps)</h2>
<div class="step"><div class="num">1</div><div><strong>Start Colab</strong> — Run notebook, wait for Ollama pull (3-5 min for 14b). Copy tunnel URL. Keep-alive cell pings every 5 min.<br><span class="k">Link: <a href="https://github.com/Tabasiarash/tokenless-cli/blob/main/colab_ollama.ipynb" target="_blank">notebook source</a> · <a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/trycloudflare/" target="_blank">trycloudflare docs</a></span></div></div>
<div class="step"><div class="num">2</div><div><strong>Check tunnel</strong> — GUI: paste URL → Check tunnel → “✔ reachable - 3 model(s)”. CLI: <code>python3 wizard.py https://xxx.trycloudflare.com</code><br><span class="k">Verifies: <code>GET /v1/models</code> → list of models</span></div></div>
<div class="step"><div class="num">3</div><div><strong>Write config</strong> — Choose model + engine (opencode / gemini / both). Writes <code>~/.config/opencode/opencode.jsonc</code> (.bak kept) + <code>~/.tokenless-cli/litellm_config.yaml</code><br><span class="k">Code: <a href="https://github.com/Tabasiarash/tokenless-cli/blob/main/update_config.py" target="_blank">update_config.py</a></span></div></div>
<div class="step"><div class="num">4</div><div><strong>Smoke test</strong> — One-shot <code>POST /v1/chat/completions</code> with <code>tokenless-ok</code>. Expects model reply.<br><span class="k">Ensures end-to-end works before you open opencode</span></div></div>
<div class="step"><div class="num">5</div><div><strong>Use it</strong> — <code>opencode</code> in terminal, or web chat <code>?base=...&model=...</code>, or <code>~/.tokenless-cli/start_gemini.sh</code> for Gemini CLI<br><span class="k">Gemini needs <code>--sandbox=false</code> (see <a href="https://github.com/google-gemini/gemini-cli/issues/2168" target="_blank">issue #2168</a>)</span></div></div>
<div class="diagram">
<svg viewBox="0 0 700 120" xmlns="http://www.w3.org/2000/svg">
<rect x="10" y="10" width="120" height="40" rx="20" fill="#2563eb"/><text x="70" y="35" text-anchor="middle" fill="#fff" font-size="12" font-weight="700">1. Colab</text>
<rect x="150" y="10" width="120" height="40" rx="20" fill="#1e222b" stroke="#2b3140"/><text x="210" y="35" text-anchor="middle" fill="#e6e9ef" font-size="12">2. Check</text>
<rect x="290" y="10" width="120" height="40" rx="20" fill="#1e222b" stroke="#2b3140"/><text x="350" y="35" text-anchor="middle" fill="#e6e9ef" font-size="12">3. Write</text>
<rect x="430" y="10" width="120" height="40" rx="20" fill="#1e222b" stroke="#2b3140"/><text x="490" y="35" text-anchor="middle" fill="#e6e9ef" font-size="12">4. Smoke</text>
<rect x="570" y="10" width="120" height="40" rx="20" fill="#1a7f37"/><text x="630" y="35" text-anchor="middle" fill="#fff" font-size="12">5. Use</text>
<path d="M130 30 L150 30" stroke="#2b3140" stroke-width="2"/><path d="M270 30 L290 30" stroke="#2b3140" stroke-width="2"/><path d="M410 30 L430 30" stroke="#2b3140" stroke-width="2"/><path d="M550 30 L570 30" stroke="#2b3140" stroke-width="2"/>
</svg>
</div>
<h2 class="sec" id="en-paths">7 — 3 usage paths</h2>
<table>
<tr><th>Path</th><th>Command</th><th>Best for</th></tr>
<tr><td><strong>opencode</strong> (direct)</td><td><code>opencode</code></td><td>Recommended, zero extra deps, uses <code>~/.config/opencode/opencode.jsonc</code></td></tr>
<tr><td><strong>Web Chat</strong></td><td><a href="/" target="_blank">Open chat UI</a> with <code>?base=&model=</code></td><td>Zero install, streaming, any browser, this preview</td></tr>
<tr><td><strong>Gemini CLI</strong></td><td><code>~/.tokenless-cli/start_gemini.sh</code> → <code>gemini --sandbox=false</code></td><td>If you prefer Gemini CLI UX, needs LiteLLM on :4000</td></tr>
</table>
<h2 class="sec" id="en-models">8 — Model tiers</h2>
<table><tr><th>Tier</th><th>GPU</th><th>Model</th><th>Size / Context</th></tr>
<tr><td>Free T4</td><td>16GB</td><td><code>qwen2.5-coder:14b</code></td><td>~9GB</td></tr>
<tr><td>CPU fallback</td><td>—</td><td><code>qwen2.5-coder:7b</code></td><td>~5GB</td></tr>
<tr><td>Pro L4/A100/V100/L40S</td><td>22GB+</td><td><code>qwen3-coder:30b-a3b-q4_K_M</code></td><td>~19GB, 256K ctx</td></tr>
</table>
<div class="note">Override via <code>MODEL_OVERRIDE</code> env in Colab. See <a href="https://ollama.com/library" target="_blank">ollama.com/library</a> for other tags.</div>
<h2 class="sec" id="en-files">9 — File locations</h2>
<pre>~/.config/opencode/opencode.jsonc # main config (patched by wizard)
~/.config/opencode/opencode.jsonc.bak # backup
~/.tokenless-cli/last_setup.json # last BASE + model
~/.tokenless-cli/litellm_config.yaml # bridge config (10 gemini models → colab-tunnel)
~/.tokenless-cli/start_gemini.sh / .bat # launcher (starts litellm + gemini --sandbox=false)</pre>
<h2 class="sec" id="en-limits">10 — Session limits</h2>
<div class="card">Colab free ~12h max, may disconnect if idle. Keep-alive pings every 5 min but keep tab visible. Tunnel URL changes each run — re-paste into wizard. If disconnected, rerun notebook.</div>
<h2 class="sec" id="en-security">11 — Security</h2>
<div class="card">Tunnel is <strong>public & unauthenticated</strong> (quick tunnel). Anyone with URL can use your GPU. Treat URL like password, don't post publicly, stop Colab when done. No data stored on Cloudflare — direct TCP proxy.</div>
<h2 class="sec" id="en-troubleshoot">12 — Troubleshooting</h2>
<table><tr><th>Symptom</th><th>Fix</th><th>Link</th></tr>
<tr><td>tunnel unreachable</td><td>Colab running? URL correct? <code>curl https://xxx.try/v1/models</code></td><td><a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/trycloudflare/" target="_blank">trycloudflare docs</a></td></tr>
<tr><td>no models</td><td>Wait for Ollama pull (3-5 min for 14b). Check Colab logs.</td><td><a href="https://ollama.com/" target="_blank">ollama.com</a></td></tr>
<tr><td>opencode uses old model</td><td>Restart opencode after Write config</td><td><a href="https://opencode.ai/docs/" target="_blank">opencode docs</a></td></tr>
<tr><td>gemini needs --sandbox=false</td><td>Our launcher adds it. Known bug.</td><td><a href="https://github.com/google-gemini/gemini-cli/issues/2168" target="_blank">#2168</a></td></tr>
</table>
<h2 class="sec" id="en-faq">13 — FAQ</h2>
<div class="card"><strong>Costs?</strong> Zero token costs, Colab free GPU.<br><strong>Offline?</strong> No, needs Colab.<br><strong>Own model?</strong> Yes, set <code>MODEL_OVERRIDE</code> in notebook.<br><strong>Why cloudflared?</strong> No port forwarding, works behind NAT, free quick tunnels.<br><strong>Repo?</strong> <a href="https://github.com/Tabasiarash/tokenless-cli" target="_blank">Tabasiarash/tokenless-cli</a> MIT license.</div>
<h2 class="sec" id="en-check">14 — Checklist</h2>
<div class="card">
<ul>
<li>☐ Colab running, URL copied</li>
<li>☐ Wizard opened, language EN</li>
<li>☐ Check tunnel → 3 models</li>
<li>☐ Write config → smoke OK</li>
<li>☐ Open <a href="/" target="_blank">chat UI</a> or opencode</li>
</ul>
</div>
</div>
<!-- PERSIAN -->
<div class="lang lang-fa" id="content-fa">
<div class="toc">
<h2>فهرست — با تغییر زبان همه چیز عوض میشود</h2>
<ol>
<li><a href="#fa-what">چیست؟</a></li>
<li><a href="#fa-how">چطور کار میکند (دیاگرام)</a></li>
<li><a href="#fa-parts">بخشهای پروژه</a></li>
<li><a href="#fa-prereq">پیشنیازها</a></li>
<li><a href="#fa-quick">شروع سریع (۳ دقیقه)</a></li>
<li><a href="#fa-tutorial">آموزش گامبهگام (۵ مرحله)</a></li>
<li><a href="#fa-paths">۳ مسیر استفاده</a></li>
<li><a href="#fa-models">ردههای مدل</a></li>
<li><a href="#fa-files">محل فایلها</a></li>
<li><a href="#fa-limits">محدودیت سشن</a></li>
<li><a href="#fa-security">امنیت</a></li>
<li><a href="#fa-troubleshoot">عیبیابی</a></li>
<li><a href="#fa-faq">سؤالات متداول</a></li>
<li><a href="#fa-check">چکلیست</a></li>
</ol>
</div>
<h2 class="sec" id="fa-what">۱ — Tokenless CLI چیست؟</h2>
<div class="card highlight">
<p><strong>Tokenless CLI</strong> به شما <strong>GPU رایگان Colab</strong> برای <code>opencode</code> و <code>Gemini CLI</code> میدهد بدون API Key.</p>
<ul>
<li>نوتبوک Colab، <a href="https://ollama.com/" target="_blank">Ollama</a> + تونل <a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/" target="_blank">cloudflared</a> را نصب میکند</li>
<li>ویزارد محلی تونل را چک میکند، کانفیگ مینویسد، تست میکند — GUI یا CLI</li>
<li>چت تحت وب مستقیم با تونل صحبت میکند — بدون سرور محلی، استریمینگ</li>
<li>مسیر Gemini: پل محلی <a href="https://docs.litellm.ai/" target="_blank">LiteLLM</a> روی <code>:4000</code> → تونل Colab</li>
</ul>
<div class="links">
<a href="https://github.com/Tabasiarash/tokenless-cli/blob/main/colab_ollama.ipynb" target="_blank">📓 باز کردن نوتبوک</a>
<a href="https://github.com/Tabasiarash/tokenless-cli/blob/main/wizard_gui.py" target="_blank">🖥️ ویزارد گرافیکی</a>
<a href="/" target="_blank">💬 چت زنده</a>
</div>
</div>
<h2 class="sec" id="fa-how">۲ — چطور کار میکند</h2>
<div class="diagram">
<svg viewBox="0 0 880 200" xmlns="http://www.w3.org/2000/svg">
<defs><marker id="a-fa" viewBox="0 0 10 10" refX="6" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" fill="#2b3140"/></marker></defs>
<rect x="10" y="20" width="160" height="70" rx="12" fill="#1e222b" stroke="#2b3140"/><text x="90" y="48" text-anchor="middle" fill="#e6e9ef" font-size="13" font-weight="700">💻 لپتاپ شما</text><text x="90" y="68" text-anchor="middle" fill="#8a93a6" font-size="11">wizard + opencode</text>
<rect x="200" y="20" width="160" height="70" rx="12" fill="#1e222b" stroke="#5b8cff"/><text x="280" y="48" text-anchor="middle" fill="#5b8cff" font-size="13" font-weight="700">🔗 cloudflared</text><text x="280" y="68" text-anchor="middle" fill="#8a93a6" font-size="11">https://xxx.trycloudflare.com</text>
<rect x="390" y="20" width="160" height="70" rx="12" fill="#1e222b" stroke="#4ade80"/><text x="470" y="48" text-anchor="middle" fill="#4ade80" font-size="13" font-weight="700">⚡ GPU کولب</text><text x="470" y="68" text-anchor="middle" fill="#8a93a6" font-size="11">Ollama + مدل</text>
<rect x="580" y="20" width="160" height="70" rx="12" fill="#1e222b" stroke="#fbbf24"/><text x="660" y="48" text-anchor="middle" fill="#fbbf24" font-size="13" font-weight="700">🌉 LiteLLM :4000</text><text x="660" y="68" text-anchor="middle" fill="#8a93a6" font-size="11">gemini → ollama</text>
<path d="M170 55 L200 55" stroke="#2b3140" stroke-width="2" marker-end="url(#a-fa)"/><path d="M360 55 L390 55" stroke="#2b3140" stroke-width="2" marker-end="url(#a-fa)"/><path d="M550 55 L580 55" stroke="#2b3140" stroke-width="2" marker-end="url(#a-fa)"/>
<rect x="10" y="115" width="840" height="70" rx="12" fill="#171a21" stroke="#2b3140"/><text x="20" y="135" fill="#8a93a6" font-size="11">جریان: ۱. Colab، Ollama را اجرا میکند → ۲. cloudflared آدرس عمومی میدهد → ۳. ویزارد ~/.config/opencode/opencode.jsonc را مینویسد → ۴. opencode / چت / gemini از آن استفاده میکنند → ۵. Keep-alive هر ۵ دقیقه پینگ میکند</text>
</svg>
</div>
<h2 class="sec" id="fa-parts">۳ — بخشهای پروژه</h2>
<table><tr><th>فایل</th><th>نقش</th><th>لینک</th></tr>
<tr><td><code>colab_ollama.ipynb</code></td><td>نصب Ollama، دانلود مدل، اجرای تونل + keep-alive</td><td><a href="https://github.com/Tabasiarash/tokenless-cli/blob/main/colab_ollama.ipynb" target="_blank">نوتبوک</a></td></tr>
<tr><td><code>wizard_gui_i18n.py</code></td><td>ویزارد چندزبانه EN/FA/DE</td><td><a href="/home/user/wizard_gui_i18n.py" target="_blank">فایل</a></td></tr>
<tr><td><code>tokenless-chat-i18n.html</code></td><td>چت مرورگر چندزبانه هماهنگ با GUI</td><td><a href="/" target="_blank">زنده</a></td></tr>
</table>
<h2 class="sec" id="fa-prereq">۴ — پیشنیازها</h2>
<div class="grid">
<div class="card"><strong>Colab</strong><br>رایگان T4 برای 14b (~9GB). Pro L4/A100 برای 30b (~19GB، 256K). تب را باز نگه دارید.<br><a href="https://colab.research.google.com/" target="_blank">→ colab.research.google.com</a></div>
<div class="card"><strong>محلی</strong><br>Python 3.10+، <a href="https://opencode.ai/" target="_blank">opencode</a>، اختیاری <code>npm i -g @google/gemini-cli</code></div>
<div class="card"><strong>امنیت</strong><br>تونل عمومی است. آدرس را مثل رمز عبور بدانید.</div>
</div>
<h2 class="sec" id="fa-quick">۵ — شروع سریع (۳ دقیقه)</h2>
<div class="card highlight"><ol><li>نوتبوک <a href="https://github.com/Tabasiarash/tokenless-cli/blob/main/colab_ollama.ipynb" target="_blank">colab_ollama.ipynb</a> را باز کنید → همه را اجرا کنید → <code>https://xxxx.trycloudflare.com</code> را کپی کنید</li><li><code>python3 wizard_gui_i18n.py</code> را اجرا کنید → آدرس را پیست کنید → <strong>بررسی تونل</strong> → <strong>نوشتن کانفیگ</strong></li><li><strong>تست اتصال</strong> → <strong>باز کردن چت مرورگر</strong> → شروع کدنویسی</li></ol></div>
<h2 class="sec" id="fa-tutorial">۶ — آموزش گامبهگام (۵ مرحله)</h2>
<div class="step"><div class="num">۱</div><div><strong>اجرای Colab</strong> — نوتبوک را اجرا کنید، منتظر دانلود Ollama بمانید (۳-۵ دقیقه برای 14b). آدرس تونل را کپی کنید.<br><span class="k">لینک: <a href="https://github.com/Tabasiarash/tokenless-cli/blob/main/colab_ollama.ipynb" target="_blank">سورس نوتبوک</a> · <a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/trycloudflare/" target="_blank">مستندات trycloudflare</a></span></div></div>
<div class="step"><div class="num">۲</div><div><strong>بررسی تونل</strong> — در GUI: آدرس را پیست کنید → بررسی تونل → باید “✔ در دسترس - ۳ مدل” را ببینید.<br><span class="k">بررسی: <code>GET /v1/models</code></span></div></div>
<div class="step"><div class="num">۳</div><div><strong>نوشتن کانفیگ</strong> — مدل + موتور را انتخاب کنید. در <code>~/.config/opencode/opencode.jsonc</code> نوشته میشود.<br><span class="k">کد: <a href="https://github.com/Tabasiarash/tokenless-cli/blob/main/update_config.py" target="_blank">update_config.py</a></span></div></div>
<div class="step"><div class="num">۴</div><div><strong>تست اتصال</strong> — پیام تست از طریق تونل ارسال میشود.<br><span class="k">اتصال end-to-end را قبل از opencode تضمین میکند</span></div></div>
<div class="step"><div class="num">۵</div><div><strong>استفاده</strong> — <code>opencode</code> در ترمینال، یا چت تحت وب، یا <code>~/.tokenless-cli/start_gemini.sh</code><br><span class="k">Gemini به <code>--sandbox=false</code> نیاز دارد (<a href="https://github.com/google-gemini/gemini-cli/issues/2168" target="_blank">issue #2168</a>)</span></div></div>
<h2 class="sec" id="fa-paths">۷ — ۳ مسیر استفاده</h2>
<table><tr><th>مسیر</th><th>دستور</th><th>بهترین برای</th></tr>
<tr><td><strong>opencode</strong> (مستقیم)</td><td><code>opencode</code></td><td>پیشنهادی، بدون وابستگی اضافی</td></tr>
<tr><td><strong>چت تحت وب</strong></td><td><a href="/" target="_blank">باز کردن چت</a> با <code>?base=&model=</code></td><td>بدون نصب، استریمینگ، هر مرورگری</td></tr>
<tr><td><strong>Gemini CLI</strong></td><td><code>~/.tokenless-cli/start_gemini.sh</code></td><td>اگر UX Gemini CLI را ترجیح میدهید</td></tr>
</table>
<h2 class="sec" id="fa-models">۸ — ردههای مدل</h2>
<table><tr><th>رده</th><th>GPU</th><th>مدل</th><th>حجم / کانتکست</th></tr>
<tr><td>رایگان T4</td><td>16GB</td><td><code>qwen2.5-coder:14b</code></td><td>~9GB</td></tr>
<tr><td>Pro L4/A100</td><td>22GB+</td><td><code>qwen3-coder:30b-a3b-q4_K_M</code></td><td>~19GB، 256K</td></tr>
</table>
<h2 class="sec" id="fa-files">۹ — محل فایلها</h2>
<pre>~/.config/opencode/opencode.jsonc
~/.tokenless-cli/last_setup.json
~/.tokenless-cli/litellm_config.yaml
~/.tokenless-cli/start_gemini.sh</pre>
<h2 class="sec" id="fa-security">۱۱ — امنیت</h2>
<div class="card">تونل <strong>عمومی و بدون احراز هویت</strong> است. هر کسی با آدرس میتواند از GPU شما استفاده کند. آدرس را عمومی نکنید، بعد از کار Colab را متوقف کنید.</div>
<h2 class="sec" id="fa-check">۱۴ — چکلیست</h2>
<div class="card"><ul><li>☐ Colab در حال اجرا، آدرس کپی شده</li><li>☐ ویزارد باز شده، زبان FA</li><li>☐ بررسی تونل → ۳ مدل</li><li>☐ نوشتن کانفیگ → تست OK</li><li>☐ باز کردن <a href="/" target="_blank">چت</a></li></ul></div>
</div>
<!-- GERMAN -->
<div class="lang lang-de" id="content-de">
<div class="toc">
<h2>Inhalt — alles ändert sich mit der Sprache</h2>
<ol>
<li><a href="#de-what">Was ist es?</a></li>
<li><a href="#de-how">Wie es funktioniert (Diagramm)</a></li>
<li><a href="#de-parts">Projektteile</a></li>
<li><a href="#de-prereq">Voraussetzungen</a></li>
<li><a href="#de-quick">Schnellstart (3 Min)</a></li>
<li><a href="#de-tutorial">Schritt-für-Schritt (5 Schritte)</a></li>
<li><a href="#de-paths">3 Nutzungswege</a></li>
<li><a href="#de-models">Modell-Stufen</a></li>
<li><a href="#de-files">Dateiorte</a></li>
<li><a href="#de-limits">Session-Limits</a></li>
<li><a href="#de-security">Sicherheit</a></li>
<li><a href="#de-troubleshoot">Fehlerbehebung</a></li>
<li><a href="#de-faq">FAQ</a></li>
<li><a href="#de-check">Checkliste</a></li>
</ol>
</div>
<h2 class="sec" id="de-what">1 — Was ist Tokenless CLI?</h2>
<div class="card highlight">
<p><strong>Tokenless CLI</strong> gibt dir <strong>kostenlose Colab-GPU</strong> für <code>opencode</code> und <code>Gemini CLI</code> ohne API-Keys.</p>
<ul>
<li>Colab-Notebook installiert <a href="https://ollama.com/" target="_blank">Ollama</a> + <a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/" target="_blank">cloudflared</a> Quick-Tunnel</li>
<li>Lokaler Wizard prüft Tunnel, schreibt Config, Smoke-Test</li>
<li>Web-Chat spricht direkt mit Tunnel — kein lokaler Server, Streaming</li>
<li>Gemini-Pfad: lokale <a href="https://docs.litellm.ai/" target="_blank">LiteLLM</a> Brücke auf <code>:4000</code></li>
</ul>
<div class="links">
<a href="https://github.com/Tabasiarash/tokenless-cli/blob/main/colab_ollama.ipynb" target="_blank">📓 Notebook öffnen</a>
<a href="/" target="_blank">💬 Live Chat</a>
</div>
</div>
<h2 class="sec" id="de-how">2 — Wie es funktioniert</h2>
<div class="diagram">
<svg viewBox="0 0 880 200" xmlns="http://www.w3.org/2000/svg">
<defs><marker id="a-de" viewBox="0 0 10 10" refX="6" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" fill="#2b3140"/></marker></defs>
<rect x="10" y="20" width="160" height="70" rx="12" fill="#1e222b" stroke="#2b3140"/><text x="90" y="48" text-anchor="middle" fill="#e6e9ef" font-size="13" font-weight="700">💻 Dein Laptop</text><text x="90" y="68" text-anchor="middle" fill="#8a93a6" font-size="11">wizard + opencode</text>
<rect x="200" y="20" width="160" height="70" rx="12" fill="#1e222b" stroke="#5b8cff"/><text x="280" y="48" text-anchor="middle" fill="#5b8cff" font-size="13" font-weight="700">🔗 cloudflared</text><text x="280" y="68" text-anchor="middle" fill="#8a93a6" font-size="11">https://xxx.trycloudflare.com</text>
<rect x="390" y="20" width="160" height="70" rx="12" fill="#1e222b" stroke="#4ade80"/><text x="470" y="48" text-anchor="middle" fill="#4ade80" font-size="13" font-weight="700">⚡ Colab GPU</text><text x="470" y="68" text-anchor="middle" fill="#8a93a6" font-size="11">Ollama + Modell</text>
<rect x="580" y="20" width="160" height="70" rx="12" fill="#1e222b" stroke="#fbbf24"/><text x="660" y="48" text-anchor="middle" fill="#fbbf24" font-size="13" font-weight="700">🌉 LiteLLM :4000</text><text x="660" y="68" text-anchor="middle" fill="#8a93a6" font-size="11">gemini → ollama</text>
<path d="M170 55 L200 55" stroke="#2b3140" stroke-width="2" marker-end="url(#a-de)"/><path d="M360 55 L390 55" stroke="#2b3140" stroke-width="2" marker-end="url(#a-de)"/><path d="M550 55 L580 55" stroke="#2b3140" stroke-width="2" marker-end="url(#a-de)"/>
<rect x="10" y="115" width="840" height="70" rx="12" fill="#171a21" stroke="#2b3140"/><text x="20" y="135" fill="#8a93a6" font-size="11">Ablauf: 1. Colab startet Ollama → 2. cloudflared exposed öffentliche URL → 3. Wizard schreibt ~/.config/opencode/opencode.jsonc → 4. opencode / chat / gemini nutzen es → 5. Keep-alive pingt alle 5 Min</text>
</svg>
</div>
<h2 class="sec" id="de-quick">5 — Schnellstart (3 Min)</h2>
<div class="card highlight"><ol><li><a href="https://github.com/Tabasiarash/tokenless-cli/blob/main/colab_ollama.ipynb" target="_blank">colab_ollama.ipynb</a> öffnen → alle ausführen → <code>https://xxxx.trycloudflare.com</code> kopieren</li><li><code>python3 wizard_gui_i18n.py</code> → URL einfügen → <strong>Tunnel prüfen</strong> → <strong>Konfig schreiben</strong></li><li><strong>Verbindungstest</strong> → <strong>Browser-Chat öffnen</strong></li></ol></div>
<h2 class="sec" id="de-tutorial">6 — Schritt-für-Schritt (5 Schritte)</h2>
<div class="step"><div class="num">1</div><div><strong>Colab starten</strong> — Notebook ausführen, auf Ollama-Pull warten (3-5 Min für 14b). URL kopieren.<br><span class="k">Links: <a href="https://github.com/Tabasiarash/tokenless-cli/blob/main/colab_ollama.ipynb" target="_blank">Notebook</a> · <a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/trycloudflare/" target="_blank">trycloudflare docs</a></span></div></div>
<div class="step"><div class="num">2</div><div><strong>Tunnel prüfen</strong> — GUI: URL einfügen → Tunnel prüfen → “✔ erreichbar - 3 Modell(e)”<br><span class="k">Prüft: <code>GET /v1/models</code></span></div></div>
<div class="step"><div class="num">3</div><div><strong>Konfig schreiben</strong> — Modell + Engine wählen. Schreibt nach <code>~/.config/opencode/opencode.jsonc</code><br><span class="k">Code: <a href="https://github.com/Tabasiarash/tokenless-cli/blob/main/update_config.py" target="_blank">update_config.py</a></span></div></div>
<div class="step"><div class="num">4</div><div><strong>Verbindungstest</strong> — Einmal-Testnachricht durch Tunnel.<br><span class="k">Stellt Ende-zu-Ende sicher</span></div></div>
<div class="step"><div class="num">5</div><div><strong>Nutzen</strong> — <code>opencode</code> im Terminal, oder Web-Chat, oder <code>~/.tokenless-cli/start_gemini.sh</code><br><span class="k">Gemini braucht <code>--sandbox=false</code> (<a href="https://github.com/google-gemini/gemini-cli/issues/2168" target="_blank">#2168</a>)</span></div></div>
<h2 class="sec" id="de-check">14 — Checkliste</h2>
<div class="card"><ul><li>☐ Colab läuft, URL kopiert</li><li>☐ Wizard geöffnet, Sprache DE</li><li>☐ Tunnel prüfen → 3 Modelle</li><li>☐ Konfig schreiben → Test OK</li><li>☐ <a href="/" target="_blank">Chat</a> öffnen</li></ul></div>
</div>
</div>
<script>
const T = {
en: {
brandTitle: "Tokenless CLI — Tutorial",
brandSub: "EN is default · FA · DE · with diagrams & links",
heroTitle: "What is Tokenless CLI?",
heroDesc: "Free Colab GPU for opencode & Gemini CLI — zero API keys, zero token costs. Notebook exposes Ollama via cloudflared tunnel, wizard writes config, you code.",
chatLink: "💬 Chat UI",
repoLink: "⭐ GitHub"
},
fa: {
brandTitle: "آموزش Tokenless CLI",
brandSub: "انگلیسی پیشفرض · فارسی · آلمانی · با دیاگرام و لینک",
heroTitle: "Tokenless CLI چیست؟",
heroDesc: "GPU رایگان Colab برای opencode و Gemini CLI — بدون API Key، بدون هزینه توکن. نوتبوک Ollama را از طریق تونل cloudflared در دسترس قرار میدهد، ویزارد کانفیگ را مینویسد، شما کدنویسی میکنید.",
chatLink: "💬 چت",
repoLink: "⭐ گیتهاب"
},
de: {
brandTitle: "Tokenless CLI — Tutorial",
brandSub: "EN Standard · FA · DE · mit Diagrammen & Links",
heroTitle: "Was ist Tokenless CLI?",
heroDesc: "Kostenlose Colab-GPU für opencode & Gemini CLI — keine API-Keys, keine Token-Kosten. Notebook stellt Ollama via cloudflared Tunnel bereit, Wizard schreibt Config, du codest.",
chatLink: "💬 Chat UI",
repoLink: "⭐ GitHub"
}
};
let lang = "en";
try {
const s = localStorage.getItem("tokenless.lang");
if (s && T[s]) lang = s;
} catch(_){}
const q = new URLSearchParams(location.search).get("lang");
if (q && T[q]) lang = q;
function apply(){
const t = T[lang];
document.documentElement.lang = lang;
document.documentElement.dir = lang === "fa" ? "rtl" : "ltr";
document.body.className = lang === "fa" ? "fa" : "";
document.getElementById("brandTitle").textContent = t.brandTitle;
document.getElementById("brandSub").textContent = t.brandSub;
document.getElementById("heroTitle").textContent = t.heroTitle;
document.getElementById("heroDesc").textContent = t.heroDesc;
document.getElementById("chatLink").textContent = t.chatLink;
document.getElementById("repoLink").textContent = t.repoLink;
document.querySelectorAll(".lang-toggle button").forEach(b=> b.classList.toggle("active", b.dataset.lang===lang));
document.querySelectorAll(".lang").forEach(el=> el.classList.remove("active"));
const active = document.getElementById("content-"+lang);
if (active) active.classList.add("active");
try { localStorage.setItem("tokenless.lang", lang); } catch(_){}
}
function setLang(c){ if (!T[c]) return; lang=c; apply(); }
document.querySelectorAll(".lang-toggle button").forEach(b=> b.addEventListener("click", ()=> setLang(b.dataset.lang)));
apply();
// Smooth scroll for TOC links within active lang
document.addEventListener("click", e=>{
if (e.target.tagName==="A" && e.target.hash){
const id = e.target.hash.slice(1);
const target = document.getElementById(id);
if (target && target.closest(".lang.active")){
e.preventDefault();
target.scrollIntoView({behavior:"smooth", block:"start"});
history.replaceState(null,"",e.target.hash);
}
}
});
</script>
</body>
</html>