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 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.1</tspan>
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.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.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.1 • by ApvCode)')}`,
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.1 by ApvCode)')}`,
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.1 — Terminal-Native AI Coding Agent\n` +
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(`⚡ Calling ${event.name}...`));
1402
+ s.start(pc.bold(pc.cyan(label)));
1375
1403
  spinnerActive = true;
1376
1404
  }
1377
1405
  else {
1378
- const chars = event.bytes > 1000 ? `${(event.bytes / 1000).toFixed(1)}k chars` : `${event.bytes} chars`;
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') {
@@ -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
- activeServer.close();
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 &bull; ${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> &bull; 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 &bull; 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 reqPath = decodeURIComponent(req.url?.split('?')[0] || '/');
63
- if (reqPath === '/') {
64
- reqPath = '/index.html';
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
- if (!filePath.startsWith(process.cwd())) {
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
- if (!fsSync.existsSync(filePath)) {
74
- // If html file requested without extension
75
- const withHtml = `${filePath}.html`;
76
- if (fsSync.existsSync(withHtml)) {
77
- const content = await fs.readFile(withHtml);
78
- res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
79
- res.end(content);
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
- const stat = await fs.stat(filePath);
87
- if (stat.isDirectory()) {
88
- const indexPath = path.join(filePath, 'index.html');
89
- if (fsSync.existsSync(indexPath)) {
90
- const content = await fs.readFile(indexPath);
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
- res.writeHead(403, { 'Content-Type': 'text/plain' });
96
- res.end('Directory listing disabled');
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
- const ext = path.extname(filePath).toLowerCase();
100
- const contentType = MIME_TYPES[ext] || 'application/octet-stream';
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 in Android Termux, try to open in browser
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;
@@ -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
  }
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "termux-dev",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "Ultra-fast, terminal-native AI coding assistant and agent built for Android Termux, Windows, macOS, and Linux.",
5
5
  "main": "dist/cli/index.js",
6
6
  "type": "module",