wormclaude 1.0.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.
package/README.md ADDED
@@ -0,0 +1,147 @@
1
+ # WormClaude CLI
2
+
3
+ Qwen 2.5 tabanlı, OpenAI-uyumlu backend'e bağlanan uçbirim (ink TUI) AI kodlama/güvenlik asistanı.
4
+ Claude Code'un araç/komut/agent mimarisinin **yerel, bulut-bağımsız** uyarlamasıdır.
5
+
6
+ ---
7
+
8
+ ## 📁 Dosya dosya: ne eklendi
9
+
10
+ ### Yeni dosyalar (`src/`) — bu oturumda sıfırdan eklendi
11
+
12
+ | Dosya | Ne yapar |
13
+ |-------|----------|
14
+ | `agent.ts` | Yeniden kullanılabilir tool-calling döngüsü (`runAgentLoop`) — alt-agent çekirdeği. `completeText()` tek-seferlik yanıt yardımcısı. |
15
+ | `commands.ts` | 18 slash komutu (`/help /clear /compact /context /cost /config /model /diff /commit /review /init /memory /doctor /mcp /tasks /kill /dream /export /resume /quit`). |
16
+ | `tasks.ts` | Arka plan görev kayıt defteri (registry) — shell/agent/dream görevleri, durum/çıktı izleme, EventEmitter. |
17
+ | `mcp.ts` | MCP istemcisi (resmi `@modelcontextprotocol/sdk`). `.wormclaude/mcp.json`'dan sunucu okur, araçları OpenAI şemasına çevirir. |
18
+ | `usage.ts` | Billing/token ölçümü — model-bazlı maliyet, oturum+kümülatif sayaç, bütçe limiti, `.wormclaude/usage.json`. |
19
+ | `compact.ts` | Oto-compact: bağlam eşiği + 9-bölümlü özet promptu + `isContextError` (reactive compact tetiği). |
20
+ | `memory.ts` | Oto-hafıza: SessionMemory tetiği + autoDream gate + PID kilidi. `.wormclaude/memory.md`. |
21
+ | `toolSummary.ts` | Araç-grubu özeti — 2+ araç sonrası tek satır git-commit tarzı etiket. |
22
+ | `tips.ts` | Spinner/footer ipuçları (14 ipucu, cooldown'lı, `.wormclaude/tips.json`). |
23
+
24
+ ### Değiştirilen dosyalar (mevcuttu, genişletildi)
25
+
26
+ | Dosya | Eklenen |
27
+ |-------|---------|
28
+ | `tools.ts` | Claude Code araç açıklamaları (birebir); `Agent` + `TaskOutput` araçları; `Bash run_in_background`; MCP yönlendirme; **paralel araç çalıştırma** (`executeToolCalls`); **per-tool metadata** (`TOOL_META`: readOnly/concurrencySafe/needsPermission/validate). |
29
+ | `api.ts` | `usage` yakalama (`stream_options.include_usage`); **retry/backoff** (`fetchWithRetry`, jitter); **abort/interrupt** (AbortSignal). |
30
+ | `cli.tsx` | Komut sistemi bağlama; footer task pill; MCP otomatik bağlanma; oto-compact + oto-hafıza tetikleri; usage kaydı; **izin dialogu**; **bütçe/maxTurns/Esc-kes**; **reactive compact**; tip + araç özeti gösterimi. |
31
+ | `theme.ts` | _(değişmedi)_ |
32
+
33
+ ### Backend (`C:\Users\potur\Desktop\modelegitim\api\server.py`)
34
+
35
+ | Eklenen | Açıklama |
36
+ |---------|----------|
37
+ | `POST /v1/chat/completions` | OpenAI-uyumlu gateway: streaming + **tools geçişi** + **usage**. Ollama'yı sarar. |
38
+ | `GET /v1/models` | Model listesi. |
39
+ | Model takma adı | `wormclaude` → `qwen2.5:7b` (env `WORMCLAUDE_DEFAULT_MODEL`). |
40
+ | Opsiyonel auth | `WORMCLAUDE_API_KEYS` env'i set edilirse Bearer key zorunlu. |
41
+ | Billing log | `api/billing.jsonl` — istek başına `{ts, key, model, prompt_tokens, completion_tokens}`. |
42
+
43
+ > Not: Eski route'lar (`/chat`, `/chat/stream`) korundu; sadece `/v1/*` eklendi.
44
+
45
+ ---
46
+
47
+ ## ⚙️ Özellikler (tümü çalışır, test edildi)
48
+
49
+ - **Araçlar:** Bash, Read, Write, Edit, Glob, Grep, WebFetch (Claude Code birebir açıklamalar) + Agent, TaskOutput
50
+ - **Çoklu-agent:** alt-agent (`Agent`), arka plan görevleri (`run_in_background`), koordinatör, dream
51
+ - **MCP:** stdio/SSE/HTTP sunucu desteği
52
+ - **Paralel araç çalıştırma:** salt-okunur araçlar eşzamanlı (limit 10), yazanlar sıralı
53
+ - **İzin sistemi:** Bash/Write/Edit öncesi onay (Evet / Evet-hep / Hayır)
54
+ - **Billing:** token/maliyet ölçümü + oturum bütçe limiti
55
+ - **Retry/backoff:** 408/409/429/5xx/529 + bağlantı hataları
56
+ - **Oto-compact + reactive compact:** bağlam dolunca / taşınca otomatik özetle
57
+ - **Oto-hafıza:** arka planda kalıcı bilgi çıkarımı
58
+ - **Araç özeti + ipuçları:** UX cilası
59
+ - **Interrupt:** çalışırken Esc ile kes
60
+
61
+ ---
62
+
63
+ ## 🚫 Dosya dosya: ne EKLENMEDİ (ve neden)
64
+
65
+ Claude Code kaynağından (`Desktop\tools`, `Desktop\wormclaudegenel`) bilerek atlananlar — hepsi Anthropic bulutuna/hesabına/SDK'sına bağlı, yerelde karşılığı yok:
66
+
67
+ ### `services/` — atlananlar
68
+ | Klasör/dosya | Neden eklenmedi |
69
+ |--------------|-----------------|
70
+ | `analytics/` (datadog, growthbook, sink...) | Anthropic telemetrisi |
71
+ | `api/` (claude.ts, bootstrap, usage, overage, referral) | Anthropic bulut API + faturalandırma _(mantığı: retry+billing uyarlandı)_ |
72
+ | `oauth/` | Anthropic hesabı/OAuth |
73
+ | `mcp/` (auth, claudeai, xaa, officialRegistry...) | Bulut-bağımlı MCP _(çekirdek MCP istemcisi yeniden yazıldı)_ |
74
+ | `plugins/` | Eklenti/marketplace sistemi |
75
+ | `policyLimits/`, `remoteManagedSettings/`, `settingsSync/`, `teamMemorySync/` | Kurumsal/uzak senkronizasyon |
76
+ | `voice*` (4 dosya) | Harici STT servisi |
77
+ | `rateLimit*`, `mockRateLimits`, `claudeAiLimits*` | Anthropic abonelik limitleri |
78
+ | `notifier`, `diagnosticTracking`, `internalLogging`, `vcr`, `preventSleep` | İç altyapı/OS |
79
+ | `lsp/` (7 dosya) | Kod dil-sunucusu — opsiyonel, kurulmadı |
80
+ | `MagicDocs/`, `AgentSummary/`, `PromptSuggestion/` (+speculation), `awaySummary` | İncelendi, orta/düşük değer → opsiyonel bırakıldı |
81
+
82
+ ### `cli/` (giriş/transport katmanı) — tamamı atlandı
83
+ `transports/` (WebSocket/SSE/remote), `handlers/` (auth/mcp/plugins/agents), `print.ts`, `structuredIO.ts`, `remoteIO.ts`, `update.ts` → Anthropic uzak agent/SDK protokolü, yerelde gereksiz.
84
+
85
+ ### `tasks/` — kısmen
86
+ | Eklenen | Atlanan |
87
+ |---------|---------|
88
+ | LocalShellTask (arka plan Bash), LocalAgentTask (alt-agent), DreamTask (hafıza) → `tasks.ts`'e uyarlandı | `RemoteAgentTask` (uzak bulut), `InProcessTeammateTask` (AsyncLocalStorage takım izolasyonu), `coordinator/` (analytics-bağımlı) |
89
+
90
+ ### Çekirdek motor (`query.ts`, `QueryEngine.ts`, `Tool.ts`, `tools.ts`) — incelendi
91
+ | Uyarlandı | Atlandı (opsiyonel) |
92
+ |-----------|---------------------|
93
+ | Agent döngüsü, paralel araç, per-tool arayüz, izin, bütçe/maxTurns/interrupt, reactive compact | Streaming tool executor, stop hooks, max-output-token escalation (8K→64K) |
94
+
95
+ ---
96
+
97
+ ## 🔧 Ortam değişkenleri
98
+
99
+ ```bash
100
+ WORMCLAUDE_BASE_URL=http://127.0.0.1:8788/v1 # backend
101
+ WORMCLAUDE_API_KEY=wormclaude # backend key
102
+ WORMCLAUDE_MODEL=wormclaude # model (backend qwen2.5:7b'ye çözer)
103
+ WORMCLAUDE_MAX_TURNS=25 # tur limiti
104
+ WORMCLAUDE_BUDGET_USD=0 # oturum maliyet tavanı (0=sınırsız)
105
+ WORMCLAUDE_CONTEXT_WINDOW=32768 # oto-compact için pencere
106
+ WORMCLAUDE_MAX_RETRIES=5 # bağlantı retry
107
+ WORMCLAUDE_MAX_TOOL_CONCURRENCY=10 # paralel araç limiti
108
+ # Backend tarafı:
109
+ WORMCLAUDE_DEFAULT_MODEL=qwen2.5:7b # takma ad hedefi
110
+ WORMCLAUDE_API_KEYS=key1,key2 # auth (boşsa kapalı)
111
+ OLLAMA_NUM_CTX=8192 # Ollama bağlam
112
+ ```
113
+
114
+ ---
115
+
116
+ ## 🚀 Kurulum & çalıştırma
117
+
118
+ ```bash
119
+ # Backend (ayrı pencere)
120
+ powershell -File C:\Users\potur\Desktop\modelegitim\api\BASLAT_API.ps1
121
+
122
+ # CLI
123
+ cd C:\Users\potur\Desktop\wormclaude-cli-ts
124
+ npm install
125
+ npm run build
126
+ wormclaude # veya: npm run dev
127
+ ```
128
+
129
+ ### MCP sunucu eklemek
130
+ `.wormclaude/mcp.json`:
131
+ ```json
132
+ {
133
+ "mcpServers": {
134
+ "filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "C:\\proje\\yol"] }
135
+ }
136
+ }
137
+ ```
138
+ Sonra CLI'da `/mcp` ile gör, `/mcp reconnect` ile yeniden bağla.
139
+
140
+ ---
141
+
142
+ ## 💰 Ticari (aylık abonelik) için hazır kancalar
143
+
144
+ 1. **Auth aç:** `WORMCLAUDE_API_KEYS="musteri1,musteri2"` — her müşteriye anahtar
145
+ 2. **Fiyat koy:** `usage.setPricing("qwen2.5:7b", { in: 0.5, out: 1.5 })` — `/cost` USD gösterir
146
+ 3. **Bütçe tavanı:** `WORMCLAUDE_BUDGET_USD=5` — oturum maliyeti aşınca durur
147
+ 4. **Faturalandır:** `api/billing.jsonl`'i aylık topla (server-tarafı = güvenilir kaynak)
package/dist/agent.js ADDED
@@ -0,0 +1,61 @@
1
+ // Faz 2 — Yeniden kullanılabilir tool-calling döngüsü (alt-agent çekirdeği).
2
+ // Ana CLI döngüsünden bağımsız; alt-agent'lar (Agent aracı) ve dream görevleri
3
+ // bunu headless olarak kullanır. Aynı OpenAI-uyumlu backend'e gider.
4
+ import { streamChat } from './api.js';
5
+ import { executeToolCalls } from './tools.js';
6
+ // Tek seferlik tam metin yanıt (araç yok). compact/memory/komutlar kullanır.
7
+ export async function completeText(messages, config) {
8
+ let out = '';
9
+ for await (const ev of streamChat(messages, [], config)) {
10
+ if (ev.type === 'text')
11
+ out += ev.text;
12
+ else if (ev.type === 'error')
13
+ return `[hata: ${ev.error}]`;
14
+ }
15
+ return out.trim();
16
+ }
17
+ export async function runAgentLoop(opts) {
18
+ const { config, tools, executeTool } = opts;
19
+ const messages = opts.messages;
20
+ const maxIters = opts.maxIters ?? 12;
21
+ let finalText = '';
22
+ let iter = 0;
23
+ for (; iter < maxIters; iter++) {
24
+ if (opts.signal?.aborted)
25
+ break;
26
+ let assistantText = '';
27
+ let toolCalls = [];
28
+ for await (const ev of streamChat(messages, tools, config)) {
29
+ if (ev.type === 'text') {
30
+ assistantText += ev.text;
31
+ opts.hooks?.onText?.(ev.text);
32
+ }
33
+ else if (ev.type === 'done') {
34
+ toolCalls = ev.toolCalls;
35
+ }
36
+ else if (ev.type === 'error') {
37
+ assistantText += `\n[hata: ${ev.error}]`;
38
+ }
39
+ }
40
+ const asMsg = { role: 'assistant', content: assistantText || '' };
41
+ if (toolCalls.length) {
42
+ asMsg.tool_calls = toolCalls.map((t) => ({
43
+ id: t.id, type: 'function', function: { name: t.name, arguments: t.args || '{}' },
44
+ }));
45
+ }
46
+ messages.push(asMsg);
47
+ if (assistantText.trim())
48
+ finalText = assistantText.trim();
49
+ if (!toolCalls.length)
50
+ break;
51
+ // Paralel çalıştırma (salt-okunur araçlar eşzamanlı). Sonuçlar orijinal sırada.
52
+ const results = await executeToolCalls(toolCalls, {
53
+ onStart: (c) => opts.hooks?.onTool?.(c.name, undefined),
54
+ onResult: (c, _i, res) => opts.hooks?.onToolResult?.(c.name, res.args, res.ok, res.output),
55
+ });
56
+ for (let i = 0; i < toolCalls.length; i++) {
57
+ messages.push({ role: 'tool', tool_call_id: toolCalls[i].id, content: results[i].output.slice(0, 8000) });
58
+ }
59
+ }
60
+ return { finalText, messages, iters: iter };
61
+ }
package/dist/api.js ADDED
@@ -0,0 +1,163 @@
1
+ // Backend istemci — OpenAI-uyumlu /v1/chat/completions (streaming + tools).
2
+ // Dayanıklılık: bağlantı kurulumunda exponential backoff + jitter retry.
3
+ // Ölçüm: usage (token) bilgisini 'done' olayında döndürür (billing için).
4
+ import { loadStored, DEFAULT_BASE_URL } from './auth.js';
5
+ export function loadConfig() {
6
+ const stored = loadStored();
7
+ return {
8
+ baseUrl: process.env.WORMCLAUDE_BASE_URL || stored.baseUrl || DEFAULT_BASE_URL,
9
+ apiKey: process.env.WORMCLAUDE_API_KEY || stored.apiKey || '',
10
+ model: process.env.WORMCLAUDE_MODEL || stored.model || 'wormclaude',
11
+ };
12
+ }
13
+ // ── Retry/backoff (api/withRetry.ts'den uyarlandı) ────────────────────────────
14
+ const MAX_RETRIES = Number(process.env.WORMCLAUDE_MAX_RETRIES) || 5;
15
+ const BASE_DELAY_MS = 500;
16
+ const MAX_DELAY_MS = 30000;
17
+ const RETRYABLE_STATUS = new Set([408, 409, 429, 500, 502, 503, 504, 529]);
18
+ const RETRYABLE_CODES = new Set(['ECONNRESET', 'EPIPE', 'ECONNREFUSED', 'ETIMEDOUT', 'UND_ERR_SOCKET']);
19
+ function backoff(attempt) {
20
+ const base = Math.min(BASE_DELAY_MS * 2 ** attempt, MAX_DELAY_MS);
21
+ return base + Math.random() * 0.25 * base; // jitter %25
22
+ }
23
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
24
+ // Sadece BAĞLANTI kurulurken retry edilir (stream başladıktan sonra retry edilmez
25
+ // — aksi halde kısmi metin tekrar gelir). HTTP başlamadan önceki hatalar güvenli.
26
+ async function fetchWithRetry(url, init) {
27
+ let lastErr;
28
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
29
+ try {
30
+ const res = await fetch(url, init);
31
+ if (res.ok)
32
+ return res;
33
+ if (!RETRYABLE_STATUS.has(res.status) || attempt === MAX_RETRIES)
34
+ return res;
35
+ try {
36
+ await res.body?.cancel();
37
+ }
38
+ catch { }
39
+ }
40
+ catch (e) {
41
+ if (e?.name === 'AbortError')
42
+ throw e; // kullanıcı kesti → retry etme
43
+ lastErr = e;
44
+ const code = e?.cause?.code || e?.code || '';
45
+ if (!RETRYABLE_CODES.has(code) && attempt === MAX_RETRIES)
46
+ throw e;
47
+ if (attempt === MAX_RETRIES)
48
+ throw e;
49
+ }
50
+ await sleep(backoff(attempt));
51
+ }
52
+ throw lastErr ?? new Error('retry exhausted');
53
+ }
54
+ export async function* streamChat(messages, tools, config, signal) {
55
+ const body = {
56
+ model: config.model,
57
+ messages,
58
+ stream: true,
59
+ temperature: 0.3,
60
+ stream_options: { include_usage: true }, // son chunk'ta usage döner
61
+ };
62
+ if (tools && tools.length)
63
+ body.tools = tools;
64
+ let res;
65
+ try {
66
+ res = await fetchWithRetry(`${config.baseUrl}/chat/completions`, {
67
+ method: 'POST',
68
+ headers: {
69
+ 'Content-Type': 'application/json',
70
+ ...(config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}),
71
+ },
72
+ body: JSON.stringify(body),
73
+ signal,
74
+ });
75
+ }
76
+ catch (e) {
77
+ if (signal?.aborted || e?.name === 'AbortError') {
78
+ yield { type: 'done', toolCalls: [] };
79
+ return;
80
+ }
81
+ yield { type: 'error', error: `Backend'e ulaşılamadı (${config.baseUrl}): ${e?.message || e}` };
82
+ return;
83
+ }
84
+ if (!res.ok || !res.body) {
85
+ let detail = '';
86
+ try {
87
+ detail = await res.text();
88
+ }
89
+ catch { }
90
+ yield { type: 'error', error: `HTTP ${res.status}: ${detail.slice(0, 300)}` };
91
+ return;
92
+ }
93
+ const reader = res.body.getReader();
94
+ const decoder = new TextDecoder();
95
+ let buf = '';
96
+ const toolAcc = {};
97
+ let usage;
98
+ while (true) {
99
+ if (signal?.aborted) {
100
+ try {
101
+ await reader.cancel();
102
+ }
103
+ catch { }
104
+ break;
105
+ }
106
+ let chunk;
107
+ try {
108
+ chunk = await reader.read();
109
+ }
110
+ catch (e) {
111
+ if (signal?.aborted || e?.name === 'AbortError')
112
+ break;
113
+ throw e;
114
+ }
115
+ const { done, value } = chunk;
116
+ if (done)
117
+ break;
118
+ buf += decoder.decode(value, { stream: true });
119
+ const lines = buf.split('\n');
120
+ buf = lines.pop() || '';
121
+ for (const line of lines) {
122
+ const t = line.trim();
123
+ if (!t.startsWith('data:'))
124
+ continue;
125
+ const data = t.slice(5).trim();
126
+ if (data === '[DONE]')
127
+ continue;
128
+ let j;
129
+ try {
130
+ j = JSON.parse(data);
131
+ }
132
+ catch {
133
+ continue;
134
+ }
135
+ if (j.usage) {
136
+ usage = {
137
+ input: j.usage.prompt_tokens || 0,
138
+ output: j.usage.completion_tokens || 0,
139
+ cacheRead: j.usage.prompt_tokens_details?.cached_tokens || 0,
140
+ };
141
+ }
142
+ const delta = j.choices?.[0]?.delta;
143
+ if (!delta)
144
+ continue;
145
+ if (delta.content)
146
+ yield { type: 'text', text: delta.content };
147
+ if (delta.tool_calls) {
148
+ for (const tc of delta.tool_calls) {
149
+ const idx = tc.index ?? 0;
150
+ if (!toolAcc[idx])
151
+ toolAcc[idx] = { id: tc.id || `call_${idx}`, name: '', args: '' };
152
+ if (tc.id)
153
+ toolAcc[idx].id = tc.id;
154
+ if (tc.function?.name)
155
+ toolAcc[idx].name += tc.function.name;
156
+ if (tc.function?.arguments)
157
+ toolAcc[idx].args += tc.function.arguments;
158
+ }
159
+ }
160
+ }
161
+ }
162
+ yield { type: 'done', toolCalls: Object.values(toolAcc), usage };
163
+ }
package/dist/auth.js ADDED
@@ -0,0 +1,108 @@
1
+ // CLI kimlik: ~/.wormclaude/config.json okuma/yazma + tarayıcı device-login akışı.
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
4
+ import * as os from 'node:os';
5
+ import { spawn } from 'node:child_process';
6
+ export const CONFIG_DIR = path.join(os.homedir(), '.wormclaude');
7
+ export const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json');
8
+ // Müşteriler bunu değiştirmesin diye varsayılan public API.
9
+ export const DEFAULT_BASE_URL = 'https://api.wormclaude.ai/v1';
10
+ export function loadStored() {
11
+ try {
12
+ return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
13
+ }
14
+ catch {
15
+ return {};
16
+ }
17
+ }
18
+ export function saveStored(cfg) {
19
+ try {
20
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
21
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2), { mode: 0o600 });
22
+ }
23
+ catch (e) {
24
+ process.stderr.write('Yapılandırma kaydedilemedi: ' + (e?.message || e) + '\n');
25
+ }
26
+ }
27
+ export function clearStored() {
28
+ try {
29
+ fs.rmSync(CONFIG_PATH);
30
+ }
31
+ catch { }
32
+ }
33
+ // baseUrl ".../v1" → device endpoint'leri için kök (".../v1" olmadan)
34
+ function apiOrigin(baseUrl) {
35
+ return baseUrl.replace(/\/v1\/?$/, '');
36
+ }
37
+ function openBrowser(url) {
38
+ try {
39
+ let child;
40
+ if (process.platform === 'win32') {
41
+ child = spawn('cmd', ['/c', 'start', '', url], { detached: true, stdio: 'ignore' });
42
+ }
43
+ else if (process.platform === 'darwin') {
44
+ child = spawn('open', [url], { detached: true, stdio: 'ignore' });
45
+ }
46
+ else {
47
+ child = spawn('xdg-open', [url], { detached: true, stdio: 'ignore' });
48
+ }
49
+ // spawn hatası async 'error' event'i olarak gelir; yutmazsak süreç çöker.
50
+ child.on('error', () => { });
51
+ child.unref();
52
+ }
53
+ catch { /* sessiz geç */ }
54
+ }
55
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
56
+ export async function deviceLogin() {
57
+ const base = process.env.WORMCLAUDE_BASE_URL || loadStored().baseUrl || DEFAULT_BASE_URL;
58
+ const origin = apiOrigin(base);
59
+ let start;
60
+ try {
61
+ const r = await fetch(`${origin}/cli/device/start`, { method: 'POST' });
62
+ if (!r.ok)
63
+ throw new Error('HTTP ' + r.status);
64
+ start = await r.json();
65
+ }
66
+ catch (e) {
67
+ process.stderr.write('Giriş başlatılamadı: ' + (e?.message || e) + '\n');
68
+ process.exit(1);
69
+ }
70
+ const url = start.verification_uri_complete || start.verification_uri;
71
+ const userCode = start.user_code;
72
+ const deviceCode = start.device_code;
73
+ process.stdout.write('\n');
74
+ process.stdout.write(' Tarayıcıda açın ve giriş yapın:\n');
75
+ process.stdout.write(' \x1b[36m' + url + '\x1b[0m\n\n');
76
+ process.stdout.write(' Doğrulama kodu: \x1b[1m' + userCode + '\x1b[0m\n\n');
77
+ openBrowser(url);
78
+ const pollMs = Math.max(2, Number(start.interval) || 3) * 1000;
79
+ const deadline = Date.now() + (Number(start.expires_in) || 600) * 1000;
80
+ process.stdout.write(' Onay bekleniyor');
81
+ while (Date.now() < deadline) {
82
+ await sleep(pollMs);
83
+ process.stdout.write('.');
84
+ let d;
85
+ try {
86
+ const r = await fetch(`${origin}/cli/device/poll?device_code=${encodeURIComponent(deviceCode)}`);
87
+ d = await r.json();
88
+ }
89
+ catch {
90
+ continue; // geçici ağ hatası — tekrar dene
91
+ }
92
+ if (d.status === 'approved') {
93
+ saveStored({
94
+ baseUrl: d.base_url || base,
95
+ apiKey: d.api_key,
96
+ model: d.model || 'wormclaude',
97
+ });
98
+ process.stdout.write('\n\n \x1b[32m✓ Giriş başarılı!\x1b[0m Artık `wormclaude` yazarak kullanabilirsiniz.\n\n');
99
+ return;
100
+ }
101
+ if (d.status === 'expired') {
102
+ process.stdout.write('\n\n Kodun süresi doldu. Tekrar deneyin: wormclaude login\n\n');
103
+ process.exit(1);
104
+ }
105
+ }
106
+ process.stdout.write('\n\n Süre doldu. Tekrar deneyin: wormclaude login\n\n');
107
+ process.exit(1);
108
+ }