@quangnv13/nonstop 1.0.4 → 1.0.7

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 CHANGED
@@ -129,7 +129,7 @@ Once the bot runtime is active, you can interact with it via the following Teleg
129
129
  * Selecting a workspace sets it as the working directory for your next PTY session.
130
130
 
131
131
  #### **⚙️ Dynamic Configuration**
132
- * Press **⚙️ Cấu hình** or send `/config`.
132
+ * Press **⚙️ Settings** or send `/config`.
133
133
  * Tap any settings button (e.g. *Token*, *Admin*, *Interval*, etc.) and send a new value via message to apply immediately.
134
134
  * If you modify the `Telegram Bot Token`, the bot will automatically reload and restart itself securely.
135
135
 
package/dist/bot.js CHANGED
@@ -40,6 +40,7 @@ const path = __importStar(require("path"));
40
40
  const logger_js_1 = require("./logger.js");
41
41
  const session_controls_js_1 = require("./session-controls.js");
42
42
  const store_js_1 = require("./store.js");
43
+ const i18n_js_1 = require("./i18n.js");
43
44
  const grammyRequire = require;
44
45
  const { Bot, InlineKeyboard } = grammyRequire('grammy');
45
46
  const SUPPORTED_PRESETS = ['powershell', 'bash', 'codex', 'antigravity'];
@@ -71,6 +72,7 @@ function createBotRuntime(deps) {
71
72
  }
72
73
  const bot = new Bot(token);
73
74
  const chatStates = new Map();
75
+ const getT = () => (0, i18n_js_1.createTranslator)(deps.getConfig().language);
74
76
  function getChatState(chatId) {
75
77
  const existing = chatStates.get(chatId);
76
78
  if (existing)
@@ -111,63 +113,69 @@ function createBotRuntime(deps) {
111
113
  }
112
114
  function buildMainMenuText() {
113
115
  const activeSession = deps.getActiveSession();
116
+ const t = getT();
114
117
  return [
115
118
  '🖥 nonstop client',
116
119
  '',
117
120
  `📁 Workspaces: ${deps.getWorkspaces().length}`,
118
121
  activeSession
119
- ? `⚡ Session: ${activeSession.preset} | ${activeSession.cwd}`
120
- : '⚡ Session: không có'
122
+ ? t('bot.menu.activeSessionRunning', { preset: activeSession.preset, cwd: activeSession.cwd })
123
+ : t('bot.menu.activeSessionNone')
121
124
  ].join('\n');
122
125
  }
123
126
  function buildMainMenuKeyboard() {
127
+ const t = getT();
124
128
  return createKeyboard()
125
- .text('📁 Workspaces', 'workspaces_list')
126
- .text('⚡ Session', 'sessions_list')
129
+ .text(t('bot.menu.workspaces'), 'workspaces_list')
130
+ .text(t('bot.menu.session'), 'sessions_list')
127
131
  .row()
128
- .text('⚙️ Cấu hình', 'config_menu')
129
- .text('ℹ️ Trợ giúp', 'help_view');
132
+ .text(t('bot.menu.config'), 'config_menu')
133
+ .text(t('bot.menu.help'), 'help_view');
130
134
  }
131
135
  async function showMainMenu(ctx) {
132
136
  await renderText(ctx, buildMainMenuText(), buildMainMenuKeyboard());
133
137
  }
134
138
  async function showHelp(ctx) {
139
+ const t = getT();
135
140
  await renderText(ctx, [
136
- '📖 Lệnh có sẵn',
141
+ t('bot.help.title'),
137
142
  '',
138
- '/start — Mở menu chính',
139
- '/status — Trạng thái runtime',
140
- '/help — Trợ giúp',
141
- '/config — Cấu hình hệ thống',
142
- '/send <lệnh> — Gửi lệnh thô tới session',
143
+ t('bot.help.start'),
144
+ t('bot.help.status'),
145
+ t('bot.help.help'),
146
+ t('bot.help.config'),
147
+ t('bot.help.send'),
143
148
  '',
144
- 'Khi input mode BẬT, tin nhắn thường sẽ được gửi thẳng vào session.'
145
- ].join('\n'), createKeyboard().text('⬅️ Quay lại', 'main_menu'));
149
+ t('bot.help.inputModeNotice')
150
+ ].join('\n'), createKeyboard().text(t('bot.general.back'), 'main_menu'));
146
151
  }
147
152
  async function showStatus(ctx) {
148
153
  const activeSession = deps.getActiveSession();
149
154
  const currentAllowedUsername = normalizeUsername(process.env.ADMIN_USERNAME || process.env.TELEGRAM_USERNAME || '');
155
+ const t = getT();
150
156
  await renderText(ctx, [
151
- '📊 Trạng thái Runtime',
157
+ t('bot.status.title'),
152
158
  '',
153
- `Người dùng: ${currentAllowedUsername || 'không giới hạn'}`,
154
- `Workspaces: ${deps.getWorkspaces().length}`,
155
- `Session: ${activeSession ? 'đang chạy' : 'không có'}`,
156
- activeSession ? `Preset: ${activeSession.preset}` : '',
157
- activeSession ? `Thư mục: ${activeSession.cwd}` : ''
158
- ].filter(Boolean).join('\n'), createKeyboard().text('⬅️ Quay lại', 'main_menu'));
159
+ `${t('bot.status.user')}: ${currentAllowedUsername || t('bot.status.unlimited')}`,
160
+ `${t('bot.status.workspaces')}: ${deps.getWorkspaces().length}`,
161
+ `${t('bot.status.session')}: ${activeSession ? t('bot.status.running') : t('bot.status.none')}`,
162
+ activeSession ? `${t('bot.status.preset')}: ${activeSession.preset}` : '',
163
+ activeSession ? `${t('bot.status.directory')}: ${activeSession.cwd}` : ''
164
+ ].filter(Boolean).join('\n'), createKeyboard().text(t('bot.general.back'), 'main_menu'));
159
165
  }
