termux-dev 1.1.1 → 1.1.2
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/assets/banner.svg +1 -1
- package/dist/cli/index.js +35 -8
- package/dist/cli/server.js +163 -40
- package/dist/core/loop.js +1 -1
- package/dist/providers/openai.js +23 -1
- package/dist/tools/index.js +3 -2
- package/dist/tools/server.js +46 -0
- package/package.json +1 -1
package/assets/banner.svg
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
<!-- Tagline & Badge -->
|
|
27
27
|
<text x="425" y="142" font-family="system-ui, -apple-system, sans-serif" font-size="14" font-weight="600" fill="#8b949e" text-anchor="middle" letter-spacing="3">
|
|
28
|
-
THE TERMINAL-NATIVE AI CODING AGENT <tspan fill="#00f2fe" font-weight="bold">v1.1.
|
|
28
|
+
THE TERMINAL-NATIVE AI CODING AGENT <tspan fill="#00f2fe" font-weight="bold">v1.1.2</tspan>
|
|
29
29
|
</text>
|
|
30
30
|
|
|
31
31
|
<!-- Top Accent Line -->
|
package/dist/cli/index.js
CHANGED
|
@@ -378,7 +378,7 @@ function drawLogo() {
|
|
|
378
378
|
pc.cyan(' █▀▀▄ █▀▀▀ █ █ █ █'),
|
|
379
379
|
pc.cyan(' █ █ █▀▀▀ ▀▄▀ ▀▄▀ '),
|
|
380
380
|
pc.cyan(' █▄▄▀ █▄▄▄ ▀ ▀ ▀ '),
|
|
381
|
-
' ' + pc.cyan(pc.bold('v1.1.
|
|
381
|
+
' ' + pc.cyan(pc.bold('v1.1.2')),
|
|
382
382
|
''
|
|
383
383
|
];
|
|
384
384
|
for (const line of logo) {
|
|
@@ -393,7 +393,7 @@ function drawLogo() {
|
|
|
393
393
|
indent + pc.cyan('▀▀▀█▀▀▀ █▀▀▀ █▀▀█ █▄ ▄█ █ █ ▀▄ ▄▀ █▀▀▄ █▀▀▀ █ █'),
|
|
394
394
|
indent + pc.cyan(' █ █▀▀▀ █▄▄▀ █ █ █ █ █ █ ▀▀ █ █ █▀▀▀ █ █'),
|
|
395
395
|
indent + pc.cyan(' █ █▄▄▄ █ ▀▄ █ █ ▀▄▄▀ ▄▀ ▀▄ █▄▄▀ █▄▄▄ ▀▄▀ '),
|
|
396
|
-
indent + pc.cyan(pc.bold('v1.1.
|
|
396
|
+
indent + pc.cyan(pc.bold('v1.1.2')),
|
|
397
397
|
''
|
|
398
398
|
];
|
|
399
399
|
for (const line of logo) {
|
|
@@ -415,6 +415,33 @@ function formatSessionTime(timestamp) {
|
|
|
415
415
|
const days = Math.floor(hours / 24);
|
|
416
416
|
return `${days}d ago`;
|
|
417
417
|
}
|
|
418
|
+
function formatToolGeneratingLabel(name, targetHint) {
|
|
419
|
+
const t = name.toLowerCase();
|
|
420
|
+
const target = targetHint ? ` ${targetHint}` : '';
|
|
421
|
+
if (t === 'read_file' || t === 'read')
|
|
422
|
+
return `→ Read${target || '...'}`;
|
|
423
|
+
if (t === 'write_file' || t === 'write')
|
|
424
|
+
return `→ Write${target || '...'}`;
|
|
425
|
+
if (t === 'edit_file' || t === 'patch')
|
|
426
|
+
return `→ Edit${target || '...'}`;
|
|
427
|
+
if (t === 'delete_file' || t === 'remove_file' || t === 'rm')
|
|
428
|
+
return `→ Delete${target || '...'}`;
|
|
429
|
+
if (t === 'bash' || t === 'run_command' || t === 'exec')
|
|
430
|
+
return `→ Run:${target || '...'}`;
|
|
431
|
+
if (t === 'search' || t === 'grep_search')
|
|
432
|
+
return `→ Search${target || '...'}`;
|
|
433
|
+
if (t === 'list_dir' || t === 'ls')
|
|
434
|
+
return `→ List${target || '...'}`;
|
|
435
|
+
if (t === 'todo_list' || t === 'update_todos')
|
|
436
|
+
return `→ Update Plan & Tasks...`;
|
|
437
|
+
if (t === 'plan_ready')
|
|
438
|
+
return `→ Finalize Plan...`;
|
|
439
|
+
if (t === 'diagnose_code')
|
|
440
|
+
return `→ Diagnosing code...`;
|
|
441
|
+
if (t === 'ask_questions')
|
|
442
|
+
return `→ Clarifying questions...`;
|
|
443
|
+
return `→ ${name}${target}...`;
|
|
444
|
+
}
|
|
418
445
|
async function handleSessionDelete() {
|
|
419
446
|
while (true) {
|
|
420
447
|
const sessions = await SessionManager.listSessions();
|
|
@@ -588,7 +615,7 @@ export async function main() {
|
|
|
588
615
|
const maxIter = config.maxIterations || 100;
|
|
589
616
|
const maxIterLabel = maxIter >= 9999 ? 'Unlimited' : `${maxIter} steps`;
|
|
590
617
|
const choice = await select({
|
|
591
|
-
message: `${pc.bold('⚙️ Settings')} ${pc.dim('(devx v1.1.
|
|
618
|
+
message: `${pc.bold('⚙️ Settings')} ${pc.dim('(devx v1.1.2 • by ApvCode)')}`,
|
|
592
619
|
choices: [
|
|
593
620
|
{
|
|
594
621
|
name: `${config.pureBlackTheme !== false ? pc.green('🎨 Pure Black Background: ON') : pc.yellow('🎨 Pure Black Background: OFF')}`,
|
|
@@ -624,7 +651,7 @@ export async function main() {
|
|
|
624
651
|
description: 'Limit how many tool steps (file edits, terminal commands) agent can do per request'
|
|
625
652
|
},
|
|
626
653
|
{
|
|
627
|
-
name: `${pc.cyan('✨ About devx')} ${pc.dim('(v1.1.
|
|
654
|
+
name: `${pc.cyan('✨ About devx')} ${pc.dim('(v1.1.2 by ApvCode)')}`,
|
|
628
655
|
value: 'about',
|
|
629
656
|
description: 'Terminal-Native AI Coding Agent created by ApvCode (https://github.com/apvcode/Termux-Dev)'
|
|
630
657
|
},
|
|
@@ -636,7 +663,7 @@ export async function main() {
|
|
|
636
663
|
]
|
|
637
664
|
});
|
|
638
665
|
if (choice === 'about') {
|
|
639
|
-
p.note(`⚡ devx v1.1.
|
|
666
|
+
p.note(`⚡ devx v1.1.2 — Terminal-Native AI Coding Agent\n` +
|
|
640
667
|
`👤 Author: ApvCode (https://github.com/apvcode)\n` +
|
|
641
668
|
`🌟 Repository: https://github.com/apvcode/Termux-Dev\n` +
|
|
642
669
|
`📜 License: MIT License (2026)\n` +
|
|
@@ -1368,15 +1395,15 @@ export async function main() {
|
|
|
1368
1395
|
}
|
|
1369
1396
|
}
|
|
1370
1397
|
else if (event.type === 'tool_generating') {
|
|
1398
|
+
const label = formatToolGeneratingLabel(event.name, event.targetHint);
|
|
1371
1399
|
if (!spinnerActive) {
|
|
1372
1400
|
streamer.finish();
|
|
1373
1401
|
finishThinking(false);
|
|
1374
|
-
s.start(pc.cyan(
|
|
1402
|
+
s.start(pc.bold(pc.cyan(label)));
|
|
1375
1403
|
spinnerActive = true;
|
|
1376
1404
|
}
|
|
1377
1405
|
else {
|
|
1378
|
-
|
|
1379
|
-
s.message(pc.cyan(`⚡ Generating ${event.name} (${chars})...`));
|
|
1406
|
+
s.message(pc.bold(pc.cyan(label)));
|
|
1380
1407
|
}
|
|
1381
1408
|
}
|
|
1382
1409
|
else if (event.type === 'tool_start') {
|
package/dist/cli/server.js
CHANGED
|
@@ -10,6 +10,8 @@ const MIME_TYPES = {
|
|
|
10
10
|
'.css': 'text/css; charset=utf-8',
|
|
11
11
|
'.js': 'application/javascript; charset=utf-8',
|
|
12
12
|
'.mjs': 'application/javascript; charset=utf-8',
|
|
13
|
+
'.ts': 'text/plain; charset=utf-8',
|
|
14
|
+
'.tsx': 'text/plain; charset=utf-8',
|
|
13
15
|
'.json': 'application/json; charset=utf-8',
|
|
14
16
|
'.png': 'image/png',
|
|
15
17
|
'.jpg': 'image/jpeg',
|
|
@@ -22,6 +24,8 @@ const MIME_TYPES = {
|
|
|
22
24
|
'.mp3': 'audio/mpeg',
|
|
23
25
|
'.ogg': 'audio/ogg',
|
|
24
26
|
'.wasm': 'application/wasm',
|
|
27
|
+
'.pdf': 'application/pdf',
|
|
28
|
+
'.md': 'text/markdown; charset=utf-8',
|
|
25
29
|
'.txt': 'text/plain; charset=utf-8'
|
|
26
30
|
};
|
|
27
31
|
let activeServer = null;
|
|
@@ -45,12 +49,98 @@ export function getServerPort() {
|
|
|
45
49
|
}
|
|
46
50
|
export function stopServer() {
|
|
47
51
|
if (activeServer) {
|
|
48
|
-
|
|
52
|
+
try {
|
|
53
|
+
activeServer.close();
|
|
54
|
+
}
|
|
55
|
+
catch { }
|
|
49
56
|
activeServer = null;
|
|
50
57
|
return true;
|
|
51
58
|
}
|
|
52
59
|
return false;
|
|
53
60
|
}
|
|
61
|
+
function renderDirectoryHtml(dirPath, relPath, files, port) {
|
|
62
|
+
const currentRel = relPath === '/' ? '' : relPath;
|
|
63
|
+
const items = files
|
|
64
|
+
.filter(f => !f.name.startsWith('.') && f.name !== 'node_modules')
|
|
65
|
+
.sort((a, b) => {
|
|
66
|
+
if (a.isDirectory() && !b.isDirectory())
|
|
67
|
+
return -1;
|
|
68
|
+
if (!a.isDirectory() && b.isDirectory())
|
|
69
|
+
return 1;
|
|
70
|
+
return a.name.localeCompare(b.name);
|
|
71
|
+
})
|
|
72
|
+
.map(f => {
|
|
73
|
+
const isDir = f.isDirectory();
|
|
74
|
+
const href = `${currentRel}/${encodeURIComponent(f.name)}${isDir ? '/' : ''}`;
|
|
75
|
+
const ext = path.extname(f.name).toLowerCase();
|
|
76
|
+
let icon = isDir ? '📁' : '📄';
|
|
77
|
+
let badge = '';
|
|
78
|
+
if (ext === '.html' || ext === '.htm') {
|
|
79
|
+
icon = '🌐';
|
|
80
|
+
badge = '<span class="badge">HTML</span>';
|
|
81
|
+
}
|
|
82
|
+
else if (['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp'].includes(ext)) {
|
|
83
|
+
icon = '🖼️';
|
|
84
|
+
}
|
|
85
|
+
else if (['.js', '.ts', '.tsx', '.jsx', '.json'].includes(ext)) {
|
|
86
|
+
icon = '⚡';
|
|
87
|
+
}
|
|
88
|
+
else if (ext === '.css') {
|
|
89
|
+
icon = '🎨';
|
|
90
|
+
}
|
|
91
|
+
return `
|
|
92
|
+
<li>
|
|
93
|
+
<a class="file-item" href="${href}">
|
|
94
|
+
<span class="icon">${icon}</span>
|
|
95
|
+
<span class="name">${f.name}</span>
|
|
96
|
+
${badge}
|
|
97
|
+
<span class="type">${isDir ? 'Directory' : ext || 'File'}</span>
|
|
98
|
+
</a>
|
|
99
|
+
</li>
|
|
100
|
+
`;
|
|
101
|
+
})
|
|
102
|
+
.join('');
|
|
103
|
+
const parentLink = currentRel && currentRel !== '/'
|
|
104
|
+
? `<li><a class="file-item" href="${path.dirname(currentRel) === '/' ? '/' : path.dirname(currentRel) + '/'}"><span class="icon">⬆️</span><span class="name">.. (Parent Directory)</span></a></li>`
|
|
105
|
+
: '';
|
|
106
|
+
return `<!DOCTYPE html>
|
|
107
|
+
<html lang="en">
|
|
108
|
+
<head>
|
|
109
|
+
<meta charset="utf-8"/>
|
|
110
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
|
111
|
+
<title>devx Live Preview • ${relPath}</title>
|
|
112
|
+
<style>
|
|
113
|
+
* { box-sizing: border-box; }
|
|
114
|
+
body { background: #0a0a0c; color: #e1e4e8; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, monospace; margin: 0; padding: 24px 16px; }
|
|
115
|
+
.card { background: #111116; border: 1px solid #22222c; border-radius: 12px; max-width: 800px; margin: 0 auto; padding: 24px; box-shadow: 0 8px 30px rgba(0,0,0,0.6); }
|
|
116
|
+
h1 { color: #00f2fe; margin: 0 0 8px 0; font-size: 22px; display: flex; align-items: center; gap: 8px; }
|
|
117
|
+
.info { color: #8b949e; font-size: 14px; margin-bottom: 20px; word-break: break-all; }
|
|
118
|
+
.info code { background: #1a1a24; padding: 2px 6px; border-radius: 4px; color: #f0f6fc; }
|
|
119
|
+
ul { list-style: none; padding: 0; margin: 0; }
|
|
120
|
+
.file-item { display: flex; align-items: center; padding: 10px 14px; border-bottom: 1px solid #1a1a22; text-decoration: none; color: #f0f6fc; border-radius: 6px; transition: all 0.15s; }
|
|
121
|
+
.file-item:hover { background: #1a1a26; transform: translateX(3px); }
|
|
122
|
+
.icon { margin-right: 12px; font-size: 18px; }
|
|
123
|
+
.name { flex: 1; font-weight: 500; font-size: 14px; word-break: break-all; }
|
|
124
|
+
.type { color: #6e7681; font-size: 12px; margin-left: 12px; }
|
|
125
|
+
.badge { background: rgba(0, 242, 254, 0.15); color: #00f2fe; border: 1px solid rgba(0, 242, 254, 0.3); padding: 2px 8px; border-radius: 12px; font-size: 11px; margin-left: 8px; font-weight: bold; }
|
|
126
|
+
.footer { margin-top: 24px; text-align: center; color: #484f58; font-size: 12px; }
|
|
127
|
+
</style>
|
|
128
|
+
</head>
|
|
129
|
+
<body>
|
|
130
|
+
<div class="card">
|
|
131
|
+
<h1>⚡ devx Live Preview</h1>
|
|
132
|
+
<div class="info">
|
|
133
|
+
Path: <code>${relPath}</code> • Port: <code>${port}</code>
|
|
134
|
+
</div>
|
|
135
|
+
<ul>
|
|
136
|
+
${parentLink}
|
|
137
|
+
${items || '<li style="padding: 20px; text-align: center; color: #6e7681;">No visible files in this directory</li>'}
|
|
138
|
+
</ul>
|
|
139
|
+
<div class="footer">devx v1.1.2 • Terminal-Native AI Assistant</div>
|
|
140
|
+
</div>
|
|
141
|
+
</body>
|
|
142
|
+
</html>`;
|
|
143
|
+
}
|
|
54
144
|
export async function startServer(preferredPort = 3000) {
|
|
55
145
|
if (activeServer) {
|
|
56
146
|
stopServer();
|
|
@@ -59,75 +149,108 @@ export async function startServer(preferredPort = 3000) {
|
|
|
59
149
|
return new Promise((resolve, reject) => {
|
|
60
150
|
const server = http.createServer(async (req, res) => {
|
|
61
151
|
try {
|
|
62
|
-
let
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const filePath = path.join(process.cwd(), reqPath);
|
|
152
|
+
let rawPath = req.url?.split('?')[0] || '/';
|
|
153
|
+
let reqPath = decodeURIComponent(rawPath);
|
|
154
|
+
const cwd = process.cwd();
|
|
155
|
+
let targetPath = path.resolve(cwd, '.' + reqPath);
|
|
67
156
|
// Security check: ensure path is within cwd
|
|
68
|
-
|
|
157
|
+
const rel = path.relative(cwd, targetPath);
|
|
158
|
+
if (rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
69
159
|
res.writeHead(403, { 'Content-Type': 'text/plain' });
|
|
70
160
|
res.end('Forbidden');
|
|
71
161
|
return;
|
|
72
162
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
163
|
+
// 1. Root / Directory Request Handling
|
|
164
|
+
if (fsSync.existsSync(targetPath)) {
|
|
165
|
+
const stat = await fs.stat(targetPath);
|
|
166
|
+
if (stat.isDirectory()) {
|
|
167
|
+
// Check for index.html in directory
|
|
168
|
+
const possibleIndexes = [
|
|
169
|
+
path.join(targetPath, 'index.html'),
|
|
170
|
+
path.join(targetPath, 'index.htm'),
|
|
171
|
+
path.join(targetPath, 'public/index.html'),
|
|
172
|
+
path.join(targetPath, 'dist/index.html'),
|
|
173
|
+
path.join(targetPath, 'build/index.html')
|
|
174
|
+
];
|
|
175
|
+
for (const idxPath of possibleIndexes) {
|
|
176
|
+
if (fsSync.existsSync(idxPath)) {
|
|
177
|
+
const content = await fs.readFile(idxPath);
|
|
178
|
+
res.writeHead(200, {
|
|
179
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
180
|
+
'Access-Control-Allow-Origin': '*'
|
|
181
|
+
});
|
|
182
|
+
res.end(content);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// If no index.html, render sleek directory explorer
|
|
187
|
+
const dirents = await fs.readdir(targetPath, { withFileTypes: true });
|
|
188
|
+
const dirHtml = renderDirectoryHtml(targetPath, reqPath, dirents, activePort);
|
|
189
|
+
res.writeHead(200, {
|
|
190
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
191
|
+
'Access-Control-Allow-Origin': '*'
|
|
192
|
+
});
|
|
193
|
+
res.end(dirHtml);
|
|
80
194
|
return;
|
|
81
195
|
}
|
|
82
|
-
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
83
|
-
res.end(`404 Not Found: ${reqPath}`);
|
|
84
|
-
return;
|
|
85
196
|
}
|
|
86
|
-
|
|
87
|
-
if (
|
|
88
|
-
const
|
|
89
|
-
if (fsSync.existsSync(
|
|
90
|
-
|
|
91
|
-
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
92
|
-
res.end(content);
|
|
93
|
-
return;
|
|
197
|
+
// 2. Fallback check for missing .html extension
|
|
198
|
+
if (!fsSync.existsSync(targetPath)) {
|
|
199
|
+
const withHtml = `${targetPath}.html`;
|
|
200
|
+
if (fsSync.existsSync(withHtml)) {
|
|
201
|
+
targetPath = withHtml;
|
|
94
202
|
}
|
|
95
|
-
|
|
96
|
-
|
|
203
|
+
}
|
|
204
|
+
// 3. Serve File
|
|
205
|
+
if (fsSync.existsSync(targetPath)) {
|
|
206
|
+
const ext = path.extname(targetPath).toLowerCase();
|
|
207
|
+
const contentType = MIME_TYPES[ext] || 'application/octet-stream';
|
|
208
|
+
const content = await fs.readFile(targetPath);
|
|
209
|
+
res.writeHead(200, {
|
|
210
|
+
'Content-Type': contentType,
|
|
211
|
+
'Access-Control-Allow-Origin': '*'
|
|
212
|
+
});
|
|
213
|
+
res.end(content);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
// 4. SPA Fallback: check if root index.html exists
|
|
217
|
+
const rootIndex = path.join(cwd, 'index.html');
|
|
218
|
+
if (fsSync.existsSync(rootIndex)) {
|
|
219
|
+
const content = await fs.readFile(rootIndex);
|
|
220
|
+
res.writeHead(200, {
|
|
221
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
222
|
+
'Access-Control-Allow-Origin': '*'
|
|
223
|
+
});
|
|
224
|
+
res.end(content);
|
|
97
225
|
return;
|
|
98
226
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const content = await fs.readFile(filePath);
|
|
102
|
-
res.writeHead(200, {
|
|
103
|
-
'Content-Type': contentType,
|
|
104
|
-
'Access-Control-Allow-Origin': '*'
|
|
105
|
-
});
|
|
106
|
-
res.end(content);
|
|
227
|
+
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
228
|
+
res.end(`404 Not Found: ${reqPath}`);
|
|
107
229
|
}
|
|
108
230
|
catch (err) {
|
|
109
|
-
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
231
|
+
res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
110
232
|
res.end(`Internal Server Error: ${err.message}`);
|
|
111
233
|
}
|
|
112
234
|
});
|
|
113
235
|
server.on('error', (err) => {
|
|
114
236
|
if (err.code === 'EADDRINUSE') {
|
|
115
|
-
server.listen(activePort + 1);
|
|
116
237
|
activePort++;
|
|
238
|
+
server.listen(activePort, '0.0.0.0');
|
|
117
239
|
}
|
|
118
240
|
else {
|
|
119
241
|
reject(err);
|
|
120
242
|
}
|
|
121
243
|
});
|
|
122
|
-
server.listen(activePort, () => {
|
|
244
|
+
server.listen(activePort, '0.0.0.0', () => {
|
|
123
245
|
activeServer = server;
|
|
124
246
|
const localIp = getLocalIp();
|
|
125
247
|
const localUrl = `http://localhost:${activePort}`;
|
|
126
248
|
const networkUrl = `http://${localIp}:${activePort}`;
|
|
127
|
-
// If
|
|
249
|
+
// If on Android Termux, attempt to open browser
|
|
128
250
|
if (process.env.PREFIX?.includes('com.termux')) {
|
|
129
251
|
try {
|
|
130
|
-
spawn('termux-open-url', [localUrl], { stdio: 'ignore' });
|
|
252
|
+
const opener = spawn('termux-open-url', [localUrl], { stdio: 'ignore', detached: true });
|
|
253
|
+
opener.unref();
|
|
131
254
|
}
|
|
132
255
|
catch { }
|
|
133
256
|
}
|
package/dist/core/loop.js
CHANGED
|
@@ -88,7 +88,7 @@ export class Agent {
|
|
|
88
88
|
yield { type: 'text_delta', delta: chunk.delta };
|
|
89
89
|
}
|
|
90
90
|
else if (chunk.type === 'tool_generating') {
|
|
91
|
-
yield { type: 'tool_generating', name: chunk.name, bytes: chunk.bytes };
|
|
91
|
+
yield { type: 'tool_generating', name: chunk.name, bytes: chunk.bytes, targetHint: chunk.targetHint };
|
|
92
92
|
}
|
|
93
93
|
else if (chunk.type === 'done') {
|
|
94
94
|
response = chunk.response;
|
package/dist/providers/openai.js
CHANGED
|
@@ -261,10 +261,32 @@ export class OpenAIProvider {
|
|
|
261
261
|
if (tc.function?.arguments)
|
|
262
262
|
existing.argsStr += tc.function.arguments;
|
|
263
263
|
toolMap.set(idx, existing);
|
|
264
|
+
// Extract live target hint (e.g. path or command being generated)
|
|
265
|
+
let targetHint = undefined;
|
|
266
|
+
const args = existing.argsStr;
|
|
267
|
+
if (args) {
|
|
268
|
+
const pathMatch = args.match(/"(?:path|filePath|targetFile)"\s*:\s*"([^"\\]*(?:\\.[^"\\]*)*)"/);
|
|
269
|
+
if (pathMatch && pathMatch[1]) {
|
|
270
|
+
targetHint = pathMatch[1];
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
const cmdMatch = args.match(/"(?:command|cmd)"\s*:\s*"([^"\\]*(?:\\.[^"\\]*)*)"/);
|
|
274
|
+
if (cmdMatch && cmdMatch[1]) {
|
|
275
|
+
targetHint = cmdMatch[1].length > 30 ? cmdMatch[1].slice(0, 27) + '...' : cmdMatch[1];
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
const qMatch = args.match(/"(?:query|pattern)"\s*:\s*"([^"\\]*(?:\\.[^"\\]*)*)"/);
|
|
279
|
+
if (qMatch && qMatch[1]) {
|
|
280
|
+
targetHint = `"${qMatch[1]}"`;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
264
285
|
yield {
|
|
265
286
|
type: 'tool_generating',
|
|
266
287
|
name: existing.name || 'tool',
|
|
267
|
-
bytes: existing.argsStr.length
|
|
288
|
+
bytes: existing.argsStr.length,
|
|
289
|
+
targetHint
|
|
268
290
|
};
|
|
269
291
|
}
|
|
270
292
|
}
|
package/dist/tools/index.js
CHANGED
|
@@ -8,11 +8,12 @@ import { installPackageTool } from './packages.js';
|
|
|
8
8
|
import { saveMemoryTool } from '../core/memory.js';
|
|
9
9
|
import { planReadyTool, lastPlanReady, resetPlanReady } from './plan.js';
|
|
10
10
|
import { todoListTool, currentTodoList, resetTodoList } from './todo.js';
|
|
11
|
+
import { servePreviewTool } from './server.js';
|
|
11
12
|
export function getTools(planMode) {
|
|
12
|
-
const baseTools = [readFileTool, listDirTool, searchTool, askQuestionsTool, webSearchTool, fetchUrlTool, saveMemoryTool, planReadyTool, todoListTool];
|
|
13
|
+
const baseTools = [readFileTool, listDirTool, searchTool, askQuestionsTool, webSearchTool, fetchUrlTool, saveMemoryTool, planReadyTool, todoListTool, servePreviewTool];
|
|
13
14
|
if (planMode) {
|
|
14
15
|
return baseTools;
|
|
15
16
|
}
|
|
16
17
|
return [...baseTools, writeFileTool, editFileTool, mkdirTool, bashTool, diagnoseCodeTool, installPackageTool];
|
|
17
18
|
}
|
|
18
|
-
export { webSearchTool, fetchUrlTool, diagnoseCodeTool, installPackageTool, saveMemoryTool, planReadyTool, lastPlanReady, resetPlanReady, todoListTool, currentTodoList, resetTodoList };
|
|
19
|
+
export { webSearchTool, fetchUrlTool, diagnoseCodeTool, installPackageTool, saveMemoryTool, planReadyTool, lastPlanReady, resetPlanReady, todoListTool, currentTodoList, resetTodoList, servePreviewTool };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { startServer, stopServer, isServerRunning, getServerPort } from '../cli/server.js';
|
|
2
|
+
export const servePreviewTool = {
|
|
3
|
+
name: 'serve_preview',
|
|
4
|
+
definition: {
|
|
5
|
+
name: 'serve_preview',
|
|
6
|
+
description: 'Start or stop the built-in local live web preview server to view HTML/web apps in the browser.',
|
|
7
|
+
parameters: {
|
|
8
|
+
type: 'object',
|
|
9
|
+
properties: {
|
|
10
|
+
action: {
|
|
11
|
+
type: 'string',
|
|
12
|
+
enum: ['start', 'stop', 'status'],
|
|
13
|
+
description: 'Action to perform: start the server, stop it, or check status'
|
|
14
|
+
},
|
|
15
|
+
port: {
|
|
16
|
+
type: 'number',
|
|
17
|
+
description: 'Optional preferred port number (defaults to 3000)'
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
required: ['action']
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
validateArgs: (args) => {
|
|
24
|
+
if (!args || !args.action) {
|
|
25
|
+
throw new Error('action is required');
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
execute: async (args) => {
|
|
29
|
+
if (args.action === 'stop') {
|
|
30
|
+
const stopped = stopServer();
|
|
31
|
+
return stopped ? 'Web preview server stopped.' : 'No web preview server was running.';
|
|
32
|
+
}
|
|
33
|
+
if (args.action === 'status') {
|
|
34
|
+
const running = isServerRunning();
|
|
35
|
+
return running ? `Web server is running on port ${getServerPort()}.` : 'Web server is not running.';
|
|
36
|
+
}
|
|
37
|
+
const preferredPort = args.port || 3000;
|
|
38
|
+
try {
|
|
39
|
+
const { port, localUrl, networkUrl } = await startServer(preferredPort);
|
|
40
|
+
return `Web Server is now live!\n• Local URL: ${localUrl}\n• Network URL: ${networkUrl}\nOpened in browser automatically.`;
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
throw new Error(`Failed to start web server: ${err.message}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
};
|
package/package.json
CHANGED