@quangnv13/nonstop 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +167 -0
- package/README.vi.md +167 -0
- package/dist/bot.js +605 -0
- package/dist/config.js +174 -0
- package/dist/i18n.js +58 -0
- package/dist/index.js +50 -0
- package/dist/logger.js +86 -0
- package/dist/prompt-detection.js +27 -0
- package/dist/runtime-manager.js +77 -0
- package/dist/runtime-state.js +78 -0
- package/dist/runtime.js +622 -0
- package/dist/session-controls.js +56 -0
- package/dist/session-delivery.js +6 -0
- package/dist/session-output.js +52 -0
- package/dist/startup.js +120 -0
- package/dist/store.js +114 -0
- package/dist/terminal.js +138 -0
- package/dist/types.js +2 -0
- package/dist/ui.js +473 -0
- package/images/nonstop.png +0 -0
- package/package.json +56 -0
package/dist/ui.js
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.launchControlCenter = launchControlCenter;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const readline = __importStar(require("node:readline"));
|
|
43
|
+
const promises_1 = require("node:readline/promises");
|
|
44
|
+
const node_process_1 = require("node:process");
|
|
45
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
46
|
+
const boxen_1 = __importDefault(require("boxen"));
|
|
47
|
+
const config_js_1 = require("./config.js");
|
|
48
|
+
const i18n_js_1 = require("./i18n.js");
|
|
49
|
+
const runtime_manager_js_1 = require("./runtime-manager.js");
|
|
50
|
+
const startup_js_1 = require("./startup.js");
|
|
51
|
+
const store_js_1 = require("./store.js");
|
|
52
|
+
function clearScreen() {
|
|
53
|
+
// \u001b[2J clears visible area, \u001b[3J clears scrollback buffer, \u001b[H moves cursor home
|
|
54
|
+
node_process_1.stdout.write('\u001b[2J\u001b[3J\u001b[H');
|
|
55
|
+
}
|
|
56
|
+
function titleBox(title) {
|
|
57
|
+
return (0, boxen_1.default)(chalk_1.default.bold.cyan(title), {
|
|
58
|
+
padding: { top: 0, bottom: 0, left: 2, right: 2 },
|
|
59
|
+
borderStyle: 'round',
|
|
60
|
+
borderColor: 'cyan',
|
|
61
|
+
textAlignment: 'center'
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function infoRow(label, value, valueColor = (s) => s) {
|
|
65
|
+
return ` ${chalk_1.default.gray(label.padEnd(12))} ${valueColor(value)}`;
|
|
66
|
+
}
|
|
67
|
+
function separator() {
|
|
68
|
+
return chalk_1.default.gray(' ' + '─'.repeat(44));
|
|
69
|
+
}
|
|
70
|
+
async function pause(rl) {
|
|
71
|
+
await rl.question(`\n${chalk_1.default.gray('Nhấn Enter để tiếp tục...')}`);
|
|
72
|
+
}
|
|
73
|
+
async function askWithDefault(rl, label, currentValue) {
|
|
74
|
+
const prompt = `${chalk_1.default.bold(label)}${currentValue ? chalk_1.default.gray(` [${currentValue}]`) : ''}: `;
|
|
75
|
+
const answer = await rl.question(prompt);
|
|
76
|
+
return answer.trim() || currentValue;
|
|
77
|
+
}
|
|
78
|
+
async function runSelectionMenu(headerRenderer, options, initialIndex = 0) {
|
|
79
|
+
let selectedIndex = Math.min(initialIndex, options.length - 1);
|
|
80
|
+
return new Promise((resolve, reject) => {
|
|
81
|
+
const isTTY = process.stdin.isTTY;
|
|
82
|
+
const wasRaw = process.stdin.isRaw;
|
|
83
|
+
const cleanup = () => {
|
|
84
|
+
process.stdin.removeListener('keypress', onKeypress);
|
|
85
|
+
process.stdout.write('\u001b[?25h');
|
|
86
|
+
if (isTTY) {
|
|
87
|
+
try {
|
|
88
|
+
process.stdin.setRawMode(wasRaw);
|
|
89
|
+
}
|
|
90
|
+
catch { /* ignore */ }
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
function render() {
|
|
94
|
+
clearScreen();
|
|
95
|
+
headerRenderer();
|
|
96
|
+
console.log('');
|
|
97
|
+
options.forEach((opt, idx) => {
|
|
98
|
+
if (idx === selectedIndex) {
|
|
99
|
+
console.log(` ${chalk_1.default.cyan('❯')} ${chalk_1.default.bold.cyan(opt.label)}`);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
console.log(` ${chalk_1.default.gray(opt.label)}`);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
console.log('');
|
|
106
|
+
console.log(chalk_1.default.gray(' ↑↓ di chuyển Enter xác nhận'));
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
readline.emitKeypressEvents(process.stdin);
|
|
110
|
+
if (isTTY)
|
|
111
|
+
process.stdin.setRawMode(true);
|
|
112
|
+
process.stdout.write('\u001b[?25l');
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
cleanup();
|
|
116
|
+
reject(err);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
render();
|
|
120
|
+
function onKeypress(_str, key) {
|
|
121
|
+
if (key?.ctrl && key.name === 'c') {
|
|
122
|
+
cleanup();
|
|
123
|
+
process.exit(0);
|
|
124
|
+
}
|
|
125
|
+
if (key?.name === 'up') {
|
|
126
|
+
selectedIndex = (selectedIndex - 1 + options.length) % options.length;
|
|
127
|
+
render();
|
|
128
|
+
}
|
|
129
|
+
else if (key?.name === 'down') {
|
|
130
|
+
selectedIndex = (selectedIndex + 1) % options.length;
|
|
131
|
+
render();
|
|
132
|
+
}
|
|
133
|
+
else if (key?.name === 'return' || key?.name === 'enter') {
|
|
134
|
+
cleanup();
|
|
135
|
+
resolve(options[selectedIndex].value);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
process.stdin.on('keypress', onKeypress);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
function renderDashboardHeader(config, snapshot) {
|
|
142
|
+
const t = (0, i18n_js_1.createTranslator)(config.language);
|
|
143
|
+
const isRunning = !!snapshot;
|
|
144
|
+
const runtimeLabel = isRunning ? chalk_1.default.bold.green(t('dashboard.running')) : chalk_1.default.bold.red(t('dashboard.stopped'));
|
|
145
|
+
const session = snapshot?.activeSession;
|
|
146
|
+
console.log(titleBox('nonstop client'));
|
|
147
|
+
console.log('');
|
|
148
|
+
console.log(infoRow('Trạng thái', isRunning ? `${runtimeLabel} ${chalk_1.default.gray(`(${snapshot.mode})`)}` : runtimeLabel));
|
|
149
|
+
console.log(infoRow('Client', config.clientName, chalk_1.default.white));
|
|
150
|
+
console.log(infoRow('Admin', config.adminUsername || '-', chalk_1.default.white));
|
|
151
|
+
console.log(infoRow('Ngôn ngữ', config.language === 'vi' ? 'Tiếng Việt' : 'English', chalk_1.default.white));
|
|
152
|
+
console.log(infoRow('Khởi động', config.startupMode, chalk_1.default.white));
|
|
153
|
+
if (snapshot?.startedAt) {
|
|
154
|
+
const dt = new Date(snapshot.startedAt);
|
|
155
|
+
console.log(infoRow('Bật lúc', dt.toLocaleTimeString('vi-VN'), chalk_1.default.white));
|
|
156
|
+
}
|
|
157
|
+
if (session) {
|
|
158
|
+
console.log(separator());
|
|
159
|
+
console.log(infoRow('Session', `${session.preset}`, chalk_1.default.yellow));
|
|
160
|
+
const shortCwd = session.cwd.length > 40 ? '...' + session.cwd.slice(-38) : session.cwd;
|
|
161
|
+
console.log(infoRow('Thư mục', shortCwd, chalk_1.default.gray));
|
|
162
|
+
}
|
|
163
|
+
if (snapshot?.lastError) {
|
|
164
|
+
console.log(separator());
|
|
165
|
+
console.log(infoRow('Lỗi', snapshot.lastError, chalk_1.default.red));
|
|
166
|
+
}
|
|
167
|
+
console.log('');
|
|
168
|
+
console.log(chalk_1.default.bold.blue(' ' + t('dashboard.menu')));
|
|
169
|
+
}
|
|
170
|
+
async function launchControlCenter() {
|
|
171
|
+
(0, config_js_1.ensureEnvExampleFile)();
|
|
172
|
+
let config = (0, config_js_1.loadConfigFromDisk)();
|
|
173
|
+
const isTTY = process.stdin.isTTY;
|
|
174
|
+
const wasRaw = process.stdin.isRaw;
|
|
175
|
+
try {
|
|
176
|
+
if ((0, config_js_1.getMissingConfigFields)(config).length > 0) {
|
|
177
|
+
config = await runSetupWizard(config);
|
|
178
|
+
}
|
|
179
|
+
const rl = (0, promises_1.createInterface)({ input: node_process_1.stdin, output: node_process_1.stdout });
|
|
180
|
+
try {
|
|
181
|
+
let lastSelection = 0;
|
|
182
|
+
while (true) {
|
|
183
|
+
config = (0, config_js_1.loadConfigFromDisk)();
|
|
184
|
+
const t = (0, i18n_js_1.createTranslator)(config.language);
|
|
185
|
+
const isRunning = (0, runtime_manager_js_1.getRuntimeStatus)().running;
|
|
186
|
+
const isVi = config.language === 'vi';
|
|
187
|
+
const toggleLabel = isRunning
|
|
188
|
+
? (isVi ? 'Tắt runtime nền' : 'Dừng background runtime')
|
|
189
|
+
: (isVi ? 'Bật runtime nền' : 'Chạy background runtime');
|
|
190
|
+
const options = [
|
|
191
|
+
{ label: toggleLabel, value: 'toggle' },
|
|
192
|
+
{ label: t('menu.settings'), value: 'settings' },
|
|
193
|
+
{ label: t('menu.workspaces'), value: 'workspaces' },
|
|
194
|
+
{ label: t('menu.startup'), value: 'startup' },
|
|
195
|
+
{ label: t('menu.language'), value: 'language' },
|
|
196
|
+
{ label: t('menu.logs'), value: 'logs' },
|
|
197
|
+
{ label: t('menu.exit'), value: 'exit' }
|
|
198
|
+
];
|
|
199
|
+
const choice = await runSelectionMenu(() => renderDashboardHeader(config, (0, runtime_manager_js_1.getRuntimeStatus)().snapshot), options, lastSelection);
|
|
200
|
+
lastSelection = options.findIndex(opt => opt.value === choice);
|
|
201
|
+
if (lastSelection < 0)
|
|
202
|
+
lastSelection = 0;
|
|
203
|
+
if (choice === 'exit')
|
|
204
|
+
break;
|
|
205
|
+
if (choice === 'toggle') {
|
|
206
|
+
await handleToggleRuntime(config, rl);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (choice === 'settings') {
|
|
210
|
+
config = await editConfig(config, rl);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (choice === 'workspaces') {
|
|
214
|
+
await manageWorkspaces(rl, config.language);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (choice === 'startup') {
|
|
218
|
+
config = await configureStartup(config, rl);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (choice === 'language') {
|
|
222
|
+
config = await switchLanguage(config, rl);
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (choice === 'logs') {
|
|
226
|
+
await showRecentLogs(rl);
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
finally {
|
|
232
|
+
rl.close();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
finally {
|
|
236
|
+
process.stdout.write('\u001b[?25h');
|
|
237
|
+
if (isTTY) {
|
|
238
|
+
try {
|
|
239
|
+
process.stdin.setRawMode(wasRaw);
|
|
240
|
+
}
|
|
241
|
+
catch { /* ignore */ }
|
|
242
|
+
}
|
|
243
|
+
clearScreen();
|
|
244
|
+
console.log(chalk_1.default.gray('Đã thoát nonstop client.'));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
async function runSetupWizard(currentConfig) {
|
|
248
|
+
const language = await runSelectionMenu(() => {
|
|
249
|
+
console.log(titleBox('Thiết lập nonstop'));
|
|
250
|
+
console.log(chalk_1.default.gray(' Chọn ngôn ngữ / Choose language:'));
|
|
251
|
+
}, [
|
|
252
|
+
{ label: 'Tiếng Việt (vi)', value: 'vi' },
|
|
253
|
+
{ label: 'English (en)', value: 'en' }
|
|
254
|
+
], currentConfig.language === 'en' ? 1 : 0);
|
|
255
|
+
const t = (0, i18n_js_1.createTranslator)(language);
|
|
256
|
+
const rl = (0, promises_1.createInterface)({ input: node_process_1.stdin, output: node_process_1.stdout });
|
|
257
|
+
try {
|
|
258
|
+
clearScreen();
|
|
259
|
+
console.log(titleBox(t('wizard.title')));
|
|
260
|
+
console.log('');
|
|
261
|
+
const telegramBotToken = await askWithDefault(rl, t('wizard.token'), currentConfig.telegramBotToken);
|
|
262
|
+
const adminUsername = await askWithDefault(rl, t('wizard.admin'), currentConfig.adminUsername);
|
|
263
|
+
const clientName = await askWithDefault(rl, t('wizard.clientName'), currentConfig.clientName);
|
|
264
|
+
rl.close();
|
|
265
|
+
const startupMode = await runSelectionMenu(() => {
|
|
266
|
+
console.log(titleBox(t('wizard.title')));
|
|
267
|
+
console.log(chalk_1.default.gray(` ${t('wizard.startupMode')}:`));
|
|
268
|
+
}, [
|
|
269
|
+
{ label: `Tắt (disabled)`, value: 'disabled' },
|
|
270
|
+
{ label: `Chạy nền (background)`, value: 'background' },
|
|
271
|
+
{ label: `Mở giao diện (open-ui)`, value: 'open-ui' }
|
|
272
|
+
], 0);
|
|
273
|
+
const nextConfig = {
|
|
274
|
+
...currentConfig,
|
|
275
|
+
language,
|
|
276
|
+
telegramBotToken,
|
|
277
|
+
adminUsername,
|
|
278
|
+
telegramUsername: adminUsername,
|
|
279
|
+
clientName,
|
|
280
|
+
startupMode
|
|
281
|
+
};
|
|
282
|
+
(0, config_js_1.saveConfigToDisk)(nextConfig);
|
|
283
|
+
const rl2 = (0, promises_1.createInterface)({ input: node_process_1.stdin, output: node_process_1.stdout });
|
|
284
|
+
try {
|
|
285
|
+
clearScreen();
|
|
286
|
+
console.log(titleBox(t('wizard.title')));
|
|
287
|
+
console.log(`\n${chalk_1.default.green(t('wizard.complete'))}`);
|
|
288
|
+
await pause(rl2);
|
|
289
|
+
}
|
|
290
|
+
finally {
|
|
291
|
+
rl2.close();
|
|
292
|
+
}
|
|
293
|
+
return nextConfig;
|
|
294
|
+
}
|
|
295
|
+
finally {
|
|
296
|
+
try {
|
|
297
|
+
rl.close();
|
|
298
|
+
}
|
|
299
|
+
catch { /* already closed */ }
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
async function handleToggleRuntime(config, rl) {
|
|
303
|
+
const status = (0, runtime_manager_js_1.getRuntimeStatus)();
|
|
304
|
+
const targetState = !status.running;
|
|
305
|
+
clearScreen();
|
|
306
|
+
try {
|
|
307
|
+
if (status.running) {
|
|
308
|
+
const msg = (0, runtime_manager_js_1.stopBackgroundRuntime)(status.snapshot);
|
|
309
|
+
console.log(`\n${chalk_1.default.yellow(msg)}`);
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
const msg = (0, runtime_manager_js_1.startBackgroundRuntime)();
|
|
313
|
+
console.log(`\n${chalk_1.default.green(msg)}`);
|
|
314
|
+
}
|
|
315
|
+
// Polling: chờ trạng thái thực sự thay đổi (tối đa 3 giây)
|
|
316
|
+
const deadline = Date.now() + 3000;
|
|
317
|
+
while (Date.now() < deadline) {
|
|
318
|
+
await new Promise(r => setTimeout(r, 80));
|
|
319
|
+
if ((0, runtime_manager_js_1.getRuntimeStatus)().running === targetState)
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
console.log(`\n${chalk_1.default.red(error instanceof Error ? error.message : String(error))}`);
|
|
325
|
+
await pause(rl);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
// Không pause — quay lại menu ngay để trạng thái cập nhật tức thì
|
|
329
|
+
}
|
|
330
|
+
async function editConfig(config, rl) {
|
|
331
|
+
clearScreen();
|
|
332
|
+
console.log(titleBox('Sửa cấu hình'));
|
|
333
|
+
console.log('');
|
|
334
|
+
const nextConfig = {
|
|
335
|
+
...config,
|
|
336
|
+
telegramBotToken: await askWithDefault(rl, 'TELEGRAM_BOT_TOKEN', config.telegramBotToken),
|
|
337
|
+
adminUsername: await askWithDefault(rl, 'ADMIN_USERNAME', config.adminUsername),
|
|
338
|
+
clientName: await askWithDefault(rl, 'CLIENT_NAME', config.clientName),
|
|
339
|
+
telegramUsername: await askWithDefault(rl, 'TELEGRAM_USERNAME', config.telegramUsername),
|
|
340
|
+
codexCmd: await askWithDefault(rl, 'CODEX_CMD', config.codexCmd),
|
|
341
|
+
antigravityCmd: await askWithDefault(rl, 'ANTIGRAVITY_CMD', config.antigravityCmd)
|
|
342
|
+
};
|
|
343
|
+
(0, config_js_1.saveConfigToDisk)(nextConfig);
|
|
344
|
+
console.log(`\n${chalk_1.default.green('✓ Đã lưu cấu hình.')}`);
|
|
345
|
+
console.log(chalk_1.default.gray('Khởi động lại runtime nền nếu đang chạy để áp dụng thay đổi.'));
|
|
346
|
+
await pause(rl);
|
|
347
|
+
return nextConfig;
|
|
348
|
+
}
|
|
349
|
+
async function manageWorkspaces(rl, language) {
|
|
350
|
+
const isVi = language === 'vi';
|
|
351
|
+
while (true) {
|
|
352
|
+
const workspaces = (0, store_js_1.loadWorkspaces)();
|
|
353
|
+
const options = [
|
|
354
|
+
{ label: isVi ? '+ Thêm workspace mới' : '+ Add new workspace', value: { type: 'add' } },
|
|
355
|
+
...workspaces.map((ws, i) => ({
|
|
356
|
+
label: `● ${ws.name} ${chalk_1.default.gray(ws.path.length > 30 ? '...' + ws.path.slice(-28) : ws.path)}`,
|
|
357
|
+
value: { type: 'workspace', index: i }
|
|
358
|
+
})),
|
|
359
|
+
{ label: isVi ? '← Quay lại menu chính' : '← Back', value: { type: 'back' } }
|
|
360
|
+
];
|
|
361
|
+
const selection = await runSelectionMenu(() => {
|
|
362
|
+
console.log(titleBox(isVi ? 'Quản lý Workspace' : 'Manage Workspaces'));
|
|
363
|
+
console.log(chalk_1.default.gray(` ${isVi ? 'Chọn workspace hoặc thêm mới:' : 'Select workspace or add new:'}`));
|
|
364
|
+
}, options);
|
|
365
|
+
if (selection.type === 'back')
|
|
366
|
+
return;
|
|
367
|
+
if (selection.type === 'add') {
|
|
368
|
+
clearScreen();
|
|
369
|
+
console.log(titleBox(isVi ? 'Thêm workspace mới' : 'Add workspace'));
|
|
370
|
+
console.log('');
|
|
371
|
+
const name = (await rl.question(chalk_1.default.bold(isVi ? 'Tên workspace: ' : 'Workspace name: '))).trim();
|
|
372
|
+
const rawPath = (await rl.question(chalk_1.default.bold(isVi ? 'Đường dẫn: ' : 'Path: '))).trim();
|
|
373
|
+
if (!rawPath) {
|
|
374
|
+
console.log(chalk_1.default.red(isVi ? ' Đường dẫn không được để trống.' : ' Path cannot be empty.'));
|
|
375
|
+
await pause(rl);
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
const resolvedPath = path.resolve(rawPath);
|
|
379
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
380
|
+
console.log(chalk_1.default.yellow(` ⚠ ${isVi ? 'Đường dẫn không tồn tại trên ổ đĩa.' : 'Path does not exist on disk.'}`));
|
|
381
|
+
}
|
|
382
|
+
workspaces.push({ id: (0, store_js_1.createWorkspaceId)(), name: name || 'Workspace', path: resolvedPath });
|
|
383
|
+
(0, store_js_1.saveWorkspaces)(workspaces);
|
|
384
|
+
console.log(chalk_1.default.green(`\n ✓ ${isVi ? 'Đã thêm workspace.' : 'Workspace added.'}`));
|
|
385
|
+
await pause(rl);
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
if (selection.type === 'workspace' && typeof selection.index === 'number') {
|
|
389
|
+
const idx = selection.index;
|
|
390
|
+
const ws = workspaces[idx];
|
|
391
|
+
const action = await runSelectionMenu(() => {
|
|
392
|
+
console.log(titleBox(isVi ? 'Hành động Workspace' : 'Workspace Actions'));
|
|
393
|
+
console.log(chalk_1.default.gray(` ${isVi ? 'Đang chọn:' : 'Selected:'} `) + chalk_1.default.bold(ws.name));
|
|
394
|
+
console.log(chalk_1.default.gray(` ${isVi ? 'Đường dẫn:' : 'Path:'} `) + ws.path);
|
|
395
|
+
}, [
|
|
396
|
+
{ label: isVi ? 'Sửa workspace' : 'Edit workspace', value: 'edit' },
|
|
397
|
+
{ label: isVi ? 'Xóa workspace' : 'Delete workspace', value: 'delete' },
|
|
398
|
+
{ label: isVi ? '← Quay lại' : '← Back', value: 'back' }
|
|
399
|
+
]);
|
|
400
|
+
if (action === 'back')
|
|
401
|
+
continue;
|
|
402
|
+
if (action === 'delete') {
|
|
403
|
+
workspaces.splice(idx, 1);
|
|
404
|
+
(0, store_js_1.saveWorkspaces)(workspaces);
|
|
405
|
+
clearScreen();
|
|
406
|
+
console.log(chalk_1.default.green(`\n ✓ ${isVi ? 'Đã xóa workspace.' : 'Workspace deleted.'}`));
|
|
407
|
+
await pause(rl);
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
if (action === 'edit') {
|
|
411
|
+
clearScreen();
|
|
412
|
+
console.log(titleBox(isVi ? 'Sửa workspace' : 'Edit workspace'));
|
|
413
|
+
console.log('');
|
|
414
|
+
const newName = await askWithDefault(rl, isVi ? 'Tên mới' : 'New name', ws.name);
|
|
415
|
+
const newPath = await askWithDefault(rl, isVi ? 'Đường dẫn mới' : 'New path', ws.path);
|
|
416
|
+
const resolvedPath = path.resolve(newPath.trim());
|
|
417
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
418
|
+
console.log(chalk_1.default.yellow(` ⚠ ${isVi ? 'Đường dẫn không tồn tại.' : 'Path does not exist.'}`));
|
|
419
|
+
}
|
|
420
|
+
workspaces[idx] = { ...ws, name: newName.trim() || ws.name, path: resolvedPath };
|
|
421
|
+
(0, store_js_1.saveWorkspaces)(workspaces);
|
|
422
|
+
console.log(chalk_1.default.green(`\n ✓ ${isVi ? 'Đã cập nhật workspace.' : 'Workspace updated.'}`));
|
|
423
|
+
await pause(rl);
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
async function configureStartup(config, rl) {
|
|
430
|
+
const isVi = config.language === 'vi';
|
|
431
|
+
const nextMode = await runSelectionMenu(() => {
|
|
432
|
+
console.log(titleBox(isVi ? 'Cấu hình khởi động' : 'Configure startup'));
|
|
433
|
+
console.log(chalk_1.default.gray(` ${isVi ? 'Chế độ hiện tại:' : 'Current mode:'} ${config.startupMode}`));
|
|
434
|
+
}, [
|
|
435
|
+
{ label: isVi ? 'Tắt (disabled)' : 'Disabled', value: 'disabled' },
|
|
436
|
+
{ label: isVi ? 'Chạy nền (background)' : 'Background', value: 'background' },
|
|
437
|
+
{ label: isVi ? 'Mở giao diện (open-ui)' : 'Open UI', value: 'open-ui' }
|
|
438
|
+
], ['disabled', 'background', 'open-ui'].indexOf(config.startupMode));
|
|
439
|
+
const entryScriptPath = path.join(process.cwd(), 'dist', 'index.js');
|
|
440
|
+
const result = (0, startup_js_1.applyStartupMode)(nextMode, entryScriptPath, process.cwd());
|
|
441
|
+
const nextConfig = { ...config, startupMode: nextMode };
|
|
442
|
+
(0, config_js_1.saveConfigToDisk)(nextConfig);
|
|
443
|
+
clearScreen();
|
|
444
|
+
console.log(titleBox(isVi ? 'Cấu hình khởi động' : 'Configure startup'));
|
|
445
|
+
console.log(`\n${chalk_1.default.green(result)}`);
|
|
446
|
+
await pause(rl);
|
|
447
|
+
return nextConfig;
|
|
448
|
+
}
|
|
449
|
+
async function switchLanguage(config, rl) {
|
|
450
|
+
const language = await runSelectionMenu(() => {
|
|
451
|
+
console.log(titleBox('Đổi ngôn ngữ / Switch language'));
|
|
452
|
+
console.log(chalk_1.default.gray(` Hiện tại: ${config.language}`));
|
|
453
|
+
}, [
|
|
454
|
+
{ label: 'Tiếng Việt (vi)', value: 'vi' },
|
|
455
|
+
{ label: 'English (en)', value: 'en' }
|
|
456
|
+
], config.language === 'en' ? 1 : 0);
|
|
457
|
+
const nextConfig = { ...config, language };
|
|
458
|
+
(0, config_js_1.saveConfigToDisk)(nextConfig);
|
|
459
|
+
return nextConfig;
|
|
460
|
+
}
|
|
461
|
+
async function showRecentLogs(rl) {
|
|
462
|
+
clearScreen();
|
|
463
|
+
console.log(titleBox('Nhật ký gần đây'));
|
|
464
|
+
const logPath = path.join(process.cwd(), 'data', 'nonstop.log');
|
|
465
|
+
if (!fs.existsSync(logPath)) {
|
|
466
|
+
console.log(chalk_1.default.gray('\n Chưa có nhật ký.'));
|
|
467
|
+
await pause(rl);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
const lines = fs.readFileSync(logPath, 'utf8').split(/\r?\n/).filter(Boolean).slice(-25);
|
|
471
|
+
console.log('\n' + lines.map(l => chalk_1.default.gray(' ') + l).join('\n'));
|
|
472
|
+
await pause(rl);
|
|
473
|
+
}
|
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@quangnv13/nonstop",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"description": "nonstop Telegram terminal control CLI",
|
|
8
|
+
"author": "quangnv13",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"homepage": "https://github.com/quangnv13/nonstop#readme",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/quangnv13/nonstop.git"
|
|
14
|
+
},
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/quangnv13/nonstop/issues"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"telegram",
|
|
20
|
+
"bot",
|
|
21
|
+
"pty",
|
|
22
|
+
"terminal",
|
|
23
|
+
"cli",
|
|
24
|
+
"tui",
|
|
25
|
+
"remote-control",
|
|
26
|
+
"ai agent"
|
|
27
|
+
],
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"images",
|
|
31
|
+
"README.md",
|
|
32
|
+
"README.vi.md"
|
|
33
|
+
],
|
|
34
|
+
"bin": {
|
|
35
|
+
"nonstop": "dist/index.js"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"dev": "tsx watch src/index.ts",
|
|
39
|
+
"build": "tsc -p tsconfig.json",
|
|
40
|
+
"start": "node dist/index.js",
|
|
41
|
+
"test": "node --import tsx --test src/*.test.ts"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"boxen": "^5.1.2",
|
|
45
|
+
"chalk": "^4.1.2",
|
|
46
|
+
"dotenv": "^16.4.5",
|
|
47
|
+
"grammy": "^1.22.4",
|
|
48
|
+
"node-pty": "^1.0.0",
|
|
49
|
+
"picocolors": "^1.1.1"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/node": "^20.11.0",
|
|
53
|
+
"tsx": "^4.7.1",
|
|
54
|
+
"typescript": "^5.3.3"
|
|
55
|
+
}
|
|
56
|
+
}
|