160
166
  async function showConfigMenu(ctx) {
161
167
  const config = deps.getConfig();
168
+ const t = getT();
169
+ const notConfigured = t('bot.config.notConfigured');
162
170
  const lines = [
163
- '⚙️ Cấu hình nonstop',
171
+ t('bot.config.title'),
164
172
  '',
165
- `• Token: ${config.telegramBotToken ? '••••' + config.telegramBotToken.slice(-4) : 'Chưa cấu hình'}`,
166
- `• Admin Username: ${config.adminUsername || 'Chưa cấu hình'}`,
167
- `• Client Name: ${config.clientName || 'Chưa cấu hình'}`,
168
- `• Telegram Username: ${config.telegramUsername || 'Chưa cấu hình'}`,
169
- `• Ngôn ngữ: ${config.language} (vi/en)`,
170
- `• Chế độ khởi động: ${config.startupMode} (disabled/background/open-ui)`,
173
+ `• Token: ${config.telegramBotToken ? '••••' + config.telegramBotToken.slice(-4) : notConfigured}`,
174
+ `• Admin Username: ${config.adminUsername || notConfigured}`,
175
+ `• Client Name: ${config.clientName || notConfigured}`,
176
+ `• Telegram Username: ${config.telegramUsername || notConfigured}`,
177
+ `• ${t('bot.config.languageLabel')}: ${config.language} (vi/en)`,
178
+ `• ${t('bot.config.startupLabel')}: ${config.startupMode} (disabled/background/open-ui)`,
171
179
  `• Output Interval: ${config.outputInterval} ms`,
172
180
  `• Max Output Lines: ${config.maxOutputLines}`,
173
181
  `• Max Render Lines: ${config.maxRenderLines}`,
@@ -183,8 +191,8 @@ function createBotRuntime(deps) {
183
191
  .text('Client Name', 'config_edit:clientName')
184
192
  .text('Telegram User', 'config_edit:telegramUsername')
185
193
  .row()
186
- .text(`Ngôn ngữ (${config.language === 'vi' ? '🇻🇳 vi' : '🇬🇧 en'})`, 'config_edit:language')
187
- .text(`Khởi động (${config.startupMode})`, 'config_edit:startupMode')
194
+ .text(`${t('bot.config.languageLabel')} (${config.language === 'vi' ? '🇻🇳 vi' : '🇬🇧 en'})`, 'config_edit:language')
195
+ .text(`${t('bot.config.startupLabel')} (${config.startupMode})`, 'config_edit:startupMode')
188
196
  .row()
189
197
  .text('Interval', 'config_edit:outputInterval')
190
198
  .text('Max Output Lines', 'config_edit:maxOutputLines')
@@ -197,15 +205,16 @@ function createBotRuntime(deps) {
197
205
  .row()
198
206
  .text('Antigravity Args', 'config_edit:antigravityArgs')
199
207
  .row()
200
- .text('⬅️ Quay lại', 'main_menu');
208
+ .text(t('bot.general.back'), 'main_menu');
201
209
  await renderText(ctx, lines.join('\n'), keyboard);
202
210
  }
203
211
  async function showWorkspacesMenu(ctx) {
204
212
  const workspaces = deps.getWorkspaces();
205
- const lines = ['📁 Danh sách Workspace', ''];
213
+ const t = getT();
214
+ const lines = [t('bot.workspaces.title'), ''];
206
215
  const keyboard = createKeyboard();
207
216
  if (workspaces.length === 0) {
208
- lines.push('Chưa có workspace nào.');
217
+ lines.push(t('bot.workspaces.empty'));
209
218
  }
210
219
  else {
211
220
  for (const ws of workspaces) {
@@ -214,15 +223,16 @@ function createBotRuntime(deps) {
214
223
  keyboard.text(`📁 ${ws.name}`, `view_workspace:${ws.id}`).row();
215
224
  }
216
225
  }
217
- keyboard.text('➕ Thêm workspace', 'workspace_action:add').row().text('⬅️ Quay lại', 'main_menu');
226
+ keyboard.text(t('bot.workspaces.add'), 'workspace_action:add').row().text(t('bot.general.back'), 'main_menu');
218
227
  await renderText(ctx, lines.join('\n'), keyboard);
219
228
  }
220
229
  function buildWorkspaceDetailsKeyboard(workspace) {
230
+ const t = getT();
221
231
  return createKeyboard()
222
- .text('✏️ Sửa tên', `workspace_action:edit_name:${workspace.id}`)
223
- .text('🛠️ Sửa đường dẫn', `workspace_action:edit_path:${workspace.id}`)
232
+ .text(t('bot.workspaces.editName'), `workspace_action:edit_name:${workspace.id}`)
233
+ .text(t('bot.workspaces.editPath'), `workspace_action:edit_path:${workspace.id}`)
224
234
  .row()
225
- .text('🗑️ Xóa', `workspace_action:delete:${workspace.id}`)
235
+ .text(t('bot.workspaces.delete'), `workspace_action:delete:${workspace.id}`)
226
236
  .row()
227
237
  .text('Powershell', `start_session:${workspace.id}:powershell`)
228
238
  .text('Bash', `start_session:${workspace.id}:bash`)
@@ -230,53 +240,59 @@ function createBotRuntime(deps) {
230
240
  .text('Codex', `start_session:${workspace.id}:codex`)
231
241
  .text('Antigravity', `start_session:${workspace.id}:antigravity`)
232
242
  .row()
233
- .text('⬅️ Quay lại', 'workspaces_list');
243
+ .text(t('bot.general.back'), 'workspaces_list');
234
244
  }
235
245
  async function showWorkspaceDetails(ctx, workspaceId) {
236
246
  const workspace = getWorkspaceById(workspaceId);
247
+ const t = getT();
237
248
  if (!workspace) {
238
- await renderText(ctx, 'Workspace không tìm thấy.', createKeyboard().text('⬅️ Quay lại', 'workspaces_list'));
249
+ await renderText(ctx, t('bot.workspaces.notFound'), createKeyboard().text(t('bot.general.back'), 'workspaces_list'));
239
250
  return;
240
251
  }
241
- await renderText(ctx, ['📁 Chi tiết Workspace', '', `Tên: ${workspace.name}`, `Đường dẫn: ${workspace.path}`].join('\n'), buildWorkspaceDetailsKeyboard(workspace));
252
+ await renderText(ctx, [t('bot.workspaces.detailsTitle'), '', `${t('bot.workspaces.detailsName')}: ${workspace.name}`, `${t('bot.workspaces.detailsPath')}: ${workspace.path}`].join('\n'), buildWorkspaceDetailsKeyboard(workspace));
242
253
  }
243
254
  async function showSessionsMenu(ctx) {
244
255
  const activeSession = deps.getActiveSession();
245
256
  const keyboard = createKeyboard();
246
- const lines = ['⚡ Session', ''];
257
+ const t = getT();
258
+ const lines = [t('bot.sessions.title'), ''];
247
259
  if (!activeSession || activeSession.status !== 'running') {
248
- lines.push('Không có session đang chạy.');
260
+ lines.push(t('bot.sessions.empty'));
249
261
  }
250
262
  else {
251
263
  lines.push(`ID: ${activeSession.sessionId}`);
252
264
  lines.push(`Preset: ${activeSession.preset}`);
253
- lines.push(`Thư mục: ${activeSession.cwd}`);
254
- keyboard.text('🎮 Điều khiển', `view_session:${activeSession.sessionId}`).row();
265
+ lines.push(`${t('bot.sessionDetails.directory')}: ${activeSession.cwd}`);
266
+ keyboard.text(t('bot.sessions.control'), `view_session:${activeSession.sessionId}`).row();
255
267
  }
256
- keyboard.text('⬅️ Quay lại', 'main_menu');
268
+ keyboard.text(t('bot.general.back'), 'main_menu');
257
269
  await renderText(ctx, lines.join('\n'), keyboard);
258
270
  }
259
271
  async function showSessionDetails(ctx, sessionId) {
260
272
  const session = deps.getActiveSession();
273
+ const t = getT();
261
274
  if (!session || session.status !== 'running' || (sessionId && session.sessionId !== sessionId)) {
262
- await renderText(ctx, 'Session không đang chạy.', createKeyboard().text('⬅️ Quay lại', 'main_menu'));
275
+ await renderText(ctx, t('bot.sessionDetails.notRunning'), createKeyboard().text(t('bot.general.back'), 'main_menu'));
263
276
  return;
264
277
  }
265
278
  const keyboard = (0, session_controls_js_1.buildSessionActionMarkup)({
266
279
  sessionId: session.sessionId,
267
280
  inputMode: session.inputMode,
268
281
  autoEnter: session.autoEnter,
269
- includeBackButton: true
282
+ includeBackButton: true,
283
+ language: deps.getConfig().language
270
284
  });
285
+ const onText = t('bot.sessionDetails.on');
286
+ const offText = t('bot.sessionDetails.off');
271
287
  await renderText(ctx, [
272
- '🎮 Điều khiển Session',
288
+ t('bot.sessionDetails.title'),
273
289
  '',
274
290
  `ID: ${session.sessionId}`,
275
- `Preset: ${session.preset}`,
276
- `Trạng thái: ${session.status}`,
277
- `Thư mục: ${session.cwd}`,
278
- `Input mode: ${session.inputMode ? 'BẬT' : 'TẮT'}`,
279
- `Auto enter: ${session.autoEnter ? 'BẬT' : 'TẮT'}`
291
+ `${t('bot.sessionDetails.preset')}: ${session.preset}`,
292
+ `${t('bot.sessionDetails.status')}: ${session.status}`,
293
+ `${t('bot.sessionDetails.directory')}: ${session.cwd}`,
294
+ `${t('bot.sessionDetails.inputMode')}: ${session.inputMode ? onText : offText}`,
295
+ `${t('bot.sessionDetails.autoEnter')}: ${session.autoEnter ? onText : offText}`
280
296
  ].join('\n'), keyboard);
281
297
  }
282
298
  async function beginWorkspaceDraft(ctx, workspaceDraft, prompt) {
@@ -296,9 +312,10 @@ function createBotRuntime(deps) {
296
312
  if (!draft)
297
313
  return false;
298
314
  const workspaces = [...deps.getWorkspaces()];
315
+ const t = getT();
299
316
  if (draft.mode === 'add_name') {
300
317
  state.workspaceDraft = { mode: 'add_path', name: text };
301
- await ctx.reply('Nhập đường dẫn workspace:');
318
+ await ctx.reply(t('bot.workspaces.addPathPrompt'));
302
319
  return true;
303
320
  }
304
321
  if (draft.mode === 'add_path') {
@@ -310,21 +327,21 @@ function createBotRuntime(deps) {
310
327
  workspaces.push(workspace);
311
328
  deps.saveWorkspaces(workspaces);
312
329
  state.workspaceDraft = null;
313
- await ctx.reply(`✓ Đã thêm workspace "${workspace.name}".`);
330
+ await ctx.reply(t('bot.workspaces.added', { name: workspace.name }));
314
331
  await showWorkspaceDetails(ctx, workspace.id);
315
332
  return true;
316
333
  }
317
334
  const targetIndex = workspaces.findIndex((w) => w.id === draft.workspaceId);
318
335
  if (targetIndex === -1) {
319
336
  state.workspaceDraft = null;
320
- await ctx.reply('Workspace không còn tồn tại.');
337
+ await ctx.reply(t('bot.workspaces.notExists'));
321
338
  return true;
322
339
  }
323
340
  if (draft.mode === 'edit_name') {
324
341
  workspaces[targetIndex] = { ...workspaces[targetIndex], name: text };
325
342
  deps.saveWorkspaces(workspaces);
326
343
  state.workspaceDraft = null;
327
- await ctx.reply('✓ Đã cập nhật tên workspace.');
344
+ await ctx.reply(t('bot.workspaces.updatedName'));
328
345
  await showWorkspaceDetails(ctx, workspaces[targetIndex].id);
329
346
  return true;
330
347
  }
@@ -332,7 +349,7 @@ function createBotRuntime(deps) {
332
349
  workspaces[targetIndex] = { ...workspaces[targetIndex], path: text };
333
350
  deps.saveWorkspaces(workspaces);
334
351
  state.workspaceDraft = null;
335
- await ctx.reply('✓ Đã cập nhật đường dẫn workspace.');
352
+ await ctx.reply(t('bot.workspaces.updatedPath'));
336
353
  await showWorkspaceDetails(ctx, workspaces[targetIndex].id);
337
354
  return true;
338
355
  }
@@ -350,10 +367,11 @@ function createBotRuntime(deps) {
350
367
  const field = draft.field;
351
368
  const config = deps.getConfig();
352
369
  const isNumeric = ['outputInterval', 'maxOutputLines', 'maxRenderLines'].includes(field);
370
+ const t = getT();
353
371
  if (isNumeric) {
354
372
  const parsed = parseInt(text, 10);
355
373
  if (isNaN(parsed) || !Number.isFinite(parsed)) {
356
- await ctx.reply(`❌ Giá trị nhập vào không hợp lệ. Vui lòng nhập một số nguyên hợp lệ cho field "${field}".`);
374
+ await ctx.reply(t('bot.config.invalidValue', { field }));
357
375
  return true;
358
376
  }
359
377
  const nextConfig = { ...config, [field]: parsed };
@@ -368,7 +386,7 @@ function createBotRuntime(deps) {
368
386
  await deps.saveConfig(nextConfig);
369
387
  }
370
388
  state.configFieldDraft = null;
371
- await ctx.reply(`✓ Đã cập nhật cấu hình cho "${field}".`);
389
+ await ctx.reply(t('bot.config.updated', { field }));
372
390
  await showConfigMenu(ctx);
373
391
  return true;
374
392
  }
@@ -378,7 +396,8 @@ function createBotRuntime(deps) {
378
396
  const username = normalizeUsername(ctx.from?.username);
379
397
  const currentAllowedUsername = normalizeUsername(process.env.ADMIN_USERNAME || process.env.TELEGRAM_USERNAME || '');
380
398
  if (currentAllowedUsername && username !== currentAllowedUsername) {
381
- await ctx.reply('Bot này chỉ dành cho tài khoản Telegram đã cấu hình.');
399
+ const t = getT();
400
+ await ctx.reply(t('bot.general.authError'));
382
401
  return;
383
402
  }
384
403
  await next();
@@ -398,22 +417,24 @@ function createBotRuntime(deps) {
398
417
  // /send <lệnh> — gửi raw text tới session đang chạy
399
418
  bot.command('send', async (ctx) => {
400
419
  const rawText = ctx.message?.text ?? '';
420
+ const t = getT();
401
421
  // Bỏ phần "/send " ở đầu
402
422
  const payload = rawText.replace(/^\/send\s*/i, '').trim();
403
423
  if (!payload) {
404
- await ctx.reply('Cách dùng: /send <lệnh cần gửi>');
424
+ await ctx.reply(t('bot.general.sendUsage'));
405
425
  return;
406
426
  }
407
427
  const session = deps.getActiveSession();
408
428
  if (!session || session.status !== 'running') {
409
- await ctx.reply('Không có session đang chạy.');
429
+ await ctx.reply(t('bot.general.noActiveSession'));
410
430
  return;
411
431
  }
412
432
  deps.sendInput(session.autoEnter ? `${payload}\r` : payload);
413
- await ctx.reply('✓ Đã gửi lệnh');
433
+ await ctx.reply(t('bot.general.sentCommand'));
414
434
  });
415
435
  bot.on('message:text', async (ctx) => {
416
436
  const text = ctx.message.text;
437
+ const t = getT();
417
438
  // Bỏ qua các lệnh bắt đầu bằng /
418
439
  if (text.startsWith('/'))
419
440
  return;
@@ -425,10 +446,10 @@ function createBotRuntime(deps) {
425
446
  if (session?.status === 'running' && session.inputMode) {
426
447
  const payload = session.autoEnter ? `${text}\r` : text;
427
448
  deps.sendInput(payload);
428
- await ctx.reply('✓ Đã gửi lệnh');
449
+ await ctx.reply(t('bot.general.sentCommand'));
429
450
  return;
430
451
  }
431
- await ctx.reply('Dùng /start để mở menu.');
452
+ await ctx.reply(t('bot.general.defaultMessage'));
432
453
  });
433
454
  bot.callbackQuery('main_menu', async (ctx) => {
434
455
  await safeAnswerCallback(ctx);
@@ -468,7 +489,8 @@ function createBotRuntime(deps) {
468
489
  if (!chatId)
469
490
  return;
470
491
  getChatState(chatId).configFieldDraft = { field };
471
- await ctx.reply(`Nhập giá trị mới cho field "${field}":`);
492
+ const t = getT();
493
+ await ctx.reply(t('bot.config.enterValue', { field }));
472
494
  });
473
495
  bot.callbackQuery('help_view', async (ctx) => {
474
496
  await safeAnswerCallback(ctx);
@@ -484,15 +506,18 @@ function createBotRuntime(deps) {
484
506
  });
485
507
  bot.callbackQuery('workspace_action:add', async (ctx) => {
486
508
  await safeAnswerCallback(ctx);
487
- await beginWorkspaceDraft(ctx, { mode: 'add_name' }, 'Nhập tên workspace mới:');
509
+ const t = getT();
510
+ await beginWorkspaceDraft(ctx, { mode: 'add_name' }, t('bot.workspaces.addNamePrompt'));
488
511
  });
489
512
  bot.callbackQuery(/^workspace_action:edit_name:(.+)$/, async (ctx) => {
490
513
  await safeAnswerCallback(ctx);
491
- await beginWorkspaceDraft(ctx, { mode: 'edit_name', workspaceId: ctx.match[1] }, 'Nhập tên workspace mới:');
514
+ const t = getT();
515
+ await beginWorkspaceDraft(ctx, { mode: 'edit_name', workspaceId: ctx.match[1] }, t('bot.workspaces.addNamePrompt'));
492
516
  });
493
517
  bot.callbackQuery(/^workspace_action:edit_path:(.+)$/, async (ctx) => {
494
518
  await safeAnswerCallback(ctx);
495
- await beginWorkspaceDraft(ctx, { mode: 'edit_path', workspaceId: ctx.match[1] }, 'Nhập đường dẫn workspace mới:');
519
+ const t = getT();
520
+ await beginWorkspaceDraft(ctx, { mode: 'edit_path', workspaceId: ctx.match[1] }, t('bot.workspaces.addPathPrompt'));
496
521
  });
497
522
  bot.callbackQuery(/^workspace_action:delete:(.+)$/, async (ctx) => {
498
523
  await safeAnswerCallback(ctx);
@@ -504,17 +529,18 @@ function createBotRuntime(deps) {
504
529
  await safeAnswerCallback(ctx);
505
530
  const workspaceId = ctx.match[1];
506
531
  const preset = ctx.match[2];
532
+ const t = getT();
507
533
  if (!SUPPORTED_PRESETS.includes(preset)) {
508
- await ctx.reply(`Preset không hỗ trợ: ${preset}`);
534
+ await ctx.reply(t('bot.sessionControls.presetNotSupported', { preset }));
509
535
  return;
510
536
  }
511
537
  if (deps.getActiveSession()?.status === 'running') {
512
- await ctx.reply('Đã có session đang chạy. Dừng session hiện tại trước.');
538
+ await ctx.reply(t('bot.sessionControls.runningSessionExists'));
513
539
  return;
514
540
  }
515
541
  const workspace = getWorkspaceById(workspaceId);
516
542
  if (!workspace) {
517
- await ctx.reply('Workspace không tìm thấy.');
543
+ await ctx.reply(t('bot.workspaces.notFound'));
518
544
  return;
519
545
  }
520
546
  try {
@@ -527,7 +553,7 @@ function createBotRuntime(deps) {
527
553
  preset,
528
554
  error: error instanceof Error ? error.message : String(error)
529
555
  });
530
- await ctx.reply(`Lỗi khi khởi chạy session: ${error instanceof Error ? error.message : String(error)}`);
556
+ await ctx.reply(t('bot.sessionControls.startError', { error: error instanceof Error ? error.message : String(error) }));
531
557
  }
532
558
  });
533
559
  bot.callbackQuery('sessions_list', async (ctx) => {
@@ -543,8 +569,9 @@ function createBotRuntime(deps) {
543
569
  const session = deps.getActiveSession();
544
570
  const sessionId = ctx.match[1];
545
571
  const action = ctx.match[2];
572
+ const t = getT();
546
573
  if (!session || session.sessionId !== sessionId || session.status !== 'running') {
547
- await ctx.reply('Session không đang chạy.');
574
+ await ctx.reply(t('bot.sessionControls.notRunning'));
548
575
  return;
549
576
  }
550
577
  switch (action) {
@@ -567,7 +594,7 @@ function createBotRuntime(deps) {
567
594
  case 'refresh':
568
595
  break;
569
596
  default:
570
- await ctx.reply(`Hành động không hỗ trợ: ${action}`);
597
+ await ctx.reply(t('bot.sessionControls.unsupportedAction', { action }));
571
598
  return;
572
599
  }
573
600
  await showSessionDetails(ctx, sessionId);
package/dist/i18n.js CHANGED
@@ -25,7 +25,78 @@ const MESSAGES = {
25
25
  'settings.saved': 'Settings saved.',
26
26
  'startup.disabled': 'Disabled',
27
27
  'startup.background': 'Background',
28
- 'startup.openUi': 'Open UI'
28
+ 'startup.openUi': 'Open UI',
29
+ // Bot English
30
+ 'bot.menu.workspaces': '📁 Workspaces',
31
+ 'bot.menu.session': '⚡ Session',
32
+ 'bot.menu.config': '⚙️ Settings',
33
+ 'bot.menu.help': 'ℹ️ Help',
34
+ 'bot.menu.activeSessionNone': '⚡ Session: none',
35
+ 'bot.menu.activeSessionRunning': '⚡ Session: {preset} | {cwd}',
36
+ 'bot.help.title': '📖 Available Commands',
37
+ 'bot.help.start': '/start — Open main menu',
38
+ 'bot.help.status': '/status — Runtime status',
39
+ 'bot.help.help': '/help — Help text',
40
+ 'bot.help.config': '/config — System configuration',
41
+ 'bot.help.send': '/send <command> — Send raw input directly to the active session',
42
+ 'bot.help.inputModeNotice': 'When Input Mode is ON, any normal text message you send (without a leading /) will be fed directly into your shell.',
43
+ 'bot.help.back': '⬅️ Back',
44
+ 'bot.status.title': '📊 Runtime Status',
45
+ 'bot.status.user': 'User',
46
+ 'bot.status.unlimited': 'unlimited',
47
+ 'bot.status.workspaces': 'Workspaces',
48
+ 'bot.status.session': 'Session',
49
+ 'bot.status.running': 'running',
50
+ 'bot.status.none': 'none',
51
+ 'bot.status.preset': 'Preset',
52
+ 'bot.status.directory': 'Directory',
53
+ 'bot.config.title': '⚙️ nonstop configuration',
54
+ 'bot.config.notConfigured': 'Not configured',
55
+ 'bot.config.languageLabel': 'Language',
56
+ 'bot.config.startupLabel': 'Startup Mode',
57
+ 'bot.config.updated': '✓ Config updated for "{field}".',
58
+ 'bot.config.enterValue': 'Enter new value for field "{field}":',
59
+ 'bot.config.invalidValue': '❌ Invalid value. Please enter a valid integer for field "{field}".',
60
+ 'bot.workspaces.title': '📁 Workspace List',
61
+ 'bot.workspaces.empty': 'No workspaces configured.',
62
+ 'bot.workspaces.add': '➕ Add workspace',
63
+ 'bot.workspaces.editName': '✏️ Edit name',
64
+ 'bot.workspaces.editPath': '🛠️ Edit path',
65
+ 'bot.workspaces.delete': '🗑️ Delete',
66
+ 'bot.workspaces.notFound': 'Workspace not found.',
67
+ 'bot.workspaces.detailsTitle': '📁 Workspace Details',
68
+ 'bot.workspaces.detailsName': 'Name',
69
+ 'bot.workspaces.detailsPath': 'Path',
70
+ 'bot.workspaces.addNamePrompt': 'Enter new workspace name:',
71
+ 'bot.workspaces.addPathPrompt': 'Enter workspace path:',
72
+ 'bot.workspaces.added': '✓ Added workspace "{name}".',
73
+ 'bot.workspaces.notExists': 'Workspace no longer exists.',
74
+ 'bot.workspaces.updatedName': '✓ Updated workspace name.',
75
+ 'bot.workspaces.updatedPath': '✓ Updated workspace path.',
76
+ 'bot.sessions.title': '⚡ Session',
77
+ 'bot.sessions.empty': 'No running session.',
78
+ 'bot.sessions.control': '🎮 Control',
79
+ 'bot.sessionDetails.title': '🎮 Session Control',
80
+ 'bot.sessionDetails.notRunning': 'Session is not running.',
81
+ 'bot.sessionDetails.id': 'ID',
82
+ 'bot.sessionDetails.preset': 'Preset',
83
+ 'bot.sessionDetails.status': 'Status',
84
+ 'bot.sessionDetails.directory': 'Directory',
85
+ 'bot.sessionDetails.inputMode': 'Input mode',
86
+ 'bot.sessionDetails.autoEnter': 'Auto enter',
87
+ 'bot.sessionDetails.on': 'ON',
88
+ 'bot.sessionDetails.off': 'OFF',
89
+ 'bot.sessionControls.presetNotSupported': 'Preset not supported: {preset}',
90
+ 'bot.sessionControls.runningSessionExists': 'There is already a running session. Stop the current session first.',
91
+ 'bot.sessionControls.startError': 'Error starting session: {error}',
92
+ 'bot.sessionControls.notRunning': 'Session is not running.',
93
+ 'bot.sessionControls.unsupportedAction': 'Unsupported action: {action}',
94
+ 'bot.general.back': '⬅️ Back',
95
+ 'bot.general.authError': 'This bot is only for the configured Telegram account.',
96
+ 'bot.general.sendUsage': 'Usage: /send <command to send>',
97
+ 'bot.general.noActiveSession': 'No active session running.',
98
+ 'bot.general.sentCommand': '✓ Command sent',
99
+ 'bot.general.defaultMessage': 'Use /start to open the menu.'
29
100
  },
30
101
  vi: {
31
102
  'wizard.title': 'Thiết lập nonstop',
@@ -50,9 +121,88 @@ const MESSAGES = {
50
121
  'settings.saved': 'Đã lưu cấu hình.',
51
122
  'startup.disabled': 'Tắt',
52
123
  'startup.background': 'Chạy nền',
53
- 'startup.openUi': 'Mở giao diện'
124
+ 'startup.openUi': 'Mở giao diện',
125
+ // Bot Vietnamese
126
+ 'bot.menu.workspaces': '📁 Workspaces',
127
+ 'bot.menu.session': '⚡ Session',
128
+ 'bot.menu.config': '⚙️ Cấu hình',
129
+ 'bot.menu.help': 'ℹ️ Trợ giúp',
130
+ 'bot.menu.activeSessionNone': '⚡ Session: không có',
131
+ 'bot.menu.activeSessionRunning': '⚡ Session: {preset} | {cwd}',
132
+ 'bot.help.title': '📖 Lệnh có sẵn',
133
+ 'bot.help.start': '/start — Mở menu chính',
134
+ 'bot.help.status': '/status — Trạng thái runtime',
135
+ 'bot.help.help': '/help — Trợ giúp',
136
+ 'bot.help.config': '/config — Cấu hình hệ thống',
137
+ 'bot.help.send': '/send <lệnh> — Gửi lệnh thô tới session',
138
+ 'bot.help.inputModeNotice': 'Khi input mode BẬT, tin nhắn thường sẽ được gửi thẳng vào session.',
139
+ 'bot.help.back': '⬅️ Quay lại',
140
+ 'bot.status.title': '📊 Trạng thái Runtime',
141
+ 'bot.status.user': 'Người dùng',
142
+ 'bot.status.unlimited': 'không giới hạn',
143
+ 'bot.status.workspaces': 'Workspaces',
144
+ 'bot.status.session': 'Session',
145
+ 'bot.status.running': 'đang chạy',
146
+ 'bot.status.none': 'không có',
147
+ 'bot.status.preset': 'Preset',
148
+ 'bot.status.directory': 'Thư mục',
149
+ 'bot.config.title': '⚙️ Cấu hình nonstop',
150
+ 'bot.config.notConfigured': 'Chưa cấu hình',
151
+ 'bot.config.languageLabel': 'Ngôn ngữ',
152
+ 'bot.config.startupLabel': 'Chế độ khởi động',
153
+ 'bot.config.updated': '✓ Đã cập nhật cấu hình cho "{field}".',
154
+ 'bot.config.enterValue': 'Nhập giá trị mới cho field "{field}":',
155
+ 'bot.config.invalidValue': '❌ Giá trị nhập vào không hợp lệ. Vui lòng nhập một số nguyên hợp lệ cho field "{field}".',
156
+ 'bot.workspaces.title': '📁 Danh sách Workspace',
157
+ 'bot.workspaces.empty': 'Chưa có workspace nào.',
158
+ 'bot.workspaces.add': '➕ Thêm workspace',
159
+ 'bot.workspaces.editName': '✏️ Sửa tên',
160
+ 'bot.workspaces.editPath': '🛠️ Sửa đường dẫn',
161
+ 'bot.workspaces.delete': '🗑️ Xóa',
162
+ 'bot.workspaces.notFound': 'Workspace không tìm thấy.',
163
+ 'bot.workspaces.detailsTitle': '📁 Chi tiết Workspace',
164
+ 'bot.workspaces.detailsName': 'Tên',
165
+ 'bot.workspaces.detailsPath': 'Đường dẫn',
166
+ 'bot.workspaces.addNamePrompt': 'Nhập tên workspace mới:',
167
+ 'bot.workspaces.addPathPrompt': 'Nhập đường dẫn workspace:',
168
+ 'bot.workspaces.added': '✓ Đã thêm workspace "{name}".',
169
+ 'bot.workspaces.notExists': 'Workspace không còn tồn tại.',
170
+ 'bot.workspaces.updatedName': '✓ Đã cập nhật tên workspace.',
171
+ 'bot.workspaces.updatedPath': '✓ Đã cập nhật đường dẫn workspace.',
172
+ 'bot.sessions.title': '⚡ Session',
173
+ 'bot.sessions.empty': 'Không có session đang chạy.',
174
+ 'bot.sessions.control': '🎮 Điều khiển',
175
+ 'bot.sessionDetails.title': '🎮 Điều khiển Session',
176
+ 'bot.sessionDetails.notRunning': 'Session không đang chạy.',
177
+ 'bot.sessionDetails.id': 'ID',
178
+ 'bot.sessionDetails.preset': 'Preset',
179
+ 'bot.sessionDetails.status': 'Trạng thái',
180
+ 'bot.sessionDetails.directory': 'Thư mục',
181
+ 'bot.sessionDetails.inputMode': 'Input mode',
182
+ 'bot.sessionDetails.autoEnter': 'Auto enter',
183
+ 'bot.sessionDetails.on': 'BẬT',
184
+ 'bot.sessionDetails.off': 'TẮT',
185
+ 'bot.sessionControls.presetNotSupported': 'Preset không hỗ trợ: {preset}',
186
+ 'bot.sessionControls.runningSessionExists': 'Đã có session đang chạy. Dừng session hiện tại trước.',
187
+ 'bot.sessionControls.startError': 'Lỗi khi khởi chạy session: {error}',
188
+ 'bot.sessionControls.notRunning': 'Session không đang chạy.',
189
+ 'bot.sessionControls.unsupportedAction': 'Hành động không hỗ trợ: {action}',
190
+ 'bot.general.back': '⬅️ Quay lại',
191
+ 'bot.general.authError': 'Bot này chỉ dành cho tài khoản Telegram đã cấu hình.',
192
+ 'bot.general.sendUsage': 'Cách dùng: /send <lệnh cần gửi>',
193
+ 'bot.general.noActiveSession': 'Không có session đang chạy.',
194
+ 'bot.general.sentCommand': '✓ Đã gửi lệnh',
195
+ 'bot.general.defaultMessage': 'Dùng /start để mở menu.'
54
196
  }
55
197
  };
56
198
  function createTranslator(language) {
57
- return (key) => MESSAGES[language][key] || MESSAGES.en[key];
199
+ return (key, params) => {
200
+ let msg = MESSAGES[language][key] || MESSAGES.en[key];
201
+ if (params) {
202
+ for (const [k, v] of Object.entries(params)) {
203
+ msg = msg.replace(new RegExp(`{${k}}`, 'g'), String(v));
204
+ }
205
+ }
206
+ return msg;
207
+ };
58
208
  }
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.getRuntimeStatus = getRuntimeStatus;
37
+ exports.getEntryScriptPath = getEntryScriptPath;
37
38
  exports.startBackgroundRuntime = startBackgroundRuntime;
38
39
  exports.stopBackgroundRuntime = stopBackgroundRuntime;
39
40
  const child_process_1 = require("child_process");
@@ -50,11 +51,25 @@ function getRuntimeStatus() {
50
51
  }
51
52
  return { running: true, snapshot };
52
53
  }
53
- function startBackgroundRuntime() {
54
- const entryScriptPath = path.join(process.cwd(), 'dist', 'index.js');
55
- if (!fs.existsSync(entryScriptPath)) {
56
- throw new Error('dist/index.js not found. Run "npm run build" first.');
54
+ function getEntryScriptPath() {
55
+ // Try relative to __dirname (compiled location inside dist/)
56
+ const prodPath = path.join(__dirname, 'index.js');
57
+ if (fs.existsSync(prodPath)) {
58
+ return prodPath;
59
+ }
60
+ // Try relative to process.cwd() (local dev environment)
61
+ const localDevPath = path.join(process.cwd(), 'dist', 'index.js');
62
+ if (fs.existsSync(localDevPath)) {
63
+ return localDevPath;
64
+ }
65
+ // Fallback to process.argv[1]
66
+ if (process.argv[1] && process.argv[1].endsWith('.js') && fs.existsSync(process.argv[1])) {
67
+ return process.argv[1];
57
68
  }
69
+ throw new Error('dist/index.js not found. Please ensure the project is built.');
70
+ }
71
+ function startBackgroundRuntime() {
72
+ const entryScriptPath = getEntryScriptPath();
58
73
  const child = (0, child_process_1.spawn)(process.execPath, [entryScriptPath, '--background'], {
59
74
  cwd: process.cwd(),
60
75
  detached: true,
package/dist/runtime.js CHANGED
@@ -373,7 +373,8 @@ class NonstopRuntime {
373
373
  sessionId: session.sessionId,
374
374
  snapshot: finalText,
375
375
  inputMode: session.inputMode,
376
- autoEnter: session.autoEnter
376
+ autoEnter: session.autoEnter,
377
+ language: this.config.language
377
378
  });
378
379
  if (!this.onSessionOutputPush) {
379
380
  session.lastSentFinalText = finalText;
@@ -4,18 +4,23 @@ exports.buildSessionActionMarkup = buildSessionActionMarkup;
4
4
  function buildSessionActionMarkup(options) {
5
5
  const inputMode = options.inputMode ?? true;
6
6
  const autoEnter = options.autoEnter ?? true;
7
+ const isVi = options.language === 'vi';
7
8
  const rows = [
8
9
  [
9
10
  {
10
- text: inputMode ? '⌨️ Input OFF' : '⌨️ Input ON',
11
+ text: inputMode
12
+ ? (isVi ? '⌨️ Tắt Input' : '⌨️ Input OFF')
13
+ : (isVi ? '⌨️ Bật Input' : '⌨️ Input ON'),
11
14
  callback_data: `session_cmd:${options.sessionId}:toggle_input`
12
15
  },
13
16
  {
14
- text: autoEnter ? '⏎ AutoEnter OFF' : '⏎ AutoEnter ON',
17
+ text: autoEnter
18
+ ? (isVi ? '⏎ Tắt AutoEnter' : '⏎ AutoEnter OFF')
19
+ : (isVi ? '⏎ Bật AutoEnter' : '⏎ Bật AutoEnter ON'),
15
20
  callback_data: `session_cmd:${options.sessionId}:toggle_enter`
16
21
  },
17
22
  {
18
- text: '🔄 Refresh',
23
+ text: isVi ? '🔄 Tải lại' : '🔄 Refresh',
19
24
  callback_data: `session_cmd:${options.sessionId}:refresh`
20
25
  }
21
26
  ],
@@ -25,11 +30,11 @@ function buildSessionActionMarkup(options) {
25
30
  callback_data: `session_cmd:${options.sessionId}:send_escape`
26
31
  },
27
32
  {
28
- text: '⬆️ Up',
33
+ text: isVi ? '⬆️ Lên' : '⬆️ Up',
29
34
  callback_data: `session_cmd:${options.sessionId}:send_up`
30
35
  },
31
36
  {
32
- text: '⬇️ Down',
37
+ text: isVi ? '⬇️ Xuống' : '⬇️ Down',
33
38
  callback_data: `session_cmd:${options.sessionId}:send_down`
34
39
  }
35
40
  ],
@@ -39,7 +44,7 @@ function buildSessionActionMarkup(options) {
39
44
  callback_data: `session_cmd:${options.sessionId}:send_enter`
40
45
  },
41
46
  {
42
- text: '🛑 Stop',
47
+ text: isVi ? '🛑 Dừng' : '🛑 Stop',
43
48
  callback_data: `session_cmd:${options.sessionId}:stop`
44
49
  }
45
50
  ]
@@ -47,7 +52,7 @@ function buildSessionActionMarkup(options) {
47
52
  if (options.includeBackButton) {
48
53
  rows.push([
49
54
  {
50
- text: '⬅️ Back',
55
+ text: isVi ? '⬅️ Quay lại' : '⬅️ Back',
51
56
  callback_data: 'sessions_list'
52
57
  }
53
58
  ]);
@@ -9,7 +9,8 @@ function buildSessionOutputMessages(options) {
9
9
  sessionId: options.sessionId,
10
10
  inputMode: options.inputMode,
11
11
  autoEnter: options.autoEnter,
12
- includeBackButton: false
12
+ includeBackButton: false,
13
+ language: options.language
13
14
  });
14
15
  return chunkTelegramCodeBlocks(options.snapshot, TELEGRAM_MESSAGE_LIMIT).map((chunk) => ({
15
16
  text: `\`\`\`\n${chunk}\n\`\`\``,
package/dist/ui.js CHANGED
@@ -67,12 +67,22 @@ function infoRow(label, value, valueColor = (s) => s) {
67
67
  function separator() {
68
68
  return chalk_1.default.gray(' ' + '─'.repeat(44));
69
69
  }
70
- async function pause(rl) {
71
- await rl.question(`\n${chalk_1.default.gray('Nhấn Enter để tiếp tục...')}`);
70
+ async function askQuestion(query) {
71
+ const rl = (0, promises_1.createInterface)({ input: node_process_1.stdin, output: node_process_1.stdout });
72
+ try {
73
+ return await rl.question(query);
74
+ }
75
+ finally {
76
+ rl.close();
77
+ process.stdin.resume();
78
+ }
79
+ }
80
+ async function pause() {
81
+ await askQuestion(`\n${chalk_1.default.gray('Nhấn Enter để tiếp tục...')}`);
72
82
  }
73
- async function askWithDefault(rl, label, currentValue) {
83
+ async function askWithDefault(label, currentValue) {
74
84
  const prompt = `${chalk_1.default.bold(label)}${currentValue ? chalk_1.default.gray(` [${currentValue}]`) : ''}: `;
75
- const answer = await rl.question(prompt);
85
+ const answer = await askQuestion(prompt);
76
86
  return answer.trim() || currentValue;
77
87
  }
78
88
  async function runSelectionMenu(headerRenderer, options, initialIndex = 0) {
@@ -172,15 +182,9 @@ async function launchControlCenter() {
172
182
  let config = (0, config_js_1.loadConfigFromDisk)();
173
183
  const isTTY = process.stdin.isTTY;
174
184
  const wasRaw = process.stdin.isRaw;
175
- let rl;
176
185
  try {
177
186
  if ((0, config_js_1.getMissingConfigFields)(config).length > 0) {
178
- const result = await runSetupWizard(config);
179
- config = result.config;
180
- rl = result.rl;
181
- }
182
- else {
183
- rl = (0, promises_1.createInterface)({ input: node_process_1.stdin, output: node_process_1.stdout });
187
+ config = await runSetupWizard(config);
184
188
  }
185
189
  let lastSelection = 0;
186
190
  while (true) {
@@ -200,47 +204,39 @@ async function launchControlCenter() {
200
204
  { label: t('menu.logs'), value: 'logs' },
201
205
  { label: t('menu.exit'), value: 'exit' }
202
206
  ];
203
- rl.pause();
204
207
  const choice = await runSelectionMenu(() => renderDashboardHeader(config, (0, runtime_manager_js_1.getRuntimeStatus)().snapshot), options, lastSelection);
205
- rl.resume();
206
208
  lastSelection = options.findIndex(opt => opt.value === choice);
207
209
  if (lastSelection < 0)
208
210
  lastSelection = 0;
209
211
  if (choice === 'exit')
210
212
  break;
211
213
  if (choice === 'toggle') {
212
- await handleToggleRuntime(config, rl);
214
+ await handleToggleRuntime(config);
213
215
  continue;
214
216
  }
215
217
  if (choice === 'settings') {
216
- config = await editConfig(config, rl);
218
+ config = await editConfig(config);
217
219
  continue;
218
220
  }
219
221
  if (choice === 'workspaces') {
220
- await manageWorkspaces(rl, config.language);
222
+ await manageWorkspaces(config.language);
221
223
  continue;
222
224
  }
223
225
  if (choice === 'startup') {
224
- config = await configureStartup(config, rl);
226
+ config = await configureStartup(config);
225
227
  continue;
226
228
  }
227
229
  if (choice === 'language') {
228
- config = await switchLanguage(config, rl);
230
+ config = await switchLanguage(config);
229
231
  continue;
230
232
  }
231
233
  if (choice === 'logs') {
232
- await showRecentLogs(rl);
234
+ await showRecentLogs();
233
235
  continue;
234
236
  }
235
237
  }
236
238
  }
237
239
  finally {
238
- if (rl) {
239
- try {
240
- rl.close();
241
- }
242
- catch { /* ignore */ }
243
- }
244
240
  process.stdout.write('\u001b[?25h');
245
241
  if (isTTY) {
246
242
  try {
@@ -261,49 +257,37 @@ async function runSetupWizard(currentConfig) {
261
257
  { label: 'English (en)', value: 'en' }
262
258
  ], currentConfig.language === 'en' ? 1 : 0);
263
259
  const t = (0, i18n_js_1.createTranslator)(language);
264
- const rl = (0, promises_1.createInterface)({ input: node_process_1.stdin, output: node_process_1.stdout });
265
- try {
266
- clearScreen();
267
- console.log(titleBox(t('wizard.title')));
268
- console.log('');
269
- const telegramBotToken = await askWithDefault(rl, t('wizard.token'), currentConfig.telegramBotToken);
270
- const adminUsername = await askWithDefault(rl, t('wizard.admin'), currentConfig.adminUsername);
271
- const clientName = await askWithDefault(rl, t('wizard.clientName'), currentConfig.clientName);
272
- rl.pause();
273
- const startupMode = await runSelectionMenu(() => {
274
- console.log(titleBox(t('wizard.title')));
275
- console.log(chalk_1.default.gray(` ${t('wizard.startupMode')}:`));
276
- }, [
277
- { label: `Tắt (disabled)`, value: 'disabled' },
278
- { label: `Chạy nền (background)`, value: 'background' },
279
- { label: `Mở giao diện (open-ui)`, value: 'open-ui' }
280
- ], 0);
281
- rl.resume();
282
- const nextConfig = {
283
- ...currentConfig,
284
- language,
285
- telegramBotToken,
286
- adminUsername,
287
- telegramUsername: adminUsername,
288
- clientName,
289
- startupMode
290
- };
291
- (0, config_js_1.saveConfigToDisk)(nextConfig);
292
- clearScreen();
260
+ clearScreen();
261
+ console.log(titleBox(t('wizard.title')));
262
+ console.log('');
263
+ const telegramBotToken = await askWithDefault(t('wizard.token'), currentConfig.telegramBotToken);
264
+ const adminUsername = await askWithDefault(t('wizard.admin'), currentConfig.adminUsername);
265
+ const clientName = await askWithDefault(t('wizard.clientName'), currentConfig.clientName);
266
+ const startupMode = await runSelectionMenu(() => {
293
267
  console.log(titleBox(t('wizard.title')));
294
- console.log(`\n${chalk_1.default.green(t('wizard.complete'))}`);
295
- await pause(rl);
296
- return { config: nextConfig, rl };
297
- }
298
- catch (error) {
299
- try {
300
- rl.close();
301
- }
302
- catch { /* ignore */ }
303
- throw error;
304
- }
268
+ console.log(chalk_1.default.gray(` ${t('wizard.startupMode')}:`));
269
+ }, [
270
+ { label: `Tắt (disabled)`, value: 'disabled' },
271
+ { label: `Chạy nền (background)`, value: 'background' },
272
+ { label: `Mở giao diện (open-ui)`, value: 'open-ui' }
273
+ ], 0);
274
+ const nextConfig = {
275
+ ...currentConfig,
276
+ language,
277
+ telegramBotToken,
278
+ adminUsername,
279
+ telegramUsername: adminUsername,
280
+ clientName,
281
+ startupMode
282
+ };
283
+ (0, config_js_1.saveConfigToDisk)(nextConfig);
284
+ clearScreen();
285
+ console.log(titleBox(t('wizard.title')));
286
+ console.log(`\n${chalk_1.default.green(t('wizard.complete'))}`);
287
+ await pause();
288
+ return nextConfig;
305
289
  }
306
- async function handleToggleRuntime(config, rl) {
290
+ async function handleToggleRuntime(config) {
307
291
  const status = (0, runtime_manager_js_1.getRuntimeStatus)();
308
292
  const targetState = !status.running;
309
293
  clearScreen();
@@ -326,31 +310,30 @@ async function handleToggleRuntime(config, rl) {
326
310
  }
327
311
  catch (error) {
328
312
  console.log(`\n${chalk_1.default.red(error instanceof Error ? error.message : String(error))}`);
329
- await pause(rl);
313
+ await pause();
330
314
  return;
331
315
  }
332
- // Không pause — quay lại menu ngay để trạng thái cập nhật tức thì
333
316
  }
334
- async function editConfig(config, rl) {
317
+ async function editConfig(config) {
335
318
  clearScreen();
336
319
  console.log(titleBox('Sửa cấu hình'));
337
320
  console.log('');
338
321
  const nextConfig = {
339
322
  ...config,
340
- telegramBotToken: await askWithDefault(rl, 'TELEGRAM_BOT_TOKEN', config.telegramBotToken),
341
- adminUsername: await askWithDefault(rl, 'ADMIN_USERNAME', config.adminUsername),
342
- clientName: await askWithDefault(rl, 'CLIENT_NAME', config.clientName),
343
- telegramUsername: await askWithDefault(rl, 'TELEGRAM_USERNAME', config.telegramUsername),
344
- codexCmd: await askWithDefault(rl, 'CODEX_CMD', config.codexCmd),
345
- antigravityCmd: await askWithDefault(rl, 'ANTIGRAVITY_CMD', config.antigravityCmd)
323
+ telegramBotToken: await askWithDefault('TELEGRAM_BOT_TOKEN', config.telegramBotToken),
324
+ adminUsername: await askWithDefault('ADMIN_USERNAME', config.adminUsername),
325
+ clientName: await askWithDefault('CLIENT_NAME', config.clientName),
326
+ telegramUsername: await askWithDefault('TELEGRAM_USERNAME', config.telegramUsername),
327
+ codexCmd: await askWithDefault('CODEX_CMD', config.codexCmd),
328
+ antigravityCmd: await askWithDefault('ANTIGRAVITY_CMD', config.antigravityCmd)
346
329
  };
347
330
  (0, config_js_1.saveConfigToDisk)(nextConfig);
348
331
  console.log(`\n${chalk_1.default.green('✓ Đã lưu cấu hình.')}`);
349
332
  console.log(chalk_1.default.gray('Khởi động lại runtime nền nếu đang chạy để áp dụng thay đổi.'));
350
- await pause(rl);
333
+ await pause();
351
334
  return nextConfig;
352
335
  }
353
- async function manageWorkspaces(rl, language) {
336
+ async function manageWorkspaces(language) {
354
337
  const isVi = language === 'vi';
355
338
  while (true) {
356
339
  const workspaces = (0, store_js_1.loadWorkspaces)();
@@ -362,23 +345,21 @@ async function manageWorkspaces(rl, language) {
362
345
  })),
363
346
  { label: isVi ? '← Quay lại menu chính' : '← Back', value: { type: 'back' } }
364
347
  ];
365
- rl.pause();
366
348
  const selection = await runSelectionMenu(() => {
367
349
  console.log(titleBox(isVi ? 'Quản lý Workspace' : 'Manage Workspaces'));
368
350
  console.log(chalk_1.default.gray(` ${isVi ? 'Chọn workspace hoặc thêm mới:' : 'Select workspace or add new:'}`));
369
351
  }, options);
370
- rl.resume();
371
352
  if (selection.type === 'back')
372
353
  return;
373
354
  if (selection.type === 'add') {
374
355
  clearScreen();
375
356
  console.log(titleBox(isVi ? 'Thêm workspace mới' : 'Add workspace'));
376
357
  console.log('');
377
- const name = (await rl.question(chalk_1.default.bold(isVi ? 'Tên workspace: ' : 'Workspace name: '))).trim();
378
- const rawPath = (await rl.question(chalk_1.default.bold(isVi ? 'Đường dẫn: ' : 'Path: '))).trim();
358
+ const name = (await askQuestion(chalk_1.default.bold(isVi ? 'Tên workspace: ' : 'Workspace name: '))).trim();
359
+ const rawPath = (await askQuestion(chalk_1.default.bold(isVi ? 'Đường dẫn: ' : 'Path: '))).trim();
379
360
  if (!rawPath) {
380
361
  console.log(chalk_1.default.red(isVi ? ' Đường dẫn không được để trống.' : ' Path cannot be empty.'));
381
- await pause(rl);
362
+ await pause();
382
363
  continue;
383
364
  }
384
365
  const resolvedPath = path.resolve(rawPath);
@@ -388,7 +369,7 @@ async function manageWorkspaces(rl, language) {
388
369
  workspaces.push({ id: (0, store_js_1.createWorkspaceId)(), name: name || 'Workspace', path: resolvedPath });
389
370
  (0, store_js_1.saveWorkspaces)(workspaces);
390
371
  console.log(chalk_1.default.green(`\n ✓ ${isVi ? 'Đã thêm workspace.' : 'Workspace added.'}`));
391
- await pause(rl);
372
+ await pause();
392
373
  continue;
393
374
  }
394
375
  if (selection.type === 'workspace' && typeof selection.index === 'number') {
@@ -410,15 +391,15 @@ async function manageWorkspaces(rl, language) {
410
391
  (0, store_js_1.saveWorkspaces)(workspaces);
411
392
  clearScreen();
412
393
  console.log(chalk_1.default.green(`\n ✓ ${isVi ? 'Đã xóa workspace.' : 'Workspace deleted.'}`));
413
- await pause(rl);
394
+ await pause();
414
395
  continue;
415
396
  }
416
397
  if (action === 'edit') {
417
398
  clearScreen();
418
399
  console.log(titleBox(isVi ? 'Sửa workspace' : 'Edit workspace'));
419
400
  console.log('');
420
- const newName = await askWithDefault(rl, isVi ? 'Tên mới' : 'New name', ws.name);
421
- const newPath = await askWithDefault(rl, isVi ? 'Đường dẫn mới' : 'New path', ws.path);
401
+ const newName = await askWithDefault(isVi ? 'Tên mới' : 'New name', ws.name);
402
+ const newPath = await askWithDefault(isVi ? 'Đường dẫn mới' : 'New path', ws.path);
422
403
  const resolvedPath = path.resolve(newPath.trim());
423
404
  if (!fs.existsSync(resolvedPath)) {
424
405
  console.log(chalk_1.default.yellow(` ⚠ ${isVi ? 'Đường dẫn không tồn tại.' : 'Path does not exist.'}`));
@@ -426,15 +407,14 @@ async function manageWorkspaces(rl, language) {
426
407
  workspaces[idx] = { ...ws, name: newName.trim() || ws.name, path: resolvedPath };
427
408
  (0, store_js_1.saveWorkspaces)(workspaces);
428
409
  console.log(chalk_1.default.green(`\n ✓ ${isVi ? 'Đã cập nhật workspace.' : 'Workspace updated.'}`));
429
- await pause(rl);
410
+ await pause();
430
411
  continue;
431
412
  }
432
413
  }
433
414
  }
434
415
  }
435
- async function configureStartup(config, rl) {
416
+ async function configureStartup(config) {
436
417
  const isVi = config.language === 'vi';
437
- rl.pause();
438
418
  const nextMode = await runSelectionMenu(() => {
439
419
  console.log(titleBox(isVi ? 'Cấu hình khởi động' : 'Configure startup'));
440
420
  console.log(chalk_1.default.gray(` ${isVi ? 'Chế độ hiện tại:' : 'Current mode:'} ${config.startupMode}`));
@@ -443,19 +423,17 @@ async function configureStartup(config, rl) {
443
423
  { label: isVi ? 'Chạy nền (background)' : 'Background', value: 'background' },
444
424
  { label: isVi ? 'Mở giao diện (open-ui)' : 'Open UI', value: 'open-ui' }
445
425
  ], ['disabled', 'background', 'open-ui'].indexOf(config.startupMode));
446
- rl.resume();
447
- const entryScriptPath = path.join(process.cwd(), 'dist', 'index.js');
426
+ const entryScriptPath = (0, runtime_manager_js_1.getEntryScriptPath)();
448
427
  const result = (0, startup_js_1.applyStartupMode)(nextMode, entryScriptPath, process.cwd());
449
428
  const nextConfig = { ...config, startupMode: nextMode };
450
429
  (0, config_js_1.saveConfigToDisk)(nextConfig);
451
430
  clearScreen();
452
431
  console.log(titleBox(isVi ? 'Cấu hình khởi động' : 'Configure startup'));
453
432
  console.log(`\n${chalk_1.default.green(result)}`);
454
- await pause(rl);
433
+ await pause();
455
434
  return nextConfig;
456
435
  }
457
- async function switchLanguage(config, rl) {
458
- rl.pause();
436
+ async function switchLanguage(config) {
459
437
  const language = await runSelectionMenu(() => {
460
438
  console.log(titleBox('Đổi ngôn ngữ / Switch language'));
461
439
  console.log(chalk_1.default.gray(` Hiện tại: ${config.language}`));
@@ -463,21 +441,20 @@ async function switchLanguage(config, rl) {
463
441
  { label: 'Tiếng Việt (vi)', value: 'vi' },
464
442
  { label: 'English (en)', value: 'en' }
465
443
  ], config.language === 'en' ? 1 : 0);
466
- rl.resume();
467
444
  const nextConfig = { ...config, language };
468
445
  (0, config_js_1.saveConfigToDisk)(nextConfig);
469
446
  return nextConfig;
470
447
  }
471
- async function showRecentLogs(rl) {
448
+ async function showRecentLogs() {
472
449
  clearScreen();
473
450
  console.log(titleBox('Nhật ký gần đây'));
474
451
  const logPath = path.join(process.cwd(), 'data', 'nonstop.log');
475
452
  if (!fs.existsSync(logPath)) {
476
453
  console.log(chalk_1.default.gray('\n Chưa có nhật ký.'));
477
- await pause(rl);
454
+ await pause();
478
455
  return;
479
456
  }
480
457
  const lines = fs.readFileSync(logPath, 'utf8').split(/\r?\n/).filter(Boolean).slice(-25);
481
458
  console.log('\n' + lines.map(l => chalk_1.default.gray(' ') + l).join('\n'));
482
- await pause(rl);
459
+ await pause();
483
460
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quangnv13/nonstop",
3
- "version": "1.0.4",
3
+ "version": "1.0.7",
4
4
  "preferGlobal": true,
5
5
  "publishConfig": {
6
6
  "access": "public"