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 +147 -0
- package/dist/agent.js +61 -0
- package/dist/api.js +163 -0
- package/dist/auth.js +108 -0
- package/dist/cli.js +851 -0
- package/dist/commands.js +540 -0
- package/dist/compact.js +53 -0
- package/dist/i18n.js +177 -0
- package/dist/learn.js +47 -0
- package/dist/links.js +31 -0
- package/dist/mcp.js +104 -0
- package/dist/memory.js +135 -0
- package/dist/skills.js +275 -0
- package/dist/tasks.js +63 -0
- package/dist/theme.js +11 -0
- package/dist/tips.js +60 -0
- package/dist/toolSummary.js +24 -0
- package/dist/tools.js +1136 -0
- package/dist/usage.js +71 -0
- package/package.json +44 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,851 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import React, { useState, useRef, useEffect } from 'react';
|
|
3
|
+
import { render, Box, Text, useApp, useInput } from 'ink';
|
|
4
|
+
import TextInput from 'ink-text-input';
|
|
5
|
+
import { theme, VERSION } from './theme.js';
|
|
6
|
+
import { loadConfig, streamChat } from './api.js';
|
|
7
|
+
import { allToolSchemas, executeToolCalls, executeTool, toolLabel, setToolConfig } from './tools.js';
|
|
8
|
+
import { summarizeTools } from './toolSummary.js';
|
|
9
|
+
import { pickTipId, tipText } from './tips.js';
|
|
10
|
+
import { t, cmdDesc, setLang, saveLang, loadLang, getLang } from './i18n.js';
|
|
11
|
+
import { linkify } from './links.js';
|
|
12
|
+
import { recordLearned } from './learn.js';
|
|
13
|
+
import { loadSkills, getSkills, getSkill, buildSkillPrompt } from './skills.js';
|
|
14
|
+
import { COMMANDS, runSlashCommand } from './commands.js';
|
|
15
|
+
import { tasks } from './tasks.js';
|
|
16
|
+
import { connectMcpServers } from './mcp.js';
|
|
17
|
+
import * as usage from './usage.js';
|
|
18
|
+
import { shouldAutoCompact, runCompact, isContextError } from './compact.js';
|
|
19
|
+
import { shouldExtract, triggerMemory } from './memory.js';
|
|
20
|
+
// --- WORMCLAUDE SUBCOMMANDS (TUI oncesi) ---
|
|
21
|
+
const _arg = (process.argv[2] || '').toLowerCase();
|
|
22
|
+
if (_arg === 'login' || _arg === 'logout' || _arg === 'whoami' || _arg === '--version' || _arg === '-v') {
|
|
23
|
+
const auth = await import('./auth.js');
|
|
24
|
+
if (_arg === 'login')
|
|
25
|
+
await auth.deviceLogin();
|
|
26
|
+
else if (_arg === 'logout') {
|
|
27
|
+
auth.clearStored();
|
|
28
|
+
console.log('Cikis yapildi.');
|
|
29
|
+
}
|
|
30
|
+
else if (_arg === 'whoami') {
|
|
31
|
+
const c = auth.loadStored();
|
|
32
|
+
console.log(c.apiKey ? ('Giris yapildi (' + (c.baseUrl || '') + ')') : 'Giris yapilmadi - wormclaude login');
|
|
33
|
+
}
|
|
34
|
+
else
|
|
35
|
+
console.log('wormclaude ' + VERSION);
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
const config = loadConfig();
|
|
39
|
+
if (!config.apiKey) {
|
|
40
|
+
process.stdout.write('\nWormClaude\'a hos geldiniz!\n\nKullanmadan once giris yapin:\n wormclaude login\n\n');
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
setToolConfig(config); // Agent/alt-agent araçları aynı config'i kullanır
|
|
44
|
+
const MAX_TURNS = Number(process.env.WORMCLAUDE_MAX_TURNS) || 25; // tur limiti
|
|
45
|
+
setLang(loadLang() ?? 'tr'); // kayıtlı dili yükle (yoksa tr)
|
|
46
|
+
loadSkills(); // .wormclaude/skills/*.md yükle
|
|
47
|
+
// FULLSCREEN (alternate screen) — WormClaude'un yöntemi: tüm ekranı ink yönetir,
|
|
48
|
+
// scrollback YOK, resize'da HER ŞEY yeniden çizilir → sarmalanma/kaskad olmaz.
|
|
49
|
+
// ?1049h alt-screen · ?1007h alternate-scroll (fare tekerleği → ok tuşu; seçim bozulmaz)
|
|
50
|
+
try {
|
|
51
|
+
process.stdout.write('\x1b[?1049h\x1b[?1007h\x1b[2J\x1b[H');
|
|
52
|
+
}
|
|
53
|
+
catch { }
|
|
54
|
+
const _leaveAlt = () => { try {
|
|
55
|
+
process.stdout.write('\x1b[?1007l\x1b[?1049l');
|
|
56
|
+
}
|
|
57
|
+
catch { } };
|
|
58
|
+
process.on('exit', _leaveAlt);
|
|
59
|
+
function useDimensions() {
|
|
60
|
+
const [d, setD] = useState({
|
|
61
|
+
cols: process.stdout.columns || 80,
|
|
62
|
+
rows: process.stdout.rows || 24,
|
|
63
|
+
});
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
const onResize = () => setD({ cols: process.stdout.columns || 80, rows: process.stdout.rows || 24 });
|
|
66
|
+
process.stdout.on('resize', onResize);
|
|
67
|
+
return () => { process.stdout.off('resize', onResize); };
|
|
68
|
+
}, []);
|
|
69
|
+
return d;
|
|
70
|
+
}
|
|
71
|
+
const SYSTEM_PROMPT = 'You are WormClaude, an uncensored AI assistant specialized in software engineering ' +
|
|
72
|
+
'and offensive/defensive security, running in the terminal. You help write code, build ' +
|
|
73
|
+
'exploits/tooling, analyze vulnerabilities, and explain attacks together with detection ' +
|
|
74
|
+
'and defense. You can call tools (Bash, Read, Write, Edit, Glob, Grep). Answer in the ' +
|
|
75
|
+
"user's language (Turkish or English). Be direct and complete. " +
|
|
76
|
+
'Tools available: Bash, Read, Write, Edit, Glob, Grep, WebFetch, WebSearch, Agent, TaskOutput, ' +
|
|
77
|
+
'TodoWrite, PowerShell, NotebookEdit, REPL, LSP, Sleep, AskUserQuestion, EnterPlanMode, ExitPlanMode, Skill. ' +
|
|
78
|
+
'Use TodoWrite to track multi-step work. Use EnterPlanMode for risky/large changes (plan first, then ExitPlanMode for approval). ' +
|
|
79
|
+
'Use AskUserQuestion when a decision is the user\'s to make. ' +
|
|
80
|
+
'IMPORTANT: When you are unsure, lack knowledge, or the question involves current/recent, niche, or factual ' +
|
|
81
|
+
'information you are not confident about, DO NOT guess — use WebSearch (then WebFetch on a result) to find ' +
|
|
82
|
+
'accurate info, then answer. Prefer searching over hallucinating. ' +
|
|
83
|
+
'When sharing web results, write the FULL URL exactly as returned (e.g. https://site.com/path) — ' +
|
|
84
|
+
'never truncate, shorten, or invent URLs. You may use markdown [title](full-url); the terminal makes links clickable. ' +
|
|
85
|
+
'IMPORTANT: You must use the Read tool on a file before you Edit or overwrite (Write) it. ' +
|
|
86
|
+
'For large or parallelizable work, act as a coordinator: delegate self-contained subtasks to sub-agents with the Agent tool ' +
|
|
87
|
+
'(use run_in_background to launch several at once, then collect results with TaskOutput). ' +
|
|
88
|
+
'Use Bash run_in_background for long-running commands.';
|
|
89
|
+
const WORM_ROWS = [
|
|
90
|
+
'██╗ ██╗ ██████╗ ██████╗ ███╗ ███╗',
|
|
91
|
+
'██║ ██║██╔═══██╗██╔══██╗████╗ ████║',
|
|
92
|
+
'██║ █╗ ██║██║ ██║██████╔╝██╔████╔██║',
|
|
93
|
+
'██║███╗██║██║ ██║██╔══██╗██║╚██╔╝██║',
|
|
94
|
+
'╚███╔███╔╝╚██████╔╝██║ ██║██║ ╚═╝ ██║',
|
|
95
|
+
' ╚══╝╚══╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝',
|
|
96
|
+
];
|
|
97
|
+
const CLAUDE_ROWS = [
|
|
98
|
+
' ██████╗██╗ █████╗ ██╗ ██╗██████╗ ███████╗',
|
|
99
|
+
'██╔════╝██║ ██╔══██╗██║ ██║██╔══██╗██╔════╝',
|
|
100
|
+
'██║ ██║ ███████║██║ ██║██║ ██║█████╗ ',
|
|
101
|
+
'██║ ██║ ██╔══██║██║ ██║██║ ██║██╔══╝ ',
|
|
102
|
+
'╚██████╗███████╗██║ ██║╚██████╔╝██████╔╝███████╗',
|
|
103
|
+
' ╚═════╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝',
|
|
104
|
+
];
|
|
105
|
+
function wrapText(text, width) {
|
|
106
|
+
const out = [];
|
|
107
|
+
for (const para of String(text).split('\n')) {
|
|
108
|
+
if (!para) {
|
|
109
|
+
out.push('');
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
let line = '';
|
|
113
|
+
for (const word of para.split(' ')) {
|
|
114
|
+
const cand = line ? line + ' ' + word : word;
|
|
115
|
+
if (cand.length <= width) {
|
|
116
|
+
line = cand;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (line)
|
|
120
|
+
out.push(line);
|
|
121
|
+
if (word.length > width) {
|
|
122
|
+
let w = word;
|
|
123
|
+
while (w.length > width) {
|
|
124
|
+
out.push(w.slice(0, width));
|
|
125
|
+
w = w.slice(width);
|
|
126
|
+
}
|
|
127
|
+
line = w;
|
|
128
|
+
}
|
|
129
|
+
else
|
|
130
|
+
line = word;
|
|
131
|
+
}
|
|
132
|
+
out.push(line);
|
|
133
|
+
}
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
// Tüm öğeleri görsel satır dizisine çevirir (kaydırma satır granülerliğinde).
|
|
137
|
+
function buildLines(items, cols) {
|
|
138
|
+
const lines = [];
|
|
139
|
+
const W = Math.max(24, cols - 2);
|
|
140
|
+
const dim = theme.greyDim, red = theme.red, redB = theme.redBright, white = theme.white;
|
|
141
|
+
for (const it of items) {
|
|
142
|
+
lines.push([]); // üstte boşluk
|
|
143
|
+
if (it.kind === 'banner') {
|
|
144
|
+
const rows = cols >= 88 ? WORM_ROWS.map((w, i) => w + CLAUDE_ROWS[i]) : [...WORM_ROWS, ...CLAUDE_ROWS];
|
|
145
|
+
for (const r of rows)
|
|
146
|
+
lines.push([{ text: r, color: red, bold: true }]);
|
|
147
|
+
lines.push([{ text: ' ' + t('banner.subtitle'), dim: true }]);
|
|
148
|
+
}
|
|
149
|
+
else if (it.kind === 'user') {
|
|
150
|
+
// şeffaf kutu
|
|
151
|
+
const inner = W - 4;
|
|
152
|
+
const wrapped = wrapText('› ' + it.text, inner);
|
|
153
|
+
lines.push([{ text: '╭' + '─'.repeat(W - 2) + '╮', dim: true }]);
|
|
154
|
+
for (const ln of wrapped)
|
|
155
|
+
lines.push([{ text: '│ ', dim: true }, { text: ln.padEnd(inner), color: white }, { text: ' │', dim: true }]);
|
|
156
|
+
lines.push([{ text: '╰' + '─'.repeat(W - 2) + '╯', dim: true }]);
|
|
157
|
+
}
|
|
158
|
+
else if (it.kind === 'assistant') {
|
|
159
|
+
wrapText(it.text, W - 2).forEach((ln, i) => lines.push(i === 0 ? [{ text: '⏺ ', color: redB, bold: true }, { text: ln, color: white }] : [{ text: ' ' + ln, color: white }]));
|
|
160
|
+
}
|
|
161
|
+
else if (it.kind === 'tool') {
|
|
162
|
+
const n = it.result.split('\n').length, chars = it.result.length;
|
|
163
|
+
lines.push([{ text: '⏺ ', color: redB, bold: true }, { text: it.label, color: white }]);
|
|
164
|
+
lines.push([{ text: ' ⎿ ', dim: true }, { text: it.ok ? `${n} satır (${chars} karakter)` : '✗ ' + it.result.slice(0, 120), color: it.ok ? theme.grey : theme.errorRed }]);
|
|
165
|
+
}
|
|
166
|
+
else if (it.kind === 'note') {
|
|
167
|
+
for (const ln of wrapText(it.text, W - 2))
|
|
168
|
+
lines.push([{ text: ln, dim: true }]);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return lines;
|
|
172
|
+
}
|
|
173
|
+
function Banner() {
|
|
174
|
+
const { cols } = useDimensions();
|
|
175
|
+
const RED = theme.red; // WORM + CLAUDE tek renk (kan kırmızısı)
|
|
176
|
+
// Çok dar: temiz tek kelime (kırpılır, asla bozulmaz)
|
|
177
|
+
if (cols < 46) {
|
|
178
|
+
return (React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
|
|
179
|
+
React.createElement(Text, { color: RED, bold: true, wrap: "truncate" }, "WORMCLAUDE"),
|
|
180
|
+
React.createElement(Text, { color: theme.greyDim, wrap: "truncate" }, t('banner.subtitle'))));
|
|
181
|
+
}
|
|
182
|
+
// Geniş ekran: tek satır WORMCLAUDE (WORM + CLAUDE yan yana, tek renk → düzgün kırpılır)
|
|
183
|
+
if (cols >= 88) {
|
|
184
|
+
return (React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
|
|
185
|
+
WORM_ROWS.map((w, i) => React.createElement(Text, { key: i, color: RED, bold: true, wrap: "truncate" }, w + CLAUDE_ROWS[i])),
|
|
186
|
+
React.createElement(Text, { color: theme.greyDim, wrap: "truncate" }, ` ${t('banner.subtitle')}`)));
|
|
187
|
+
}
|
|
188
|
+
// Normal: WORM üstte, CLAUDE altta (iki blok, tek renk)
|
|
189
|
+
return (React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
|
|
190
|
+
[...WORM_ROWS, ...CLAUDE_ROWS].map((r, i) => React.createElement(Text, { key: i, color: RED, bold: true, wrap: "truncate" }, r)),
|
|
191
|
+
React.createElement(Text, { color: theme.greyDim, wrap: "truncate" }, ` ${t('banner.subtitle')}`)));
|
|
192
|
+
}
|
|
193
|
+
function RenderItem({ item }) {
|
|
194
|
+
if (item.kind === 'banner')
|
|
195
|
+
return React.createElement(Banner, null);
|
|
196
|
+
if (item.kind === 'user') {
|
|
197
|
+
// Kullanıcı mesajı: şeffaf (içi boş) kenarlıklı kutu — Claude Code tarzı
|
|
198
|
+
return (React.createElement(Box, { marginTop: 1, borderStyle: "round", borderColor: theme.greyDim, paddingX: 1 },
|
|
199
|
+
React.createElement(Text, { color: theme.greyDim }, "\u203A "),
|
|
200
|
+
React.createElement(Text, { color: theme.white }, item.text)));
|
|
201
|
+
}
|
|
202
|
+
if (item.kind === 'assistant') {
|
|
203
|
+
return (React.createElement(Box, { marginTop: 1, flexDirection: "row" },
|
|
204
|
+
React.createElement(Text, { color: theme.redBright, bold: true }, "\u23FA "),
|
|
205
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
206
|
+
React.createElement(Text, { color: theme.white }, linkify(item.text)))));
|
|
207
|
+
}
|
|
208
|
+
if (item.kind === 'tool') {
|
|
209
|
+
const lines = item.result.split('\n').length;
|
|
210
|
+
const chars = item.result.length;
|
|
211
|
+
return (React.createElement(Box, { flexDirection: "column", marginTop: 1 },
|
|
212
|
+
React.createElement(Box, null,
|
|
213
|
+
React.createElement(Text, { color: theme.redBright, bold: true }, "\u23FA "),
|
|
214
|
+
React.createElement(Text, { color: theme.white }, item.label)),
|
|
215
|
+
React.createElement(Box, null,
|
|
216
|
+
React.createElement(Text, { color: theme.greyDim }, " \u23BF "),
|
|
217
|
+
React.createElement(Text, { color: item.ok ? theme.grey : theme.errorRed }, item.ok ? `${lines} satır (${chars} karakter)` : `✗ ${item.result.slice(0, 160)}`))));
|
|
218
|
+
}
|
|
219
|
+
if (item.kind === 'note')
|
|
220
|
+
return React.createElement(Box, { marginTop: 1 },
|
|
221
|
+
React.createElement(Text, { color: theme.greyDim }, linkify(item.text)));
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
const SPINNER_FRAMES = ['·', '✢', '✳', '✶', '✻', '✽', '✶', '✳', '✢'];
|
|
225
|
+
const VERBS = [
|
|
226
|
+
'Worming', 'Forging', 'Exploiting', 'Crafting', 'Computing', 'Crunching',
|
|
227
|
+
'Hacking', 'Compiling', 'Scanning', 'Cooking', 'Brewing', 'Conjuring',
|
|
228
|
+
'Decrypting', 'Injecting', 'Probing', 'Assembling', 'Architecting',
|
|
229
|
+
'Generating', 'Tinkering', 'Sharpening', 'Churning', 'Pwning',
|
|
230
|
+
];
|
|
231
|
+
function WormSpinner({ label, tokens }) {
|
|
232
|
+
const [f, setF] = useState(0);
|
|
233
|
+
useEffect(() => {
|
|
234
|
+
const t = setInterval(() => setF((x) => x + 1), 120);
|
|
235
|
+
return () => clearInterval(t);
|
|
236
|
+
}, []);
|
|
237
|
+
return (React.createElement(Box, { marginTop: 1 },
|
|
238
|
+
React.createElement(Text, { color: theme.red },
|
|
239
|
+
SPINNER_FRAMES[f % SPINNER_FRAMES.length],
|
|
240
|
+
" "),
|
|
241
|
+
React.createElement(Text, { color: theme.grey },
|
|
242
|
+
label,
|
|
243
|
+
"\u2026"),
|
|
244
|
+
tokens ? React.createElement(Text, { color: theme.greyDim },
|
|
245
|
+
" \u00B7 ",
|
|
246
|
+
tokens,
|
|
247
|
+
" tokens") : null));
|
|
248
|
+
}
|
|
249
|
+
function StatusLine({ model, ctxTokens }) {
|
|
250
|
+
const pct = Math.min(100, Math.round((ctxTokens / 8192) * 100));
|
|
251
|
+
return (React.createElement(Box, null,
|
|
252
|
+
React.createElement(Text, { color: theme.greyDim },
|
|
253
|
+
' ',
|
|
254
|
+
model,
|
|
255
|
+
" \u00B7 ~",
|
|
256
|
+
ctxTokens.toLocaleString(),
|
|
257
|
+
" tok \u00B7 ",
|
|
258
|
+
pct,
|
|
259
|
+
"% ba\u011Flam")));
|
|
260
|
+
}
|
|
261
|
+
function App() {
|
|
262
|
+
const { exit } = useApp();
|
|
263
|
+
const [items, setItems] = useState([{ kind: 'banner' }]);
|
|
264
|
+
const [input, setInput] = useState('');
|
|
265
|
+
const [busy, setBusy] = useState(false);
|
|
266
|
+
const [streaming, setStreaming] = useState('');
|
|
267
|
+
const [phase, setPhase] = useState('Düşünüyor');
|
|
268
|
+
const [verb, setVerb] = useState('Worming');
|
|
269
|
+
const [tokens, setTokens] = useState(0);
|
|
270
|
+
const [thinking, setThinking] = useState(true);
|
|
271
|
+
const [ctxTokens, setCtxTokens] = useState(0);
|
|
272
|
+
const [started, setStarted] = useState(false);
|
|
273
|
+
const [trustSel, setTrustSel] = useState(0); // 0=Evet, 1=Hayir
|
|
274
|
+
const [lang, setLangState] = useState(() => loadLang()); // null → dil sor
|
|
275
|
+
const [langSel, setLangSel] = useState(0); // 0=tr 1=en
|
|
276
|
+
const chooseLang = (l) => { setLang(l); saveLang(l); setLangState(l); };
|
|
277
|
+
const [taskPill, setTaskPill] = useState(''); // arka plan görev göstergesi
|
|
278
|
+
const [tipId] = useState(() => pickTipId()); // oturum ipucu (dile göre çözülür)
|
|
279
|
+
const [cmdSel, setCmdSel] = useState(0); // slash menüsü seçili satır
|
|
280
|
+
const [scroll, setScroll] = useState(0); // alttan kaç öğe yukarı kaydırıldı (0 = en alt)
|
|
281
|
+
const [perm, setPerm] = useState(null);
|
|
282
|
+
const [permSel, setPermSel] = useState(0); // 0=Evet 1=Evet(hep) 2=Hayır+yönlendir
|
|
283
|
+
const [permMode, setPermMode] = useState('select');
|
|
284
|
+
const [permFeedback, setPermFeedback] = useState('');
|
|
285
|
+
const abortRef = useRef(null);
|
|
286
|
+
const allowedToolsRef = useRef(new Set()); // oturum boyu izinli araçlar
|
|
287
|
+
const [ask, setAsk] = useState(null);
|
|
288
|
+
const [askSel, setAskSel] = useState(0); // AskUserQuestion seçimi
|
|
289
|
+
const historyRef = useRef([{ role: 'system', content: SYSTEM_PROMPT }]);
|
|
290
|
+
// Acilis: klasore guven sorusu (WormClaude tarzi) — yon tuslari/1-2 sec, Enter onay
|
|
291
|
+
// Dil seçimi (ilk açılış)
|
|
292
|
+
useInput((inp, key) => {
|
|
293
|
+
if (lang !== null)
|
|
294
|
+
return;
|
|
295
|
+
if (key.upArrow || inp === '1')
|
|
296
|
+
setLangSel(0);
|
|
297
|
+
else if (key.downArrow || inp === '2')
|
|
298
|
+
setLangSel(1);
|
|
299
|
+
else if (key.return)
|
|
300
|
+
chooseLang(langSel === 0 ? 'tr' : 'en');
|
|
301
|
+
else if (key.escape)
|
|
302
|
+
exit();
|
|
303
|
+
});
|
|
304
|
+
// Klasör güven sorusu
|
|
305
|
+
useInput((inp, key) => {
|
|
306
|
+
if (lang === null || started)
|
|
307
|
+
return;
|
|
308
|
+
if (key.upArrow || inp === '1')
|
|
309
|
+
setTrustSel(0);
|
|
310
|
+
else if (key.downArrow || inp === '2')
|
|
311
|
+
setTrustSel(1);
|
|
312
|
+
else if (key.return) {
|
|
313
|
+
if (trustSel === 0)
|
|
314
|
+
setStarted(true);
|
|
315
|
+
else
|
|
316
|
+
exit();
|
|
317
|
+
}
|
|
318
|
+
else if (key.escape)
|
|
319
|
+
exit();
|
|
320
|
+
});
|
|
321
|
+
// Soru dialogu (AskUserQuestion / plan onayı)
|
|
322
|
+
useInput((inp, key) => {
|
|
323
|
+
if (!ask)
|
|
324
|
+
return;
|
|
325
|
+
const n = ask.options.length;
|
|
326
|
+
if (key.upArrow)
|
|
327
|
+
setAskSel((s) => (s - 1 + n) % n);
|
|
328
|
+
else if (key.downArrow)
|
|
329
|
+
setAskSel((s) => (s + 1) % n);
|
|
330
|
+
else if (/^[1-9]$/.test(inp) && +inp <= n)
|
|
331
|
+
setAskSel(+inp - 1);
|
|
332
|
+
else if (key.return) {
|
|
333
|
+
ask.resolve(ask.options[askSel]?.label || '');
|
|
334
|
+
setAsk(null);
|
|
335
|
+
}
|
|
336
|
+
else if (key.escape) {
|
|
337
|
+
ask.resolve(ask.options[0]?.label || '');
|
|
338
|
+
setAsk(null);
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
// İzin dialogu + çalışırken Esc ile kesme
|
|
342
|
+
useInput((inp, key) => {
|
|
343
|
+
if (ask)
|
|
344
|
+
return;
|
|
345
|
+
if (perm) {
|
|
346
|
+
if (permMode === 'feedback') {
|
|
347
|
+
// feedback metni TextInput ile giriliyor; sadece Esc'i burada yakala
|
|
348
|
+
if (key.escape) {
|
|
349
|
+
perm.resolve({ deny: '' });
|
|
350
|
+
setPerm(null);
|
|
351
|
+
setPermMode('select');
|
|
352
|
+
}
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
if (key.upArrow || inp === '1')
|
|
356
|
+
setPermSel(0);
|
|
357
|
+
else if (inp === '2')
|
|
358
|
+
setPermSel(1);
|
|
359
|
+
else if (key.downArrow || inp === '3')
|
|
360
|
+
setPermSel(2);
|
|
361
|
+
else if (key.return) {
|
|
362
|
+
const sel = permSel;
|
|
363
|
+
if (sel === 2) {
|
|
364
|
+
setPermMode('feedback');
|
|
365
|
+
setPermFeedback('');
|
|
366
|
+
return;
|
|
367
|
+
} // yönlendirme yaz
|
|
368
|
+
if (sel === 1)
|
|
369
|
+
allowedToolsRef.current.add(perm.name);
|
|
370
|
+
perm.resolve('allow');
|
|
371
|
+
setPerm(null);
|
|
372
|
+
}
|
|
373
|
+
else if (key.escape) {
|
|
374
|
+
perm.resolve({ deny: '' });
|
|
375
|
+
setPerm(null);
|
|
376
|
+
}
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
if (busy && key.escape) {
|
|
380
|
+
abortRef.current?.abort();
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
// Slash menüsü filtresi (yerleşik komutlar + skill'ler)
|
|
384
|
+
const allCommands = () => [
|
|
385
|
+
...COMMANDS,
|
|
386
|
+
...getSkills().map((s) => ({ name: '/' + s.name, desc: s.description })),
|
|
387
|
+
];
|
|
388
|
+
const cmdFilter = (inp) => {
|
|
389
|
+
const tok = inp.split(' ')[0];
|
|
390
|
+
return allCommands().filter((c) => c.name.startsWith(tok));
|
|
391
|
+
};
|
|
392
|
+
// Slash menüsünde ok tuşlarıyla gezinme (yukarı/aşağı)
|
|
393
|
+
useInput((inp, key) => {
|
|
394
|
+
if (!started || busy || perm)
|
|
395
|
+
return;
|
|
396
|
+
if (!input.startsWith('/'))
|
|
397
|
+
return;
|
|
398
|
+
const n = cmdFilter(input).length;
|
|
399
|
+
if (n === 0)
|
|
400
|
+
return;
|
|
401
|
+
if (key.upArrow)
|
|
402
|
+
setCmdSel((s) => (s - 1 + n) % n);
|
|
403
|
+
else if (key.downArrow)
|
|
404
|
+
setCmdSel((s) => (s + 1) % n);
|
|
405
|
+
});
|
|
406
|
+
// Geçmişte kaydırma: PageUp/PageDown her zaman; boş input'ta ok tuşları (fare
|
|
407
|
+
// tekerleği alternate-scroll ile ok tuşu yollar → tekerlekle kaydırma).
|
|
408
|
+
useInput((_inp, key) => {
|
|
409
|
+
if (!started || perm || ask || lang === null)
|
|
410
|
+
return;
|
|
411
|
+
// satır-temelli; üst sınır render'da off=min(scroll,maxScroll) ile kırpılır
|
|
412
|
+
if (key.pageUp)
|
|
413
|
+
setScroll((s) => s + 10);
|
|
414
|
+
else if (key.pageDown)
|
|
415
|
+
setScroll((s) => Math.max(0, s - 10));
|
|
416
|
+
else if (input === '' && !busy) {
|
|
417
|
+
if (key.upArrow)
|
|
418
|
+
setScroll((s) => s + 3);
|
|
419
|
+
else if (key.downArrow)
|
|
420
|
+
setScroll((s) => Math.max(0, s - 3));
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
// Yeni mesaj gelince en alta dön
|
|
424
|
+
useEffect(() => { setScroll(0); }, [items.length]);
|
|
425
|
+
const push = (it) => setItems((prev) => [...prev, it]);
|
|
426
|
+
// Arka plan görevlerini izle → footer pill
|
|
427
|
+
useEffect(() => {
|
|
428
|
+
const update = () => {
|
|
429
|
+
const run = tasks.running();
|
|
430
|
+
const all = tasks.list();
|
|
431
|
+
if (!all.length) {
|
|
432
|
+
setTaskPill('');
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
const byKind = {};
|
|
436
|
+
for (const t of run)
|
|
437
|
+
byKind[t.kind] = (byKind[t.kind] || 0) + 1;
|
|
438
|
+
const parts = Object.entries(byKind).map(([k, n]) => `${n} ${k}`);
|
|
439
|
+
setTaskPill(run.length ? t('pill.running', parts.join(', ')) : t('pill.done', all.length));
|
|
440
|
+
};
|
|
441
|
+
tasks.on('change', update);
|
|
442
|
+
update();
|
|
443
|
+
return () => { tasks.off('change', update); };
|
|
444
|
+
}, []);
|
|
445
|
+
// Başlangıçta MCP sunucularına bağlan (.wormclaude/mcp.json)
|
|
446
|
+
useEffect(() => {
|
|
447
|
+
if (!started)
|
|
448
|
+
return;
|
|
449
|
+
let cancelled = false;
|
|
450
|
+
(async () => {
|
|
451
|
+
const servers = await connectMcpServers();
|
|
452
|
+
if (cancelled || !servers.length)
|
|
453
|
+
return;
|
|
454
|
+
const ok = servers.filter((s) => s.status === 'connected');
|
|
455
|
+
const bad = servers.filter((s) => s.status === 'error');
|
|
456
|
+
const toolCount = ok.reduce((n, s) => n + s.toolNames.length, 0);
|
|
457
|
+
let msg = t('mcp.connected', ok.length, toolCount);
|
|
458
|
+
if (bad.length)
|
|
459
|
+
msg += t('mcp.errors', bad.length, bad.map((b) => b.name).join(', '));
|
|
460
|
+
push({ kind: 'note', text: msg });
|
|
461
|
+
})();
|
|
462
|
+
return () => { cancelled = true; };
|
|
463
|
+
}, [started]);
|
|
464
|
+
async function runAgent(userText, displayText) {
|
|
465
|
+
const ac = new AbortController();
|
|
466
|
+
abortRef.current = ac;
|
|
467
|
+
setBusy(true);
|
|
468
|
+
push({ kind: 'user', text: displayText ?? userText });
|
|
469
|
+
historyRef.current = [...historyRef.current, { role: 'user', content: userText }];
|
|
470
|
+
let iter = 0;
|
|
471
|
+
let reactiveRetried = false;
|
|
472
|
+
let done = false;
|
|
473
|
+
let usedWeb = false; // bu turda web aracı kullanıldı mı (öğrenme için)
|
|
474
|
+
let lastAnswer = ''; // modelin son (sentez) cevabı
|
|
475
|
+
while (iter < MAX_TURNS) {
|
|
476
|
+
if (ac.signal.aborted) {
|
|
477
|
+
push({ kind: 'note', text: t('note.interrupted') });
|
|
478
|
+
done = true;
|
|
479
|
+
break;
|
|
480
|
+
}
|
|
481
|
+
// Oto-compact: bağlam eşiği aşılınca otomatik özetle
|
|
482
|
+
if (shouldAutoCompact(historyRef.current)) {
|
|
483
|
+
setThinking(false);
|
|
484
|
+
setPhase(t('phase.autoCompact'));
|
|
485
|
+
try {
|
|
486
|
+
const { history: nh } = await runCompact(historyRef.current, config);
|
|
487
|
+
historyRef.current = nh;
|
|
488
|
+
push({ kind: 'note', text: t('note.autoCompacted') });
|
|
489
|
+
}
|
|
490
|
+
catch { }
|
|
491
|
+
}
|
|
492
|
+
let assistantText = '';
|
|
493
|
+
let gotCtxError = false;
|
|
494
|
+
setStreaming('');
|
|
495
|
+
setThinking(true);
|
|
496
|
+
setTokens(0);
|
|
497
|
+
setVerb(VERBS[Math.floor(Math.random() * VERBS.length)]);
|
|
498
|
+
let toolCalls = [];
|
|
499
|
+
for await (const ev of streamChat(historyRef.current, allToolSchemas(), config, ac.signal)) {
|
|
500
|
+
if (ev.type === 'text') {
|
|
501
|
+
assistantText += ev.text;
|
|
502
|
+
setStreaming(assistantText);
|
|
503
|
+
setTokens(Math.round(assistantText.length / 4));
|
|
504
|
+
}
|
|
505
|
+
else if (ev.type === 'error') {
|
|
506
|
+
if (isContextError(ev.error))
|
|
507
|
+
gotCtxError = true;
|
|
508
|
+
assistantText += `\n[hata: ${ev.error}]`;
|
|
509
|
+
setStreaming(assistantText);
|
|
510
|
+
}
|
|
511
|
+
else if (ev.type === 'done') {
|
|
512
|
+
toolCalls = ev.toolCalls;
|
|
513
|
+
usage.record(config.model, ev.usage); // billing: token/maliyet kaydı
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
setStreaming('');
|
|
517
|
+
// Reactive compact: bağlam taştıysa bir kez özetle ve turu tekrar dene
|
|
518
|
+
if (gotCtxError && !reactiveRetried) {
|
|
519
|
+
reactiveRetried = true;
|
|
520
|
+
push({ kind: 'note', text: t('note.reactive') });
|
|
521
|
+
try {
|
|
522
|
+
const { history: nh } = await runCompact(historyRef.current, config);
|
|
523
|
+
historyRef.current = nh;
|
|
524
|
+
}
|
|
525
|
+
catch { }
|
|
526
|
+
continue; // tur sayılmaz
|
|
527
|
+
}
|
|
528
|
+
if (ac.signal.aborted) {
|
|
529
|
+
if (assistantText.trim()) {
|
|
530
|
+
historyRef.current = [...historyRef.current, { role: 'assistant', content: assistantText.trim() }];
|
|
531
|
+
push({ kind: 'assistant', text: assistantText.trim() });
|
|
532
|
+
}
|
|
533
|
+
push({ kind: 'note', text: t('note.interrupted') });
|
|
534
|
+
done = true;
|
|
535
|
+
break;
|
|
536
|
+
}
|
|
537
|
+
iter++; // gerçek tur
|
|
538
|
+
const assistantMsg = { role: 'assistant', content: assistantText || '' };
|
|
539
|
+
if (toolCalls.length) {
|
|
540
|
+
assistantMsg.tool_calls = toolCalls.map((t) => ({
|
|
541
|
+
id: t.id, type: 'function', function: { name: t.name, arguments: t.args || '{}' },
|
|
542
|
+
}));
|
|
543
|
+
}
|
|
544
|
+
historyRef.current = [...historyRef.current, assistantMsg];
|
|
545
|
+
if (assistantText.trim()) {
|
|
546
|
+
lastAnswer = assistantText.trim();
|
|
547
|
+
push({ kind: 'assistant', text: lastAnswer });
|
|
548
|
+
}
|
|
549
|
+
// Bütçe limiti kontrolü
|
|
550
|
+
if (usage.isOverBudget()) {
|
|
551
|
+
push({ kind: 'note', text: t('note.budget', usage.getBudget()) });
|
|
552
|
+
done = true;
|
|
553
|
+
break;
|
|
554
|
+
}
|
|
555
|
+
if (!toolCalls.length) {
|
|
556
|
+
done = true;
|
|
557
|
+
break;
|
|
558
|
+
}
|
|
559
|
+
// Paralel araç çalıştırma + izin onayı (yazanlar sıralı)
|
|
560
|
+
setThinking(false);
|
|
561
|
+
setPhase(toolCalls.length > 1 ? t('phase.toolsMulti', toolCalls.length) : t('phase.toolRun', toolCalls[0].name));
|
|
562
|
+
const results = await executeToolCalls(toolCalls, {
|
|
563
|
+
onResult: (c, _i, res) => {
|
|
564
|
+
if ((c.name === 'WebSearch' || c.name === 'WebFetch') && res.ok)
|
|
565
|
+
usedWeb = true;
|
|
566
|
+
push({ kind: 'tool', label: toolLabel(c.name, res.args), result: res.output, ok: res.ok });
|
|
567
|
+
},
|
|
568
|
+
confirm: (c, args) => new Promise((resolve) => {
|
|
569
|
+
if (allowedToolsRef.current.has(c.name))
|
|
570
|
+
return resolve('allow');
|
|
571
|
+
setPermSel(0);
|
|
572
|
+
setPerm({ label: toolLabel(c.name, args), name: c.name, resolve });
|
|
573
|
+
}),
|
|
574
|
+
ask: (q) => new Promise((resolve) => {
|
|
575
|
+
setAskSel(0);
|
|
576
|
+
setAsk({ question: q.question, options: q.options.length ? q.options : [{ label: 'OK' }], resolve });
|
|
577
|
+
}),
|
|
578
|
+
});
|
|
579
|
+
// Sonuçları orijinal sırada geçmişe ekle (OpenAI tool sırası şart)
|
|
580
|
+
for (let i = 0; i < toolCalls.length; i++) {
|
|
581
|
+
historyRef.current = [...historyRef.current, {
|
|
582
|
+
role: 'tool', tool_call_id: toolCalls[i].id, content: results[i].output.slice(0, 8000),
|
|
583
|
+
}];
|
|
584
|
+
}
|
|
585
|
+
// Araç-grubu özeti (2+ araçta tek satır etiket)
|
|
586
|
+
if (toolCalls.length >= 2) {
|
|
587
|
+
const sum = await summarizeTools(toolCalls.map((t, i) => ({ name: t.name, args: results[i].args })), config);
|
|
588
|
+
if (sum)
|
|
589
|
+
push({ kind: 'note', text: `⎿ ${sum}` });
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
if (!done)
|
|
593
|
+
push({ kind: 'note', text: t('note.maxTurns', MAX_TURNS) });
|
|
594
|
+
abortRef.current = null;
|
|
595
|
+
setCtxTokens(Math.round(JSON.stringify(historyRef.current).length / 4));
|
|
596
|
+
setBusy(false);
|
|
597
|
+
// Öğrenme: web'de arayıp cevap ürettiyse {soru→cevap}'ı eğitim datasına ekle
|
|
598
|
+
if (usedWeb && lastAnswer) {
|
|
599
|
+
const sources = (lastAnswer.match(/https?:\/\/[^\s<>"')\]]+/g) || []).slice(0, 8);
|
|
600
|
+
if (recordLearned(userText, lastAnswer, sources, config)) {
|
|
601
|
+
push({ kind: 'note', text: getLang() === 'en' ? '✎ saved to training data (local + server)' : '✎ eğitim datasına eklendi (yerel + sunucu)' });
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
// Oto-hafıza: eşik geçildiyse arka planda hafızayı güncelle
|
|
605
|
+
if (shouldExtract(historyRef.current))
|
|
606
|
+
triggerMemory(historyRef.current, config);
|
|
607
|
+
}
|
|
608
|
+
async function onSubmit(value) {
|
|
609
|
+
let v = value.trim();
|
|
610
|
+
setInput('');
|
|
611
|
+
setCmdSel(0);
|
|
612
|
+
if (!v)
|
|
613
|
+
return;
|
|
614
|
+
if (v.startsWith('/')) {
|
|
615
|
+
// Seçili menü öğesini çalıştır (tam eşleşme yoksa ve argüman yoksa)
|
|
616
|
+
const firstTok = v.split(' ')[0];
|
|
617
|
+
const isExact = COMMANDS.some((c) => c.name === firstTok);
|
|
618
|
+
if (!isExact && !v.includes(' ')) {
|
|
619
|
+
const filtered = cmdFilter(v);
|
|
620
|
+
if (filtered.length)
|
|
621
|
+
v = filtered[Math.min(cmdSel, filtered.length - 1)].name;
|
|
622
|
+
}
|
|
623
|
+
const tok = v.split(' ')[0];
|
|
624
|
+
// /agent ve /multi-agent — tek / çoklu alt-agent
|
|
625
|
+
if (tok === '/agent' || tok === '/multi-agent') {
|
|
626
|
+
const task = v.slice(tok.length).trim();
|
|
627
|
+
const en = getLang() === 'en';
|
|
628
|
+
if (!task) {
|
|
629
|
+
push({ kind: 'note', text: en ? `Usage: ${tok} <task>` : `Kullanım: ${tok} <görev>` });
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
if (tok === '/agent') {
|
|
633
|
+
setBusy(true);
|
|
634
|
+
setThinking(false);
|
|
635
|
+
setPhase('Agent');
|
|
636
|
+
push({ kind: 'note', text: (en ? 'Sub-agent: ' : 'Alt-agent: ') + task.slice(0, 50) });
|
|
637
|
+
try {
|
|
638
|
+
const res = await executeTool('Agent', { description: 'agent', prompt: task });
|
|
639
|
+
push({ kind: 'assistant', text: res.output });
|
|
640
|
+
}
|
|
641
|
+
catch (e) {
|
|
642
|
+
push({ kind: 'note', text: t('note.cmdErr', e?.message || e) });
|
|
643
|
+
}
|
|
644
|
+
setBusy(false);
|
|
645
|
+
}
|
|
646
|
+
else {
|
|
647
|
+
const coord = (en
|
|
648
|
+
? 'Act as a COORDINATOR. Break this task into independent subtasks and launch them as PARALLEL background sub-agents via the Agent tool with run_in_background=true. Then collect each result with TaskOutput and synthesize a final answer.\n\nTask: '
|
|
649
|
+
: 'KOORDİNATÖR ol. Bu görevi bağımsız alt-görevlere böl ve Agent aracını run_in_background=true ile PARALEL arka plan alt-agent\'ları olarak başlat. Sonra her sonucu TaskOutput ile topla ve nihai cevabı birleştir.\n\nGörev: ') + task;
|
|
650
|
+
runAgent(coord, `${tok} ${task}`);
|
|
651
|
+
}
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
// Skill mi? (yerleşik komut değilse ve .wormclaude/skills'te varsa)
|
|
655
|
+
const builtin = COMMANDS.some((c) => c.name === tok);
|
|
656
|
+
const skill = !builtin ? getSkill(tok.slice(1)) : undefined;
|
|
657
|
+
if (skill) {
|
|
658
|
+
const skillArgs = v.slice(tok.length).trim();
|
|
659
|
+
const prompt = buildSkillPrompt(skill, skillArgs);
|
|
660
|
+
const disp = `/${skill.name}${skillArgs ? ' ' + skillArgs : ''}`;
|
|
661
|
+
if (skill.context === 'fork') {
|
|
662
|
+
setBusy(true);
|
|
663
|
+
setThinking(false);
|
|
664
|
+
setPhase(`Skill: ${skill.name}`);
|
|
665
|
+
push({ kind: 'note', text: `${getLang() === 'en' ? 'Skill (sub-agent): ' : 'Skill (alt-agent): '}${skill.name}` });
|
|
666
|
+
try {
|
|
667
|
+
const res = await executeTool('Agent', { description: skill.name, prompt });
|
|
668
|
+
push({ kind: 'assistant', text: res.output });
|
|
669
|
+
}
|
|
670
|
+
catch (e) {
|
|
671
|
+
push({ kind: 'note', text: t('note.cmdErr', e?.message || e) });
|
|
672
|
+
}
|
|
673
|
+
setBusy(false);
|
|
674
|
+
}
|
|
675
|
+
else {
|
|
676
|
+
runAgent(prompt, disp);
|
|
677
|
+
}
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
const ctx = {
|
|
681
|
+
config,
|
|
682
|
+
getHistory: () => historyRef.current,
|
|
683
|
+
setHistory: (h) => { historyRef.current = h; setCtxTokens(Math.round(JSON.stringify(h).length / 4)); },
|
|
684
|
+
note: (text) => push({ kind: 'note', text }),
|
|
685
|
+
assistant: (text) => push({ kind: 'assistant', text }),
|
|
686
|
+
clearConv: () => { setItems([{ kind: 'banner' }]); historyRef.current = [{ role: 'system', content: SYSTEM_PROMPT }]; },
|
|
687
|
+
exit,
|
|
688
|
+
};
|
|
689
|
+
setBusy(true);
|
|
690
|
+
setThinking(false);
|
|
691
|
+
setPhase(t('phase.cmd', v.split(/\s+/)[0]));
|
|
692
|
+
try {
|
|
693
|
+
await runSlashCommand(v, ctx);
|
|
694
|
+
}
|
|
695
|
+
catch (e) {
|
|
696
|
+
push({ kind: 'note', text: t('note.cmdErr', e?.message || e) });
|
|
697
|
+
}
|
|
698
|
+
setBusy(false);
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
runAgent(v);
|
|
702
|
+
}
|
|
703
|
+
const { cols, rows } = useDimensions();
|
|
704
|
+
// İlk açılış: dil seçimi
|
|
705
|
+
if (lang === null) {
|
|
706
|
+
return (React.createElement(Box, { flexDirection: "column", height: rows, width: cols },
|
|
707
|
+
React.createElement(Banner, null),
|
|
708
|
+
React.createElement(Box, { flexDirection: "column", marginTop: 1, paddingX: 1 },
|
|
709
|
+
React.createElement(Text, { color: theme.redBright, bold: true }, t('lang.title')),
|
|
710
|
+
React.createElement(Box, { flexDirection: "column", marginTop: 1 },
|
|
711
|
+
React.createElement(Text, { color: langSel === 0 ? theme.redBright : theme.grey, bold: langSel === 0 },
|
|
712
|
+
langSel === 0 ? '❯ ' : ' ',
|
|
713
|
+
t('lang.tr')),
|
|
714
|
+
React.createElement(Text, { color: langSel === 1 ? theme.redBright : theme.grey, bold: langSel === 1 },
|
|
715
|
+
langSel === 1 ? '❯ ' : ' ',
|
|
716
|
+
t('lang.en'))),
|
|
717
|
+
React.createElement(Text, { color: theme.greyDim },
|
|
718
|
+
" ",
|
|
719
|
+
t('lang.hint'))),
|
|
720
|
+
React.createElement(Box, { flexGrow: 1 }),
|
|
721
|
+
React.createElement(StatusLine, { model: config.model, ctxTokens: 0 })));
|
|
722
|
+
}
|
|
723
|
+
// Açılış: klasöre güven sorusu (WormClaude tarzı)
|
|
724
|
+
if (!started) {
|
|
725
|
+
return (React.createElement(Box, { flexDirection: "column", height: rows, width: cols },
|
|
726
|
+
React.createElement(Banner, null),
|
|
727
|
+
React.createElement(Box, { flexDirection: "column", marginTop: 1, paddingX: 1 },
|
|
728
|
+
React.createElement(Text, { color: theme.redBright, bold: true }, t('trust.accessing')),
|
|
729
|
+
React.createElement(Text, null, " "),
|
|
730
|
+
React.createElement(Text, { color: theme.white }, process.cwd()),
|
|
731
|
+
React.createElement(Text, null, " "),
|
|
732
|
+
t('trust.check').split('\n').map((line, i) => React.createElement(Text, { key: i, color: theme.grey }, line)),
|
|
733
|
+
React.createElement(Text, null, " "),
|
|
734
|
+
React.createElement(Text, { color: theme.grey }, t('trust.canDo')),
|
|
735
|
+
React.createElement(Box, { flexDirection: "column", marginTop: 1 },
|
|
736
|
+
React.createElement(Text, { color: trustSel === 0 ? theme.redBright : theme.grey, bold: trustSel === 0 },
|
|
737
|
+
trustSel === 0 ? '❯ ' : ' ',
|
|
738
|
+
t('trust.yes')),
|
|
739
|
+
React.createElement(Text, { color: trustSel === 1 ? theme.redBright : theme.grey, bold: trustSel === 1 },
|
|
740
|
+
trustSel === 1 ? '❯ ' : ' ',
|
|
741
|
+
t('trust.no'))),
|
|
742
|
+
React.createElement(Text, { color: theme.greyDim },
|
|
743
|
+
" ",
|
|
744
|
+
t('trust.hint'))),
|
|
745
|
+
React.createElement(Box, { flexGrow: 1 }),
|
|
746
|
+
React.createElement(StatusLine, { model: config.model, ctxTokens: 0 })));
|
|
747
|
+
}
|
|
748
|
+
return (React.createElement(Box, { flexDirection: "column", height: rows, width: cols },
|
|
749
|
+
React.createElement(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden" }, (() => {
|
|
750
|
+
// Satır-temelli kaydırma (Claude tarzı): tüm öğeleri satıra çevir, pencere göster
|
|
751
|
+
const all = buildLines(items, cols);
|
|
752
|
+
const reserve = busy ? 4 : 6;
|
|
753
|
+
const avail = Math.max(4, rows - reserve - (streaming ? 3 : 0) - 2); // -2: göstergeler
|
|
754
|
+
const maxScroll = Math.max(0, all.length - avail);
|
|
755
|
+
const off = Math.min(scroll, maxScroll); // alttan kaç satır yukarı
|
|
756
|
+
const startLine = Math.max(0, all.length - avail - off);
|
|
757
|
+
const view = all.slice(startLine, startLine + avail);
|
|
758
|
+
return (React.createElement(React.Fragment, null,
|
|
759
|
+
startLine > 0 ? React.createElement(Text, { color: theme.greyDim },
|
|
760
|
+
" \u2191 ",
|
|
761
|
+
startLine,
|
|
762
|
+
" sat\u0131r \u00B7 PageUp") : null,
|
|
763
|
+
view.map((segs, i) => (React.createElement(Text, { key: i }, segs.length === 0
|
|
764
|
+
? ' '
|
|
765
|
+
: segs.map((s, j) => (React.createElement(Text, { key: j, color: s.dim ? theme.greyDim : s.color, bold: s.bold }, linkify(s.text))))))),
|
|
766
|
+
off > 0 ? React.createElement(Text, { color: theme.greyDim },
|
|
767
|
+
" \u2193 ",
|
|
768
|
+
off,
|
|
769
|
+
" sat\u0131r \u00B7 PageDown / Son") : null,
|
|
770
|
+
streaming && off === 0 ? (React.createElement(Box, { flexDirection: "row" },
|
|
771
|
+
React.createElement(Text, { color: theme.redBright, bold: true }, "\u23FA "),
|
|
772
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
773
|
+
React.createElement(Text, { color: theme.white }, streaming)))) : null));
|
|
774
|
+
})()),
|
|
775
|
+
perm ? (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.red, paddingX: 1, marginTop: 1 },
|
|
776
|
+
React.createElement(Text, { color: theme.redBright, bold: true }, t('perm.title', perm.label)),
|
|
777
|
+
permMode === 'feedback' ? (React.createElement(React.Fragment, null,
|
|
778
|
+
React.createElement(Text, { color: theme.grey }, t('perm.feedbackPrompt')),
|
|
779
|
+
React.createElement(Box, null,
|
|
780
|
+
React.createElement(Text, { color: theme.redBright, bold: true }, "\u276F "),
|
|
781
|
+
React.createElement(TextInput, { value: permFeedback, onChange: setPermFeedback, onSubmit: (val) => { perm.resolve({ deny: val.trim() }); setPerm(null); setPermMode('select'); }, placeholder: t('perm.feedbackPlaceholder') })),
|
|
782
|
+
React.createElement(Text, { color: theme.greyDim }, " Enter g\u00F6nder \u00B7 Esc iptal"))) : (React.createElement(React.Fragment, null,
|
|
783
|
+
React.createElement(Text, { color: permSel === 0 ? theme.redBright : theme.grey, bold: permSel === 0 },
|
|
784
|
+
permSel === 0 ? '❯ ' : ' ',
|
|
785
|
+
t('perm.yes')),
|
|
786
|
+
React.createElement(Text, { color: permSel === 1 ? theme.redBright : theme.grey, bold: permSel === 1 },
|
|
787
|
+
permSel === 1 ? '❯ ' : ' ',
|
|
788
|
+
t('perm.yesAlways')),
|
|
789
|
+
React.createElement(Text, { color: permSel === 2 ? theme.redBright : theme.grey, bold: permSel === 2 },
|
|
790
|
+
permSel === 2 ? '❯ ' : ' ',
|
|
791
|
+
t('perm.no')),
|
|
792
|
+
React.createElement(Text, { color: theme.greyDim },
|
|
793
|
+
" ",
|
|
794
|
+
t('perm.hint')))))) : null,
|
|
795
|
+
ask ? (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.red, paddingX: 1, marginTop: 1 },
|
|
796
|
+
React.createElement(Text, { color: theme.redBright, bold: true }, ask.question.slice(0, 300)),
|
|
797
|
+
ask.options.map((o, i) => (React.createElement(Text, { key: i, color: askSel === i ? theme.redBright : theme.grey, bold: askSel === i },
|
|
798
|
+
askSel === i ? '❯ ' : ' ',
|
|
799
|
+
i + 1,
|
|
800
|
+
". ",
|
|
801
|
+
o.label,
|
|
802
|
+
o.description ? ` — ${o.description}` : ''))),
|
|
803
|
+
React.createElement(Text, { color: theme.greyDim },
|
|
804
|
+
" \u2191\u2193 / 1-",
|
|
805
|
+
ask.options.length,
|
|
806
|
+
" se\u00E7 \u00B7 Enter onayla"))) : null,
|
|
807
|
+
busy && !perm && !ask ? (thinking
|
|
808
|
+
? React.createElement(WormSpinner, { label: verb, tokens: tokens })
|
|
809
|
+
: React.createElement(WormSpinner, { label: phase })) : null,
|
|
810
|
+
taskPill ? (React.createElement(Box, { paddingX: 1 },
|
|
811
|
+
React.createElement(Text, { color: theme.greyDim }, taskPill))) : null,
|
|
812
|
+
!busy ? (React.createElement(Box, { flexDirection: "column" },
|
|
813
|
+
React.createElement(Box, { borderStyle: "round", borderColor: theme.red, paddingX: 1 },
|
|
814
|
+
React.createElement(Text, { color: theme.redBright, bold: true }, "\u276F "),
|
|
815
|
+
React.createElement(TextInput, { value: input, onChange: (val) => { setInput(val); setCmdSel(0); }, onSubmit: onSubmit, placeholder: t('input.placeholder') })),
|
|
816
|
+
input.startsWith('/') ? (() => {
|
|
817
|
+
const filtered = cmdFilter(input);
|
|
818
|
+
if (!filtered.length)
|
|
819
|
+
return React.createElement(Text, { color: theme.greyDim },
|
|
820
|
+
" ",
|
|
821
|
+
t('menu.noCmd'));
|
|
822
|
+
const selIdx = Math.min(cmdSel, filtered.length - 1);
|
|
823
|
+
const WIN = 8;
|
|
824
|
+
const start = Math.max(0, Math.min(selIdx - 3, filtered.length - WIN));
|
|
825
|
+
const view = filtered.slice(start, start + WIN);
|
|
826
|
+
return (React.createElement(Box, { flexDirection: "column", marginTop: 0, paddingX: 1 },
|
|
827
|
+
start > 0 ? React.createElement(Text, { color: theme.greyDim },
|
|
828
|
+
" ",
|
|
829
|
+
t('menu.moreUp', start)) : null,
|
|
830
|
+
view.map((c, k) => {
|
|
831
|
+
const on = start + k === selIdx;
|
|
832
|
+
return (React.createElement(Text, { key: c.name },
|
|
833
|
+
React.createElement(Text, { color: on ? theme.redBright : theme.grey, bold: on },
|
|
834
|
+
on ? '❯ ' : ' ',
|
|
835
|
+
c.name),
|
|
836
|
+
React.createElement(Text, { color: theme.greyDim },
|
|
837
|
+
' ',
|
|
838
|
+
cmdDesc(c.name) || c.desc)));
|
|
839
|
+
}),
|
|
840
|
+
start + WIN < filtered.length ? React.createElement(Text, { color: theme.greyDim },
|
|
841
|
+
" ",
|
|
842
|
+
t('menu.moreDown', filtered.length - (start + WIN))) : null,
|
|
843
|
+
React.createElement(Text, { color: theme.greyDim },
|
|
844
|
+
" ",
|
|
845
|
+
t('menu.navHint'))));
|
|
846
|
+
})() : (React.createElement(Text, { color: theme.greyDim },
|
|
847
|
+
" \uD83D\uDCA1 ",
|
|
848
|
+
tipText(tipId))),
|
|
849
|
+
React.createElement(StatusLine, { model: config.model, ctxTokens: ctxTokens }))) : null));
|
|
850
|
+
}
|
|
851
|
+
render(React.createElement(App, null));
|