@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 +1 -1
- package/dist/bot.js +103 -76
- package/dist/i18n.js +153 -3
- package/dist/runtime-manager.js +19 -4
- package/dist/runtime.js +2 -1
- package/dist/session-controls.js +12 -7
- package/dist/session-output.js +2 -1
- package/dist/ui.js +75 -98
- package/package.json +1 -1
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 **⚙️
|
|
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
|
-
?
|
|
120
|
-
: '
|
|
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('
|
|
126
|
-
.text('
|
|
129
|
+
.text(t('bot.menu.workspaces'), 'workspaces_list')
|
|
130
|
+
.text(t('bot.menu.session'), 'sessions_list')
|
|
127
131
|
.row()
|
|
128
|
-
.text('
|
|
129
|
-
.text('
|
|
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
|
-
'
|
|
141
|
+
t('bot.help.title'),
|
|
137
142
|
'',
|
|
138
|
-
'
|
|
139
|
-
'
|
|
140
|
-
'
|
|
141
|
-
'
|
|
142
|
-
'
|
|
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
|
-
'
|
|
145
|
-
].join('\n'), createKeyboard().text('
|
|
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
|
-
'
|
|
157
|
+
t('bot.status.title'),
|
|
152
158
|
'',
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
activeSession ?
|
|
157
|
-
activeSession ?
|
|
158
|
-
].filter(Boolean).join('\n'), createKeyboard().text('
|
|
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
|
-
'
|
|
171
|
+
t('bot.config.title'),
|
|
164
172
|
'',
|
|
165
|
-
`• Token: ${config.telegramBotToken ? '••••' + config.telegramBotToken.slice(-4) :
|
|
166
|
-
`• Admin Username: ${config.adminUsername ||
|
|
167
|
-
`• Client Name: ${config.clientName ||
|
|
168
|
-
`• Telegram Username: ${config.telegramUsername ||
|
|
169
|
-
`•
|
|
170
|
-
`•
|
|
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(
|
|
187
|
-
.text(
|
|
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('
|
|
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
|
|
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('
|
|
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('
|
|
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('
|
|
223
|
-
.text('
|
|
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('
|
|
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('
|
|
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, '
|
|
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, ['
|
|
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
|
|
257
|
+
const t = getT();
|
|
258
|
+
const lines = [t('bot.sessions.title'), ''];
|
|
247
259
|
if (!activeSession || activeSession.status !== 'running') {
|
|
248
|
-
lines.push('
|
|
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(
|
|
254
|
-
keyboard.text('
|
|
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('
|
|
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, '
|
|
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
|
-
'
|
|
288
|
+
t('bot.sessionDetails.title'),
|
|
273
289
|
'',
|
|
274
290
|
`ID: ${session.sessionId}`,
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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('
|
|
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(
|
|
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('
|
|
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('
|
|
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('
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
449
|
+
await ctx.reply(t('bot.general.sentCommand'));
|
|
429
450
|
return;
|
|
430
451
|
}
|
|
431
|
-
await ctx.reply('
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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('
|
|
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('
|
|
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(
|
|
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('
|
|
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(
|
|
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) =>
|
|
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
|
}
|
package/dist/runtime-manager.js
CHANGED
|
@@ -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
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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;
|
package/dist/session-controls.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
]);
|
package/dist/session-output.js
CHANGED
|
@@ -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
|
|
71
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
214
|
+
await handleToggleRuntime(config);
|
|
213
215
|
continue;
|
|
214
216
|
}
|
|
215
217
|
if (choice === 'settings') {
|
|
216
|
-
config = await editConfig(config
|
|
218
|
+
config = await editConfig(config);
|
|
217
219
|
continue;
|
|
218
220
|
}
|
|
219
221
|
if (choice === 'workspaces') {
|
|
220
|
-
await manageWorkspaces(
|
|
222
|
+
await manageWorkspaces(config.language);
|
|
221
223
|
continue;
|
|
222
224
|
}
|
|
223
225
|
if (choice === 'startup') {
|
|
224
|
-
config = await configureStartup(config
|
|
226
|
+
config = await configureStartup(config);
|
|
225
227
|
continue;
|
|
226
228
|
}
|
|
227
229
|
if (choice === 'language') {
|
|
228
|
-
config = await switchLanguage(config
|
|
230
|
+
config = await switchLanguage(config);
|
|
229
231
|
continue;
|
|
230
232
|
}
|
|
231
233
|
if (choice === 'logs') {
|
|
232
|
-
await showRecentLogs(
|
|
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
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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(
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
341
|
-
adminUsername: await askWithDefault(
|
|
342
|
-
clientName: await askWithDefault(
|
|
343
|
-
telegramUsername: await askWithDefault(
|
|
344
|
-
codexCmd: await askWithDefault(
|
|
345
|
-
antigravityCmd: await askWithDefault(
|
|
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(
|
|
333
|
+
await pause();
|
|
351
334
|
return nextConfig;
|
|
352
335
|
}
|
|
353
|
-
async function manageWorkspaces(
|
|
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
|
|
378
|
-
const rawPath = (await
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
421
|
-
const newPath = await askWithDefault(
|
|
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(
|
|
410
|
+
await pause();
|
|
430
411
|
continue;
|
|
431
412
|
}
|
|
432
413
|
}
|
|
433
414
|
}
|
|
434
415
|
}
|
|
435
|
-
async function configureStartup(config
|
|
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
|
-
|
|
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(
|
|
433
|
+
await pause();
|
|
455
434
|
return nextConfig;
|
|
456
435
|
}
|
|
457
|
-
async function switchLanguage(config
|
|
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(
|
|
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(
|
|
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(
|
|
459
|
+
await pause();
|
|
483
460
|
}
|