create-openclaw-bot 5.7.3 → 5.7.4
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/dist/cli.js +197 -194
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import chalk from 'chalk';
|
|
|
8
8
|
import { spawn, execSync, execFileSync } from 'child_process';
|
|
9
9
|
import { createRequire } from 'module';
|
|
10
10
|
|
|
11
|
-
//
|
|
11
|
+
// ─── Shared generators (dual-mode IIFE + CJS) ────────────────────────────────
|
|
12
12
|
// These modules export via module.exports when required from Node.js
|
|
13
13
|
const _require = createRequire(import.meta.url);
|
|
14
14
|
|
|
@@ -75,8 +75,8 @@ function installRelayPluginForProject(projectDir, isVi) {
|
|
|
75
75
|
// silent fallback
|
|
76
76
|
}
|
|
77
77
|
console.log(chalk.yellow(isVi
|
|
78
|
-
? `\
|
|
79
|
-
: `\
|
|
78
|
+
? `\nâš ï¸ Chua the tu dong cai plugin. Sau khi bot chay, chay thu cong:\n openclaw plugins install ${TELEGRAM_RELAY_PLUGIN_SPEC}`
|
|
79
|
+
: `\nâš ï¸ Could not auto-install plugin. After the bot starts, run manually:\n openclaw plugins install ${TELEGRAM_RELAY_PLUGIN_SPEC}`));
|
|
80
80
|
return false;
|
|
81
81
|
}
|
|
82
82
|
|
|
@@ -366,8 +366,8 @@ function ensureUserWritableGlobalNpm({ isVi, osChoice }) {
|
|
|
366
366
|
return true;
|
|
367
367
|
} catch {
|
|
368
368
|
console.log(chalk.yellow(isVi
|
|
369
|
-
? '
|
|
370
|
-
: '
|
|
369
|
+
? 'âš ï¸ Không thể cấu hình npm global prefix trong ~/.local. Tiếp tục thá» cà i đặt trá»±c tiếp.'
|
|
370
|
+
: 'âš ï¸ Could not configure npm global prefix in ~/.local. Falling back to direct install.'));
|
|
371
371
|
return false;
|
|
372
372
|
}
|
|
373
373
|
}
|
|
@@ -401,35 +401,35 @@ function installGlobalPackage(pkg, { isVi, osChoice, displayName }) {
|
|
|
401
401
|
}
|
|
402
402
|
|
|
403
403
|
console.log(chalk.yellow(isVi
|
|
404
|
-
?
|
|
405
|
-
:
|
|
404
|
+
? `âš ï¸ Không thể tá»± cà i ${displayName}. Chạy thá»§ công: ${osChoice === 'windows' ? `npm install -g ${pkg}` : `npm config set prefix ~/.local && npm install -g ${pkg}`}`
|
|
405
|
+
: `âš ï¸ Could not auto-install ${displayName}. Run manually: ${osChoice === 'windows' ? `npm install -g ${pkg}` : `npm config set prefix ~/.local && npm install -g ${pkg}`}`));
|
|
406
406
|
return false;
|
|
407
407
|
}
|
|
408
408
|
|
|
409
409
|
function installLatestOpenClaw({ isVi, osChoice }) {
|
|
410
410
|
if (shouldReuseInstalledGlobals() && isOpenClawInstalled()) {
|
|
411
411
|
console.log(chalk.green(isVi
|
|
412
|
-
? '\n
|
|
413
|
-
: '\n
|
|
412
|
+
? '\nâ™»ï¸ Dang dung lai openclaw da cai san de test nhanh.'
|
|
413
|
+
: '\nâ™»ï¸ Reusing the installed openclaw for a faster test run.'));
|
|
414
414
|
return;
|
|
415
415
|
}
|
|
416
416
|
|
|
417
417
|
console.log(chalk.cyan(isVi
|
|
418
|
-
? `\n
|
|
419
|
-
: `\n
|
|
418
|
+
? `\n📦 Dang cai/cap nhat ${OPENCLAW_NPM_SPEC}...`
|
|
419
|
+
: `\n📦 Installing/updating ${OPENCLAW_NPM_SPEC}...`));
|
|
420
420
|
|
|
421
421
|
if (!installGlobalPackage(OPENCLAW_NPM_SPEC, { isVi, osChoice, displayName: 'openclaw' })) {
|
|
422
422
|
process.exit(1);
|
|
423
423
|
}
|
|
424
424
|
|
|
425
425
|
console.log(chalk.green(isVi
|
|
426
|
-
?
|
|
427
|
-
:
|
|
426
|
+
? `✅ openclaw da duoc ghim dung ban ${OPENCLAW_NPM_SPEC}!`
|
|
427
|
+
: `✅ openclaw is now pinned to ${OPENCLAW_NPM_SPEC}!`));
|
|
428
428
|
}
|
|
429
429
|
|
|
430
|
-
//
|
|
430
|
+
// ─── Shared from docker-gen.js ──────────────────────────────────────────────
|
|
431
431
|
// build9RouterSmartRouteSyncScript, indentBlock, build9RouterComposeEntrypointScript
|
|
432
|
-
// are imported from setup/shared/docker-gen.js
|
|
432
|
+
// are imported from setup/shared/docker-gen.js — do NOT re-define here.
|
|
433
433
|
|
|
434
434
|
function resolveCommandOnPath(command) {
|
|
435
435
|
if (process.platform === 'win32') {
|
|
@@ -624,7 +624,7 @@ function printZaloPersonalLoginInfo({ isVi, deployMode, projectDir }) {
|
|
|
624
624
|
: `cp "${qrPath}" "${path.join(projectDir, 'zalo-login-qr.png')}"`)
|
|
625
625
|
: `docker cp openclaw-bot:${qrPath} ./zalo-qr.png`;
|
|
626
626
|
|
|
627
|
-
console.log(chalk.yellow(`\n
|
|
627
|
+
console.log(chalk.yellow(`\n📱 ${isVi ? 'Äăng nháºp Zalo Personal (1 lần):' : 'Zalo Personal login (one time):'}`));
|
|
628
628
|
if (deployMode === 'docker') {
|
|
629
629
|
console.log(chalk.white(isVi
|
|
630
630
|
? ` 1. cd ${projectDir}/docker/openclaw`
|
|
@@ -633,19 +633,19 @@ function printZaloPersonalLoginInfo({ isVi, deployMode, projectDir }) {
|
|
|
633
633
|
? ` 2. ${cmd}`
|
|
634
634
|
: ` 2. ${cmd}`));
|
|
635
635
|
console.log(chalk.white(isVi
|
|
636
|
-
? ` 3.
|
|
636
|
+
? ` 3. Tìm file QR trong container: ${qrPath}`
|
|
637
637
|
: ` 3. Find QR image in container: ${qrPath}`));
|
|
638
638
|
console.log(chalk.gray(isVi
|
|
639
|
-
? `
|
|
640
|
-
: `
|
|
639
|
+
? ` → Mở Docker Desktop > container openclaw-bot > tab Files > tìm file trên`
|
|
640
|
+
: ` → Open Docker Desktop > container openclaw-bot > Files tab > find file above`));
|
|
641
641
|
console.log(chalk.gray(isVi
|
|
642
|
-
? `
|
|
643
|
-
: `
|
|
642
|
+
? ` → Hoặc chạy: ${copyCmd}`
|
|
643
|
+
: ` → Or run: ${copyCmd}`));
|
|
644
644
|
console.log(chalk.white(isVi
|
|
645
|
-
? ' 4.
|
|
645
|
+
? ' 4. Mở app Zalo > Quét QR > quét mã trong file QR'
|
|
646
646
|
: ' 4. Open Zalo app > Scan QR > scan the QR image'));
|
|
647
647
|
console.log(chalk.white(isVi
|
|
648
|
-
? ' 5.
|
|
648
|
+
? ' 5. Äợi thấy "Login successful" trong terminal'
|
|
649
649
|
: ' 5. Wait for "Login successful" in terminal'));
|
|
650
650
|
console.log(chalk.white(isVi
|
|
651
651
|
? ' 6. docker compose restart'
|
|
@@ -653,12 +653,12 @@ function printZaloPersonalLoginInfo({ isVi, deployMode, projectDir }) {
|
|
|
653
653
|
} else {
|
|
654
654
|
console.log(chalk.white(` cd ${projectDir} ${process.platform === 'win32' ? ';' : '&&'} ${cmd}`));
|
|
655
655
|
console.log(chalk.gray(isVi
|
|
656
|
-
? `
|
|
657
|
-
: `
|
|
656
|
+
? ` → File QR sẽ tạo tại: ${qrPath}`
|
|
657
|
+
: ` → QR file will be created at: ${qrPath}`));
|
|
658
658
|
if (process.platform === 'win32') {
|
|
659
659
|
console.log(chalk.gray(isVi
|
|
660
|
-
? `
|
|
661
|
-
: `
|
|
660
|
+
? ` → Copy QR ra project: ${copyCmd}`
|
|
661
|
+
: ` → Copy QR to project: ${copyCmd}`));
|
|
662
662
|
}
|
|
663
663
|
}
|
|
664
664
|
}
|
|
@@ -708,13 +708,13 @@ function approveZaloPairingCode({ pairingCode, projectDir, isVi }) {
|
|
|
708
708
|
env: process.env
|
|
709
709
|
});
|
|
710
710
|
console.log(chalk.green(isVi
|
|
711
|
-
?
|
|
712
|
-
:
|
|
711
|
+
? `✅ Da tu dong approve pairing code Zalo: ${pairingCode}`
|
|
712
|
+
: `✅ Automatically approved the Zalo pairing code: ${pairingCode}`));
|
|
713
713
|
return true;
|
|
714
714
|
} catch {
|
|
715
715
|
console.log(chalk.yellow(isVi
|
|
716
|
-
?
|
|
717
|
-
:
|
|
716
|
+
? `âš ï¸ Khong the tu dong approve pairing code ${pairingCode}. Ban co the chay thu cong: openclaw pairing approve zalouser ${pairingCode}`
|
|
717
|
+
: `âš ï¸ Could not auto-approve pairing code ${pairingCode}. You can run it manually: openclaw pairing approve zalouser ${pairingCode}`));
|
|
718
718
|
return false;
|
|
719
719
|
}
|
|
720
720
|
}
|
|
@@ -722,7 +722,7 @@ function approveZaloPairingCode({ pairingCode, projectDir, isVi }) {
|
|
|
722
722
|
async function runNativeZaloPersonalLoginFlow({ isVi, projectDir }) {
|
|
723
723
|
const qrSourcePath = path.join(os.tmpdir(), 'openclaw', 'openclaw-zalouser-qr-default.png');
|
|
724
724
|
const qrProjectPath = path.join(projectDir, 'zalo-login-qr.png');
|
|
725
|
-
console.log(chalk.yellow(`\n
|
|
725
|
+
console.log(chalk.yellow(`\n📱 ${isVi ? 'Äang tạo QR đăng nháºp Zalo Personal...' : 'Generating the Zalo Personal login QR...'}`));
|
|
726
726
|
const loginStartedAt = Date.now();
|
|
727
727
|
|
|
728
728
|
try {
|
|
@@ -776,8 +776,8 @@ async function runNativeZaloPersonalLoginFlow({ isVi, projectDir }) {
|
|
|
776
776
|
await fs.copy(qrSourcePath, qrProjectPath, { overwrite: true });
|
|
777
777
|
qrCopied = true;
|
|
778
778
|
console.log(chalk.green(isVi
|
|
779
|
-
?
|
|
780
|
-
:
|
|
779
|
+
? `✅ QR đã được copy và o: ${qrProjectPath}`
|
|
780
|
+
: `✅ QR copied to: ${qrProjectPath}`));
|
|
781
781
|
}
|
|
782
782
|
};
|
|
783
783
|
|
|
@@ -794,13 +794,13 @@ async function runNativeZaloPersonalLoginFlow({ isVi, projectDir }) {
|
|
|
794
794
|
|
|
795
795
|
if (exitCode !== 0 && !loginSucceeded) {
|
|
796
796
|
console.log(chalk.yellow(isVi
|
|
797
|
-
? '
|
|
798
|
-
: '
|
|
797
|
+
? 'âš ï¸ Chưa hoà n tất đăng nháºp Zalo trong lúc setup. Bạn có thể chạy lại lệnh login thá»§ công sau.'
|
|
798
|
+
: 'âš ï¸ Zalo login was not completed during setup. You can run the login command manually afterwards.'));
|
|
799
799
|
printZaloPersonalLoginInfo({ isVi, deployMode: 'native', projectDir });
|
|
800
800
|
} else if (loginSucceeded && exitCode !== 0) {
|
|
801
801
|
console.log(chalk.green(isVi
|
|
802
|
-
? '
|
|
803
|
-
: '
|
|
802
|
+
? '✅ Äăng nháºp Zalo đã thà nh công dù CLI trả vá» trạng thái không chuẩn.'
|
|
803
|
+
: '✅ Zalo login succeeded even though the CLI returned a non-standard exit status.'));
|
|
804
804
|
}
|
|
805
805
|
}
|
|
806
806
|
|
|
@@ -814,8 +814,8 @@ function runPm2Save({ projectDir, isVi }) {
|
|
|
814
814
|
});
|
|
815
815
|
} catch {
|
|
816
816
|
console.log(chalk.yellow(isVi
|
|
817
|
-
? '
|
|
818
|
-
: '
|
|
817
|
+
? 'âš ï¸ PM2 save khong hoan tat. Bot van co the dang chay, nhung hay thu chay lai `pm2 save` sau.'
|
|
818
|
+
: 'âš ï¸ PM2 save did not complete. The app may still be running, but try `pm2 save` again afterwards.'));
|
|
819
819
|
}
|
|
820
820
|
}
|
|
821
821
|
|
|
@@ -1047,26 +1047,26 @@ function startNative9RouterPm2({ isVi, projectDir, appName, syncScriptPath }) {
|
|
|
1047
1047
|
});
|
|
1048
1048
|
} catch (syncErr) {
|
|
1049
1049
|
console.log(chalk.yellow(isVi
|
|
1050
|
-
? `\
|
|
1051
|
-
: `\
|
|
1050
|
+
? `\nâš ï¸ Khong the tu dong khoi dong sync script qua PM2.`
|
|
1051
|
+
: `\nâš ï¸ Could not auto-start 9router sync script via PM2.`));
|
|
1052
1052
|
}
|
|
1053
1053
|
}
|
|
1054
1054
|
runPm2Save({ projectDir, isVi });
|
|
1055
|
-
console.log(chalk.green(`\n
|
|
1055
|
+
console.log(chalk.green(`\n✅ ${isVi ? '9Router da duoc khoi dong qua PM2.' : '9Router is running via PM2.'}`));
|
|
1056
1056
|
console.log(chalk.gray(isVi ? ` Xem log: pm2 logs ${routerAppName}` : ` View logs: pm2 logs ${routerAppName}`));
|
|
1057
1057
|
}
|
|
1058
1058
|
|
|
1059
1059
|
async function ensureProjectRuntimeDirs(projectDir, isVi) {
|
|
1060
1060
|
await fs.ensureDir(path.join(projectDir, '.openclaw'));
|
|
1061
1061
|
await fs.ensureDir(getProject9RouterDataDir(projectDir));
|
|
1062
|
-
console.log(chalk.green(`\n
|
|
1062
|
+
console.log(chalk.green(`\n✅ ${isVi
|
|
1063
1063
|
? 'Da chuan bi runtime directories local trong project.'
|
|
1064
1064
|
: 'Prepared project-local runtime directories.'}`));
|
|
1065
1065
|
}
|
|
1066
1066
|
|
|
1067
1067
|
// buildTelegramPostInstallChecklist is imported from setup/shared/common-gen.js
|
|
1068
1068
|
|
|
1069
|
-
//
|
|
1069
|
+
// ─── Docker Auto-Detection ───────────────────────────────────────────────────
|
|
1070
1070
|
function isDockerInstalled() {
|
|
1071
1071
|
try {
|
|
1072
1072
|
execSync('docker --version', { stdio: 'ignore' });
|
|
@@ -1077,15 +1077,15 @@ function isDockerInstalled() {
|
|
|
1077
1077
|
|
|
1078
1078
|
|
|
1079
1079
|
const LOGO = `
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1080
|
+
████████╗██╗ ██╗ █████╗ ███╗ ██╗███╗ ███╗██╗███╗ ██╗██╗ ██╗██╗ ██╗ ██████╗ ██╗ ███████╗
|
|
1081
|
+
╚â•â•██╔â•â•â•██║ ██║██╔â•â•██╗████╗ ██║████╗ ████║██║████╗ ██║██║ ██║██║ ██║██╔â•â•â•██╗██║ ██╔â•â•â•â•â•
|
|
1082
|
+
██║ ██║ ██║███████║██╔██╗ ██║██╔████╔██║██║██╔██╗ ██║███████║███████║██║ ██║██║ █████╗
|
|
1083
|
+
██║ ██║ ██║██╔â•â•██║██║╚██╗██║██║╚██╔â•██║██║██║╚██╗██║██╔â•â•██║██╔â•â•██║██║ ██║██║ ██╔â•â•â•
|
|
1084
|
+
██║ ╚██████╔â•██║ ██║██║ ╚████║██║ ╚â•╠██║██║██║ ╚████║██║ ██║██║ ██║╚██████╔â•███████╗███████╗
|
|
1085
|
+
╚â•╠╚â•â•â•â•â•╠╚â•╠╚â•â•╚â•╠╚â•â•â•â•╚â•╠╚â•â•╚â•â•╚â•╠╚â•â•â•â•╚â•╠╚â•â•╚â•╠╚â•╠╚â•â•â•â•â•╠╚â•â•â•â•â•â•â•╚â•â•â•â•â•â•â•
|
|
1086
1086
|
`;
|
|
1087
1087
|
|
|
1088
|
-
//
|
|
1088
|
+
// ── Data constants from setup/data/index.js (single source of truth) ──────────
|
|
1089
1089
|
const CHANNELS = _CHANNELS;
|
|
1090
1090
|
const PROVIDERS = _PROVIDERS;
|
|
1091
1091
|
const SKILLS = _SKILLS;
|
|
@@ -1114,8 +1114,8 @@ function getCliSkillChoices({ providerKey, isVi }) {
|
|
|
1114
1114
|
let name = `${skill.icon || ''} ${isVi ? (skill.nameVi || skill.name) : (skill.nameEn || skill.name)}`.trim();
|
|
1115
1115
|
if (value === 'memory') {
|
|
1116
1116
|
name = isVi
|
|
1117
|
-
? (memoryRecommended ? '
|
|
1118
|
-
: (memoryRecommended ? '
|
|
1117
|
+
? (memoryRecommended ? '🧠Long-term Memory â(Khuyên dùng)' : '🧠Long-term Memory')
|
|
1118
|
+
: (memoryRecommended ? '🧠Long-term Memory â(Recommended)' : '🧠Long-term Memory');
|
|
1119
1119
|
}
|
|
1120
1120
|
return {
|
|
1121
1121
|
name,
|
|
@@ -1129,13 +1129,13 @@ const CLI_BACK = '__openclaw_cli_back__';
|
|
|
1129
1129
|
|
|
1130
1130
|
function getBackChoice(isVi) {
|
|
1131
1131
|
return {
|
|
1132
|
-
name: isVi ? '
|
|
1132
|
+
name: isVi ? '↠Quay lại' : '↠Back',
|
|
1133
1133
|
value: CLI_BACK,
|
|
1134
1134
|
};
|
|
1135
1135
|
}
|
|
1136
1136
|
|
|
1137
1137
|
function withBackHint(message, isVi) {
|
|
1138
|
-
return `${message} ${isVi ? '(
|
|
1138
|
+
return `${message} ${isVi ? '(gõ "back" để quay lại)' : '(type "back" to go back)'}`;
|
|
1139
1139
|
}
|
|
1140
1140
|
|
|
1141
1141
|
async function selectWithBack({ message, choices, defaultValue, allowBack = false, isVi = true }) {
|
|
@@ -1182,7 +1182,7 @@ async function collectBotSetupStep({
|
|
|
1182
1182
|
|
|
1183
1183
|
if (channelKey === 'telegram') {
|
|
1184
1184
|
const botCountValue = await selectWithBack({
|
|
1185
|
-
message: isVi ? '
|
|
1185
|
+
message: isVi ? 'Bạn muốn cà i bao nhiêu Telegram bot?' : 'How many Telegram bots do you want to deploy?',
|
|
1186
1186
|
choices: [
|
|
1187
1187
|
{ name: '1 bot (single)', value: '1' },
|
|
1188
1188
|
{ name: '2 bots (Department Room)', value: '2' },
|
|
@@ -1201,10 +1201,10 @@ async function collectBotSetupStep({
|
|
|
1201
1201
|
|
|
1202
1202
|
if (botCount > 1) {
|
|
1203
1203
|
const groupOption = await selectWithBack({
|
|
1204
|
-
message: isVi ? '
|
|
1204
|
+
message: isVi ? 'Bạn có sẵn Telegram Group chưa?' : 'Do you already have a Telegram Group?',
|
|
1205
1205
|
choices: [
|
|
1206
|
-
{ name: isVi ? '
|
|
1207
|
-
{ name: isVi ? '
|
|
1206
|
+
{ name: isVi ? '✨ Tôi sẽ tạo sau (nháºp Group ID và o .env sau khi setup)' : '✨ I\'ll create later (add Group ID to .env after setup)', value: 'create' },
|
|
1207
|
+
{ name: isVi ? '🔗 Äã có group — nháºp Group ID ngay' : '🔗 Already have a group — enter Group ID now', value: 'existing' }
|
|
1208
1208
|
],
|
|
1209
1209
|
defaultValue: groupId ? 'existing' : 'create',
|
|
1210
1210
|
allowBack: true,
|
|
@@ -1216,8 +1216,8 @@ async function collectBotSetupStep({
|
|
|
1216
1216
|
|
|
1217
1217
|
if (groupOption === 'existing') {
|
|
1218
1218
|
console.log(chalk.dim(isVi
|
|
1219
|
-
? '\n
|
|
1220
|
-
: '\n
|
|
1219
|
+
? '\n 📌 Cách lấy Group ID:\n 1. Mở Telegram → tìm @userinfobot\n 2. Bấm Start để bắt đầu → chá»n nút "Group" trên mà n hình → chá»n Group bạn muốn thêm bot và o\n 3. Bot sẽ trả vá» "Chat ID" — đó là Group ID (bắt đầu bằng -100)\n 👉 https://t.me/userinfobot\n'
|
|
1220
|
+
: '\n 📌 How to get Group ID:\n 1. Open Telegram → find @userinfobot\n 2. Click Start → select "Group" button on the screen → select the group you want to add the bot to\n 3. The bot replies with "Chat ID" — that is your Group ID (starts with -100)\n 👉 https://t.me/userinfobot\n'));
|
|
1221
1221
|
const nextGroupId = await inputWithBack({
|
|
1222
1222
|
message: isVi ? 'Telegram Group ID (VD: -1001234567890):' : 'Telegram Group ID (e.g. -1001234567890):',
|
|
1223
1223
|
defaultValue: groupId,
|
|
@@ -1236,12 +1236,12 @@ async function collectBotSetupStep({
|
|
|
1236
1236
|
}
|
|
1237
1237
|
|
|
1238
1238
|
for (let i = 0; i < botCount; i++) {
|
|
1239
|
-
console.log(chalk.bold(`\n${isVi ?
|
|
1239
|
+
console.log(chalk.bold(`\n${isVi ? `─── Bot ${i + 1} / ${botCount} ───` : `─── Bot ${i + 1} / ${botCount} ───`}`));
|
|
1240
1240
|
const defaults = existingBots[i] || {};
|
|
1241
1241
|
const fields = [
|
|
1242
1242
|
{
|
|
1243
1243
|
key: 'name',
|
|
1244
|
-
message: isVi ? `
|
|
1244
|
+
message: isVi ? `Tên Bot ${i + 1}:` : `Bot ${i + 1} name:`,
|
|
1245
1245
|
defaultValue: defaults.name || `Bot ${i + 1}`,
|
|
1246
1246
|
required: true,
|
|
1247
1247
|
},
|
|
@@ -1253,19 +1253,19 @@ async function collectBotSetupStep({
|
|
|
1253
1253
|
},
|
|
1254
1254
|
{
|
|
1255
1255
|
key: 'desc',
|
|
1256
|
-
message: isVi ? `
|
|
1257
|
-
defaultValue: defaults.desc || (isVi ? '
|
|
1256
|
+
message: isVi ? `Mô tả Bot ${i + 1} (VD: Trợ lý AI cá nhân):` : `Bot ${i + 1} description (e.g. Personal AI assistant):`,
|
|
1257
|
+
defaultValue: defaults.desc || (isVi ? 'Trợ lý AI cá nhân' : 'Personal AI assistant'),
|
|
1258
1258
|
required: true,
|
|
1259
1259
|
},
|
|
1260
1260
|
{
|
|
1261
1261
|
key: 'persona',
|
|
1262
|
-
message: isVi ? `
|
|
1262
|
+
message: isVi ? `TÃnh cách & quy tắc Bot ${i + 1} (VD: siêu thân thiện, nhiá»u emoji):` : `Bot ${i + 1} persona & rules (e.g. friendly, uses emojis):`,
|
|
1263
1263
|
defaultValue: defaults.persona || '',
|
|
1264
1264
|
required: false,
|
|
1265
1265
|
},
|
|
1266
1266
|
{
|
|
1267
1267
|
key: 'token',
|
|
1268
|
-
message: isVi ? 'Bot Token (
|
|
1268
|
+
message: isVi ? 'Bot Token (từ @BotFather):' : 'Bot Token (from @BotFather):',
|
|
1269
1269
|
defaultValue: defaults.token || '',
|
|
1270
1270
|
required: true,
|
|
1271
1271
|
},
|
|
@@ -1299,25 +1299,25 @@ async function collectBotSetupStep({
|
|
|
1299
1299
|
const fields = [
|
|
1300
1300
|
{
|
|
1301
1301
|
key: 'name',
|
|
1302
|
-
message: isVi ? '
|
|
1302
|
+
message: isVi ? 'Tên Bot:' : 'Bot Name:',
|
|
1303
1303
|
defaultValue: defaults.name || 'Chat Bot',
|
|
1304
1304
|
required: true,
|
|
1305
1305
|
},
|
|
1306
1306
|
{
|
|
1307
1307
|
key: 'desc',
|
|
1308
|
-
message: isVi ? '
|
|
1309
|
-
defaultValue: defaults.desc || (isVi ? '
|
|
1308
|
+
message: isVi ? 'Mô tả Bot:' : 'Bot Description:',
|
|
1309
|
+
defaultValue: defaults.desc || (isVi ? 'Trợ lý AI cá nhân' : 'Personal AI assistant'),
|
|
1310
1310
|
required: true,
|
|
1311
1311
|
},
|
|
1312
1312
|
{
|
|
1313
1313
|
key: 'persona',
|
|
1314
|
-
message: isVi ? '
|
|
1314
|
+
message: isVi ? 'TÃnh cách & quy tắc (VD: gá»n gà ng, thân thiện):' : 'Persona & rules (e.g. concise, friendly):',
|
|
1315
1315
|
defaultValue: defaults.persona || '',
|
|
1316
1316
|
required: false,
|
|
1317
1317
|
},
|
|
1318
1318
|
{
|
|
1319
1319
|
key: 'token',
|
|
1320
|
-
message: isVi ? `
|
|
1320
|
+
message: isVi ? `Nháºp ${channel.name} Token:` : `Enter ${channel.name} Token:`,
|
|
1321
1321
|
defaultValue: defaults.token || '',
|
|
1322
1322
|
required: true,
|
|
1323
1323
|
},
|
|
@@ -1373,7 +1373,7 @@ async function collectBotSetupStepWithGroupBack(options) {
|
|
|
1373
1373
|
|
|
1374
1374
|
if (channelKey === 'telegram') {
|
|
1375
1375
|
const botCountValue = await selectWithBack({
|
|
1376
|
-
message: isVi ? '
|
|
1376
|
+
message: isVi ? 'Bạn muốn cà i bao nhiêu Telegram bot?' : 'How many Telegram bots do you want to deploy?',
|
|
1377
1377
|
choices: [
|
|
1378
1378
|
{ name: '1 bot (single)', value: '1' },
|
|
1379
1379
|
{ name: '2 bots (Department Room)', value: '2' },
|
|
@@ -1393,10 +1393,10 @@ async function collectBotSetupStepWithGroupBack(options) {
|
|
|
1393
1393
|
if (botCount > 1) {
|
|
1394
1394
|
while (true) {
|
|
1395
1395
|
const groupOption = await selectWithBack({
|
|
1396
|
-
message: isVi ? '
|
|
1396
|
+
message: isVi ? 'Bạn có sẵn Telegram Group chưa?' : 'Do you already have a Telegram Group?',
|
|
1397
1397
|
choices: [
|
|
1398
|
-
{ name: isVi ? '
|
|
1399
|
-
{ name: isVi ? '
|
|
1398
|
+
{ name: isVi ? '✨ Tôi sẽ tạo sau (nháºp Group ID và o .env sau khi setup)' : '✨ I\'ll create later (add Group ID to .env after setup)', value: 'create' },
|
|
1399
|
+
{ name: isVi ? '🔗 Äã có group — nháºp Group ID ngay' : '🔗 Already have a group — enter Group ID now', value: 'existing' }
|
|
1400
1400
|
],
|
|
1401
1401
|
defaultValue: groupId ? 'existing' : 'create',
|
|
1402
1402
|
allowBack: true,
|
|
@@ -1408,8 +1408,8 @@ async function collectBotSetupStepWithGroupBack(options) {
|
|
|
1408
1408
|
|
|
1409
1409
|
if (groupOption === 'existing') {
|
|
1410
1410
|
console.log(chalk.dim(isVi
|
|
1411
|
-
? '\n
|
|
1412
|
-
: '\n
|
|
1411
|
+
? '\n 📌 Cách lấy Group ID:\n 1. Mở Telegram → tìm @userinfobot\n 2. Bấm Start để bắt đầu → chá»n nút "Group" trên mà n hình → chá»n Group bạn muốn thêm bot và o\n 3. Bot sẽ trả vá» "Chat ID" — đó là Group ID (bắt đầu bằng -100)\n 👉 https://t.me/userinfobot\n'
|
|
1412
|
+
: '\n 📌 How to get Group ID:\n 1. Open Telegram → find @userinfobot\n 2. Click Start → select "Group" button on the screen → select the group you want to add the bot to\n 3. The bot replies with "Chat ID" — that is your Group ID (starts with -100)\n 👉 https://t.me/userinfobot\n'));
|
|
1413
1413
|
const nextGroupId = await inputWithBack({
|
|
1414
1414
|
message: isVi ? 'Telegram Group ID (VD: -1001234567890):' : 'Telegram Group ID (e.g. -1001234567890):',
|
|
1415
1415
|
defaultValue: groupId,
|
|
@@ -1431,12 +1431,12 @@ async function collectBotSetupStepWithGroupBack(options) {
|
|
|
1431
1431
|
}
|
|
1432
1432
|
|
|
1433
1433
|
for (let i = 0; i < botCount; i++) {
|
|
1434
|
-
console.log(chalk.bold(`\n${isVi ?
|
|
1434
|
+
console.log(chalk.bold(`\n${isVi ? `─── Bot ${i + 1} / ${botCount} ───` : `─── Bot ${i + 1} / ${botCount} ───`}`));
|
|
1435
1435
|
const defaults = existingBots[i] || {};
|
|
1436
1436
|
const fields = [
|
|
1437
1437
|
{
|
|
1438
1438
|
key: 'name',
|
|
1439
|
-
message: isVi ? `
|
|
1439
|
+
message: isVi ? `Tên Bot ${i + 1}:` : `Bot ${i + 1} name:`,
|
|
1440
1440
|
defaultValue: defaults.name || `Bot ${i + 1}`,
|
|
1441
1441
|
required: true,
|
|
1442
1442
|
},
|
|
@@ -1448,19 +1448,19 @@ async function collectBotSetupStepWithGroupBack(options) {
|
|
|
1448
1448
|
},
|
|
1449
1449
|
{
|
|
1450
1450
|
key: 'desc',
|
|
1451
|
-
message: isVi ? `
|
|
1452
|
-
defaultValue: defaults.desc || (isVi ? '
|
|
1451
|
+
message: isVi ? `Mô tả Bot ${i + 1} (VD: Trợ lý AI cá nhân):` : `Bot ${i + 1} description (e.g. Personal AI assistant):`,
|
|
1452
|
+
defaultValue: defaults.desc || (isVi ? 'Trợ lý AI cá nhân' : 'Personal AI assistant'),
|
|
1453
1453
|
required: true,
|
|
1454
1454
|
},
|
|
1455
1455
|
{
|
|
1456
1456
|
key: 'persona',
|
|
1457
|
-
message: isVi ? `
|
|
1457
|
+
message: isVi ? `TÃnh cách & quy tắc Bot ${i + 1} (VD: siêu thân thiện, nhiá»u emoji):` : `Bot ${i + 1} persona & rules (e.g. friendly, uses emojis):`,
|
|
1458
1458
|
defaultValue: defaults.persona || '',
|
|
1459
1459
|
required: false,
|
|
1460
1460
|
},
|
|
1461
1461
|
{
|
|
1462
1462
|
key: 'token',
|
|
1463
|
-
message: isVi ? 'Bot Token (
|
|
1463
|
+
message: isVi ? 'Bot Token (từ @BotFather):' : 'Bot Token (from @BotFather):',
|
|
1464
1464
|
defaultValue: defaults.token || '',
|
|
1465
1465
|
required: true,
|
|
1466
1466
|
},
|
|
@@ -1494,25 +1494,25 @@ async function collectBotSetupStepWithGroupBack(options) {
|
|
|
1494
1494
|
const fields = [
|
|
1495
1495
|
{
|
|
1496
1496
|
key: 'name',
|
|
1497
|
-
message: isVi ? '
|
|
1497
|
+
message: isVi ? 'Tên Bot:' : 'Bot Name:',
|
|
1498
1498
|
defaultValue: defaults.name || 'Chat Bot',
|
|
1499
1499
|
required: true,
|
|
1500
1500
|
},
|
|
1501
1501
|
{
|
|
1502
1502
|
key: 'desc',
|
|
1503
|
-
message: isVi ? '
|
|
1504
|
-
defaultValue: defaults.desc || (isVi ? '
|
|
1503
|
+
message: isVi ? 'Mô tả Bot:' : 'Bot Description:',
|
|
1504
|
+
defaultValue: defaults.desc || (isVi ? 'Trợ lý AI cá nhân' : 'Personal AI assistant'),
|
|
1505
1505
|
required: true,
|
|
1506
1506
|
},
|
|
1507
1507
|
{
|
|
1508
1508
|
key: 'persona',
|
|
1509
|
-
message: isVi ? '
|
|
1509
|
+
message: isVi ? 'TÃnh cách & quy tắc (VD: gá»n gà ng, thân thiện):' : 'Persona & rules (e.g. concise, friendly):',
|
|
1510
1510
|
defaultValue: defaults.persona || '',
|
|
1511
1511
|
required: false,
|
|
1512
1512
|
},
|
|
1513
1513
|
{
|
|
1514
1514
|
key: 'token',
|
|
1515
|
-
message: isVi ? `
|
|
1515
|
+
message: isVi ? `Nháºp ${channel.name} Token:` : `Enter ${channel.name} Token:`,
|
|
1516
1516
|
defaultValue: defaults.token || '',
|
|
1517
1517
|
required: true,
|
|
1518
1518
|
},
|
|
@@ -1559,7 +1559,7 @@ async function collectProviderStep({
|
|
|
1559
1559
|
existingOllamaModel = 'gemma4:e2b',
|
|
1560
1560
|
}) {
|
|
1561
1561
|
const providerKey = await selectWithBack({
|
|
1562
|
-
message: isVi ? '
|
|
1562
|
+
message: isVi ? 'Chá»n AI Provider:' : 'Select AI Provider:',
|
|
1563
1563
|
choices: Object.entries(PROVIDERS).map(([k, v]) => ({ name: `${v.icon} ${v.name}`, value: k })),
|
|
1564
1564
|
defaultValue: existingProviderKey || undefined,
|
|
1565
1565
|
allowBack: true,
|
|
@@ -1573,7 +1573,7 @@ async function collectProviderStep({
|
|
|
1573
1573
|
let providerKeyVal = existingProviderKey === providerKey ? existingProviderKeyVal : '';
|
|
1574
1574
|
if (!provider.isProxy && !provider.isLocal) {
|
|
1575
1575
|
const keyValue = await inputWithBack({
|
|
1576
|
-
message: isVi ? `
|
|
1576
|
+
message: isVi ? `Nháºp ${provider.envKey}:` : `Enter ${provider.envKey}:`,
|
|
1577
1577
|
defaultValue: providerKeyVal,
|
|
1578
1578
|
required: true,
|
|
1579
1579
|
allowBack: true,
|
|
@@ -1588,33 +1588,33 @@ async function collectProviderStep({
|
|
|
1588
1588
|
let selectedOllamaModel = existingProviderKey === 'ollama' ? existingOllamaModel : 'gemma4:e2b';
|
|
1589
1589
|
if (providerKey === 'ollama') {
|
|
1590
1590
|
console.log(chalk.yellow(isVi
|
|
1591
|
-
? '\n
|
|
1592
|
-
: '\n
|
|
1591
|
+
? '\n💡 Gemma 4 (02/04/2026) — chá»n kÃch thước phù hợp vá»›i RAM máy bạn:'
|
|
1592
|
+
: '\n💡 Gemma 4 (April 2, 2026) — pick a size that fits your RAM:'));
|
|
1593
1593
|
const modelValue = await selectWithBack({
|
|
1594
|
-
message: isVi ? '
|
|
1594
|
+
message: isVi ? 'Chá»n model Ollama:' : 'Select Ollama model:',
|
|
1595
1595
|
choices: [
|
|
1596
1596
|
{
|
|
1597
1597
|
name: isVi
|
|
1598
|
-
? '
|
|
1599
|
-
: '
|
|
1598
|
+
? '🟢 gemma4:e2b — Nhẹ nhất (~4-6 GB RAM) — Laptop / test nhanh ★ Khuyên dùng'
|
|
1599
|
+
: '🟢 gemma4:e2b — Lightest (~4-6 GB RAM) — Laptop / fastest test ★ Recommended',
|
|
1600
1600
|
value: 'gemma4:e2b'
|
|
1601
1601
|
},
|
|
1602
1602
|
{
|
|
1603
1603
|
name: isVi
|
|
1604
|
-
? '
|
|
1605
|
-
: '
|
|
1604
|
+
? '🟡 gemma4:e4b — Cân bằng (~8-10 GB RAM) — Dùng hằng ngà y'
|
|
1605
|
+
: '🟡 gemma4:e4b — Balanced (~8-10 GB RAM) — Daily use',
|
|
1606
1606
|
value: 'gemma4:e4b'
|
|
1607
1607
|
},
|
|
1608
1608
|
{
|
|
1609
1609
|
name: isVi
|
|
1610
|
-
? '
|
|
1611
|
-
: '
|
|
1610
|
+
? '🟠gemma4:26b — Mạnh (~18-24 GB RAM/VRAM) — Máy mạnh'
|
|
1611
|
+
: '🟠gemma4:26b — Powerful (~18-24 GB RAM/VRAM) — High-end machine',
|
|
1612
1612
|
value: 'gemma4:26b'
|
|
1613
1613
|
},
|
|
1614
1614
|
{
|
|
1615
1615
|
name: isVi
|
|
1616
|
-
? '
|
|
1617
|
-
: '
|
|
1616
|
+
? '🔴 gemma4:31b — Mạnh nhất (~24+ GB RAM/VRAM) — GPU workstation'
|
|
1617
|
+
: '🔴 gemma4:31b — Most powerful (~24+ GB RAM/VRAM) — GPU workstation',
|
|
1618
1618
|
value: 'gemma4:31b'
|
|
1619
1619
|
},
|
|
1620
1620
|
],
|
|
@@ -1650,7 +1650,7 @@ async function collectSkillsStep({
|
|
|
1650
1650
|
existingSmtpPass = '',
|
|
1651
1651
|
}) {
|
|
1652
1652
|
const selectedSkills = await checkboxWithBack({
|
|
1653
|
-
message: isVi ? '
|
|
1653
|
+
message: isVi ? 'Báºt tÃnh năng bổ sung (Space để chá»n):' : 'Enable extra skills (Space to select):',
|
|
1654
1654
|
choices: getCliSkillChoices({ providerKey, isVi }).map((choice) => ({
|
|
1655
1655
|
...choice,
|
|
1656
1656
|
checked: existingSelectedSkills.includes(choice.value),
|
|
@@ -1666,18 +1666,18 @@ async function collectSkillsStep({
|
|
|
1666
1666
|
if (selectedSkills.includes('browser')) {
|
|
1667
1667
|
const isLinux = process.platform === 'linux';
|
|
1668
1668
|
const browserValue = await selectWithBack({
|
|
1669
|
-
message: isVi ? '
|
|
1669
|
+
message: isVi ? 'Chế độ Browser Automation:' : 'Browser Automation mode:',
|
|
1670
1670
|
choices: [
|
|
1671
1671
|
{
|
|
1672
1672
|
name: isVi
|
|
1673
|
-
? '
|
|
1674
|
-
: '
|
|
1673
|
+
? 'ðŸ–¥ï¸ Dùng Chrome trên máy tÃnh (Windows/Mac — Bypass Cloudflare tốt hÆ¡n)'
|
|
1674
|
+
: 'ðŸ–¥ï¸ Use Host Chrome (Windows/Mac — Better Cloudflare bypass)',
|
|
1675
1675
|
value: 'desktop'
|
|
1676
1676
|
},
|
|
1677
1677
|
{
|
|
1678
1678
|
name: isVi
|
|
1679
|
-
? '
|
|
1680
|
-
: '
|
|
1679
|
+
? '🳠Headless Chromium trong Docker (Ubuntu Server / VPS — không cần GUI)'
|
|
1680
|
+
: '🳠Headless Chromium inside Docker (Ubuntu Server / VPS — No GUI)',
|
|
1681
1681
|
value: 'server'
|
|
1682
1682
|
}
|
|
1683
1683
|
],
|
|
@@ -1697,7 +1697,7 @@ async function collectSkillsStep({
|
|
|
1697
1697
|
let ttsElevenKey = existingTtsElevenKey;
|
|
1698
1698
|
if (selectedSkills.includes('tts')) {
|
|
1699
1699
|
const openaiKey = await inputWithBack({
|
|
1700
|
-
message: isVi ? '
|
|
1700
|
+
message: isVi ? 'Nháºp OPENAI_API_KEY (cho TTS, bá» trống nếu dùng ElevenLabs):' : 'Enter OPENAI_API_KEY (for TTS, leave empty for ElevenLabs):',
|
|
1701
1701
|
defaultValue: ttsOpenaiKey,
|
|
1702
1702
|
allowBack: true,
|
|
1703
1703
|
isVi,
|
|
@@ -1708,7 +1708,7 @@ async function collectSkillsStep({
|
|
|
1708
1708
|
ttsOpenaiKey = openaiKey;
|
|
1709
1709
|
|
|
1710
1710
|
const elevenKey = await inputWithBack({
|
|
1711
|
-
message: isVi ? '
|
|
1711
|
+
message: isVi ? 'Nháºp ELEVENLABS_API_KEY (hoặc bá» trống):' : 'Enter ELEVENLABS_API_KEY (or leave empty):',
|
|
1712
1712
|
defaultValue: ttsElevenKey,
|
|
1713
1713
|
allowBack: true,
|
|
1714
1714
|
isVi,
|
|
@@ -1795,7 +1795,7 @@ async function collectSkillsStep({
|
|
|
1795
1795
|
}
|
|
1796
1796
|
|
|
1797
1797
|
|
|
1798
|
-
//
|
|
1798
|
+
// ─── Shared workspace file writer ─────────────────────────────────────────────
|
|
1799
1799
|
// Used by both native and docker flows to write .md + browser files consistently.
|
|
1800
1800
|
async function writeWorkspaceFiles({
|
|
1801
1801
|
workspaceDir, // absolute path to the workspace dir
|
|
@@ -1815,6 +1815,7 @@ async function writeWorkspaceFiles({
|
|
|
1815
1815
|
agentWorkspaceDir = 'workspace',
|
|
1816
1816
|
isRelayBot = false,
|
|
1817
1817
|
replyToDirectMessages = true,
|
|
1818
|
+
channelKey = 'telegram',
|
|
1818
1819
|
}) {
|
|
1819
1820
|
const skillListStr = SKILLS
|
|
1820
1821
|
.filter((s) => selectedSkills.includes(s.value))
|
|
@@ -1824,7 +1825,7 @@ async function writeWorkspaceFiles({
|
|
|
1824
1825
|
? `- ${label}${s.slug ? ` (${s.slug})` : ' (native)'}`
|
|
1825
1826
|
: `- **${label}**${s.slug ? ` (${s.slug})` : ' (native)'}`;
|
|
1826
1827
|
})
|
|
1827
|
-
.join('\n') || (isVi ? '- _(
|
|
1828
|
+
.join('\n') || (isVi ? '- _(Chưa có skill nà o)_' : '- _(No skills installed)_');
|
|
1828
1829
|
|
|
1829
1830
|
const workspacePath = `.openclaw/${agentWorkspaceDir}/`;
|
|
1830
1831
|
const teamRosterFormatted = teamRoster
|
|
@@ -1916,9 +1917,9 @@ async function main() {
|
|
|
1916
1917
|
while (true) {
|
|
1917
1918
|
if (setupStep === 'language') {
|
|
1918
1919
|
lang = await select({
|
|
1919
|
-
message: 'Select language /
|
|
1920
|
+
message: 'Select language / Chá»n ngôn ngữ:',
|
|
1920
1921
|
choices: [
|
|
1921
|
-
{ name: '
|
|
1922
|
+
{ name: 'Tiếng Việt', value: 'vi' },
|
|
1922
1923
|
{ name: 'English', value: 'en' }
|
|
1923
1924
|
],
|
|
1924
1925
|
default: lang
|
|
@@ -1930,12 +1931,12 @@ async function main() {
|
|
|
1930
1931
|
|
|
1931
1932
|
if (setupStep === 'os') {
|
|
1932
1933
|
const nextOsChoice = await selectWithBack({
|
|
1933
|
-
message: isVi ? '
|
|
1934
|
+
message: isVi ? 'Bạn Ä‘ang chạy trên hệ Ä‘iá»u hà nh nà o?' : 'What OS are you running on?',
|
|
1934
1935
|
choices: [
|
|
1935
|
-
{ name: isVi ? '
|
|
1936
|
-
{ name: isVi ? '
|
|
1937
|
-
{ name: isVi ? '
|
|
1938
|
-
{ name: isVi ? '
|
|
1936
|
+
{ name: isVi ? '🪟 Windows' : '🪟 Windows', value: 'windows' },
|
|
1937
|
+
{ name: isVi ? '🎠macOS' : '🎠macOS', value: 'macos' },
|
|
1938
|
+
{ name: isVi ? '🧠Ubuntu Desktop' : '🧠Ubuntu Desktop', value: 'ubuntu' },
|
|
1939
|
+
{ name: isVi ? 'ðŸ–¥ï¸ VPS / Ubuntu Server' : 'ðŸ–¥ï¸ VPS / Ubuntu Server', value: 'vps' },
|
|
1939
1940
|
],
|
|
1940
1941
|
defaultValue: osChoice,
|
|
1941
1942
|
allowBack: true,
|
|
@@ -1953,10 +1954,10 @@ async function main() {
|
|
|
1953
1954
|
if (setupStep === 'deploy') {
|
|
1954
1955
|
const deployModeDefault = (osChoice === 'ubuntu' || osChoice === 'vps') ? 'native' : 'docker';
|
|
1955
1956
|
const nextDeployMode = await selectWithBack({
|
|
1956
|
-
message: isVi ? '
|
|
1957
|
+
message: isVi ? 'Chá»n cách chạy bot:' : 'How do you want to run the bot?',
|
|
1957
1958
|
choices: [
|
|
1958
|
-
{ name: isVi ? '
|
|
1959
|
-
{ name: isVi ? '
|
|
1959
|
+
{ name: isVi ? '🳠Docker (Khuyên dùng cho Windows / macOS — dễ cà i, chạy ngay)' : '🳠Docker (Recommended for Windows / macOS — easy setup, runs immediately)', value: 'docker' },
|
|
1960
|
+
{ name: isVi ? 'âš¡ Native / PM2 (Khuyên dùng cho Ubuntu / VPS — Ãt RAM, ổn định hÆ¡n)' : 'âš¡ Native / PM2 (Recommended for Ubuntu / VPS — less RAM, more stable)', value: 'native' }
|
|
1960
1961
|
],
|
|
1961
1962
|
defaultValue: deployMode || deployModeDefault,
|
|
1962
1963
|
allowBack: true,
|
|
@@ -1968,24 +1969,24 @@ async function main() {
|
|
|
1968
1969
|
}
|
|
1969
1970
|
deployMode = nextDeployMode;
|
|
1970
1971
|
if (deployMode === 'docker' && !isDockerInstalled()) {
|
|
1971
|
-
console.log(chalk.cyan(isVi ? '\n
|
|
1972
|
+
console.log(chalk.cyan(isVi ? '\n🳠Docker chưa được cà i — đang tự động cà i Docker Engine + Compose plugin...' : '\n🳠Docker not found — auto-installing Docker Engine + Compose plugin...'));
|
|
1972
1973
|
try {
|
|
1973
1974
|
const platform = process.platform;
|
|
1974
1975
|
if (platform === 'win32') {
|
|
1975
1976
|
execSync('winget install -e --id Docker.DockerDesktop --accept-source-agreements --accept-package-agreements', { stdio: 'inherit' });
|
|
1976
|
-
console.log(chalk.green(isVi ? '
|
|
1977
|
+
console.log(chalk.green(isVi ? '✅ Docker Desktop đã cà i xong. Vui lòng mở Docker Desktop, đợi khởi động (icon tray chuyển xanh) rồi chạy lại lệnh nà y.' : '✅ Docker Desktop installed. Open Docker Desktop, wait for it to start (tray icon turns green), then re-run this command.'));
|
|
1977
1978
|
process.exit(0);
|
|
1978
1979
|
} else if (platform === 'darwin') {
|
|
1979
1980
|
execSync('brew install --cask docker', { stdio: 'inherit' });
|
|
1980
|
-
console.log(chalk.green(isVi ? '
|
|
1981
|
+
console.log(chalk.green(isVi ? '✅ Docker Desktop cà i xong qua Homebrew. Mở Docker Desktop, đợi khởi động rồi chạy lại lệnh nà y.' : '✅ Docker Desktop installed via Homebrew. Open Docker Desktop, wait for it to start, then re-run this command.'));
|
|
1981
1982
|
process.exit(0);
|
|
1982
1983
|
} else {
|
|
1983
1984
|
execSync('curl -fsSL https://get.docker.com | sh', { stdio: 'inherit', shell: true });
|
|
1984
1985
|
try { execSync('apt-get install -y docker-compose-plugin', { stdio: 'ignore', shell: true }); } catch {}
|
|
1985
|
-
console.log(chalk.green(isVi ? '
|
|
1986
|
+
console.log(chalk.green(isVi ? '✅ Docker Engine + Compose plugin đã cà i xong.' : '✅ Docker Engine + Compose plugin installed.'));
|
|
1986
1987
|
}
|
|
1987
1988
|
} catch {
|
|
1988
|
-
console.log(chalk.red(isVi ? '
|
|
1989
|
+
console.log(chalk.red(isVi ? '⌠Không thể tự cà i Docker. Tải thủ công: https://www.docker.com/products/docker-desktop/' : '⌠Could not auto-install Docker. Download manually: https://www.docker.com/products/docker-desktop/'));
|
|
1989
1990
|
process.exit(1);
|
|
1990
1991
|
}
|
|
1991
1992
|
}
|
|
@@ -1995,7 +1996,7 @@ async function main() {
|
|
|
1995
1996
|
|
|
1996
1997
|
if (setupStep === 'channel') {
|
|
1997
1998
|
const nextChannelKey = await selectWithBack({
|
|
1998
|
-
message: isVi ? '
|
|
1999
|
+
message: isVi ? 'Chá»n ná»n tảng bot:' : 'Select bot platform:',
|
|
1999
2000
|
choices: Object.entries(CHANNELS).map(([k, v]) => ({ name: v.icon + ' ' + v.name, value: k })),
|
|
2000
2001
|
defaultValue: channelKey,
|
|
2001
2002
|
allowBack: true,
|
|
@@ -2008,7 +2009,7 @@ async function main() {
|
|
|
2008
2009
|
channelKey = nextChannelKey;
|
|
2009
2010
|
channel = CHANNELS[channelKey];
|
|
2010
2011
|
if (channelKey === 'zalo-bot') {
|
|
2011
|
-
console.log(chalk.yellow('\
|
|
2012
|
+
console.log(chalk.yellow('\nâš ï¸ ' + (isVi ? 'LƯU Ã: Zalo OA Bot yêu cầu phải thiết láºp Webhook Public (qua VPS/ngrok có HTTPS). Hãy dùng Zalo Personal nếu bạn chưa có Webhook.' : 'NOTE: Zalo OA requires a Public Webhook (via VPS/ngrok with HTTPS). Use Zalo Personal if you do not have one.')));
|
|
2012
2013
|
}
|
|
2013
2014
|
setupStep = 'botSetup';
|
|
2014
2015
|
continue;
|
|
@@ -2029,8 +2030,8 @@ async function main() {
|
|
|
2029
2030
|
}
|
|
2030
2031
|
|
|
2031
2032
|
if (setupStep === 'userInfo') {
|
|
2032
|
-
console.log(chalk.bold('\n' + (isVi ? '
|
|
2033
|
-
const nextUserInfo = await inputWithBack({ message: isVi ? '
|
|
2033
|
+
console.log(chalk.bold('\n' + (isVi ? '👤 Thông tin của bạn 👤' : '👤 About You 👤')));
|
|
2034
|
+
const nextUserInfo = await inputWithBack({ message: isVi ? '👤 Thông tin vá» bạn (tên bạn, ngôn ngữ, múi giá», sở thÃch...):' : '👤 About you (your name, language, timezone, interests...):', defaultValue: userInfo, required: true, allowBack: true, isVi });
|
|
2034
2035
|
if (nextUserInfo === CLI_BACK) {
|
|
2035
2036
|
setupStep = 'botSetup';
|
|
2036
2037
|
continue;
|
|
@@ -2073,7 +2074,7 @@ async function main() {
|
|
|
2073
2074
|
}
|
|
2074
2075
|
|
|
2075
2076
|
if (setupStep === 'projectDir') {
|
|
2076
|
-
const nextProjectDir = await inputWithBack({ message: isVi ? '
|
|
2077
|
+
const nextProjectDir = await inputWithBack({ message: isVi ? 'Thư mục cà i đặt project:' : 'Project install directory:', defaultValue: projectDir, allowBack: true, isVi });
|
|
2077
2078
|
if (nextProjectDir === CLI_BACK) {
|
|
2078
2079
|
setupStep = 'skills';
|
|
2079
2080
|
continue;
|
|
@@ -2091,12 +2092,12 @@ async function main() {
|
|
|
2091
2092
|
const modelsPrimary = providerKey === 'ollama' ? selectedOllamaModel : providerKey === '9router' ? 'smart-route' : provider.models?.[0]?.id || 'gpt-4o-mini';
|
|
2092
2093
|
const hasBrowserDesktop = selectedSkills.includes('browser') && browserMode === 'desktop';
|
|
2093
2094
|
const hasBrowserServer = selectedSkills.includes('browser') && browserMode === 'server';
|
|
2094
|
-
console.log(chalk.cyan(`\n
|
|
2095
|
+
console.log(chalk.cyan(`\n🚀 ${isVi ? 'Äang tạo thư mục và file cấu hình...' : 'Generating directories and configurations...'}`));
|
|
2095
2096
|
|
|
2096
2097
|
await fs.ensureDir(projectDir);
|
|
2097
2098
|
|
|
2098
2099
|
|
|
2099
|
-
//
|
|
2100
|
+
// ─── Helper: build .env content per bot ──────────────────────────────────
|
|
2100
2101
|
|
|
2101
2102
|
function buildEnvContentForBot(botIndex) {
|
|
2102
2103
|
const tok = bots[botIndex]?.token || botToken;
|
|
@@ -2137,7 +2138,7 @@ async function main() {
|
|
|
2137
2138
|
});
|
|
2138
2139
|
}
|
|
2139
2140
|
|
|
2140
|
-
//
|
|
2141
|
+
// ─── Create directories and write .env files ─────────────────────────────
|
|
2141
2142
|
if (isMultiBot) {
|
|
2142
2143
|
await fs.ensureDir(path.join(projectDir, '.openclaw'));
|
|
2143
2144
|
if (deployMode === 'docker') {
|
|
@@ -2156,7 +2157,7 @@ async function main() {
|
|
|
2156
2157
|
}
|
|
2157
2158
|
|
|
2158
2159
|
|
|
2159
|
-
//
|
|
2160
|
+
// ── Docker artifacts: Dockerfile + docker-compose via shared buildDockerArtifacts() ──────
|
|
2160
2161
|
const skillSlugs = SKILLS
|
|
2161
2162
|
.filter(s => selectedSkills.includes(s.value) && s.slug)
|
|
2162
2163
|
.map(s => s.slug);
|
|
@@ -2309,7 +2310,7 @@ async function main() {
|
|
|
2309
2310
|
);
|
|
2310
2311
|
// Generate ecosystem.config.js for PM2 native multi-bot
|
|
2311
2312
|
if (deployMode === 'native') {
|
|
2312
|
-
// Also write config to ~/.openclaw/
|
|
2313
|
+
// Also write config to ~/.openclaw/ — openclaw binary on Linux/Mac reads from home dir
|
|
2313
2314
|
const homeClawDir = path.join(os.homedir(), '.openclaw');
|
|
2314
2315
|
await fs.ensureDir(homeClawDir);
|
|
2315
2316
|
const homeConfig = JSON.parse(JSON.stringify(sharedConfig));
|
|
@@ -2340,7 +2341,7 @@ async function main() {
|
|
|
2340
2341
|
' }',
|
|
2341
2342
|
].join('\n');
|
|
2342
2343
|
const ecosystemContent = [
|
|
2343
|
-
'// PM2 ecosystem
|
|
2344
|
+
'// PM2 ecosystem — run: pm2 start ecosystem.config.js',
|
|
2344
2345
|
'module.exports = {',
|
|
2345
2346
|
' apps: [',
|
|
2346
2347
|
pm2Apps,
|
|
@@ -2371,12 +2372,12 @@ async function main() {
|
|
|
2371
2372
|
.map((peer) => ({ name: peer.name, agentId: peer.agentId }));
|
|
2372
2373
|
|
|
2373
2374
|
// agentYaml & auth still needed, keep non-workspace writes here
|
|
2374
|
-
// .yaml removed
|
|
2375
|
+
// .yaml removed — OpenClaw reads config exclusively from openclaw.json
|
|
2375
2376
|
if (Object.keys(authProfilesJson).length > 0) {
|
|
2376
2377
|
await fs.writeJson(path.join(rootClawDir, 'agents', meta.agentId, 'agent', 'auth-profiles.json'), authProfilesJson, { spaces: 2 });
|
|
2377
2378
|
}
|
|
2378
2379
|
|
|
2379
|
-
//
|
|
2380
|
+
// ── Workspace files via shared helper ────────────────────────────────
|
|
2380
2381
|
await writeWorkspaceFiles({
|
|
2381
2382
|
workspaceDir,
|
|
2382
2383
|
isVi, botName: meta.name, botDesc: meta.desc, persona: meta.persona,
|
|
@@ -2387,6 +2388,7 @@ async function main() {
|
|
|
2387
2388
|
agentWorkspaceDir: meta.workspaceDir,
|
|
2388
2389
|
isRelayBot: true,
|
|
2389
2390
|
replyToDirectMessages: true,
|
|
2391
|
+
channelKey,
|
|
2390
2392
|
});
|
|
2391
2393
|
}
|
|
2392
2394
|
} else {
|
|
@@ -2443,7 +2445,7 @@ async function main() {
|
|
|
2443
2445
|
|
|
2444
2446
|
await fs.writeJson(path.join(loopBotDir, '.openclaw', 'openclaw.json'), botConfig, { spaces: 2 });
|
|
2445
2447
|
|
|
2446
|
-
//
|
|
2448
|
+
// ── Workspace files: use shared writeWorkspaceFiles() ──────────────────────
|
|
2447
2449
|
const dockerWorkspaceDir = path.join(loopBotDir, '.openclaw', loopWorkspaceDir);
|
|
2448
2450
|
const dockerOwnAliases = [loopBotName, bots[bIndex]?.slashCmd || '', `bot ${bIndex + 1}`].filter(Boolean);
|
|
2449
2451
|
const dockerOtherAgents = teamRoster
|
|
@@ -2468,23 +2470,24 @@ async function main() {
|
|
|
2468
2470
|
agentWorkspaceDir: loopWorkspaceDir,
|
|
2469
2471
|
isRelayBot: isMultiBot,
|
|
2470
2472
|
replyToDirectMessages: true,
|
|
2473
|
+
channelKey,
|
|
2471
2474
|
});
|
|
2472
2475
|
|
|
2473
2476
|
if (isMultiBot) {
|
|
2474
2477
|
// Append per-bot reply rules to AGENTS.md
|
|
2475
2478
|
const otherBotNames = teamRoster.filter((p) => p.idx !== bIndex).map((p) => p.name);
|
|
2476
2479
|
const extraAgentsMd = isVi
|
|
2477
|
-
? `\n\n## Khi nao nen tra loi\n- Neu metadata khong noi ro day la group/supergroup, mac dinh xem la chat rieng/DM va tra loi binh thuong.\n- Trong group, chi tra loi khi tin nhan co alias cua ban: ${dockerOwnAliases.map((a) => `\`${a}\``).join(', ')} hoac username Telegram cua ban.\n- Quy tac im lang khi tin nhan khong goi ban chi ap dung cho group chat, khong ap dung cho DM/chat rieng.\n- Neu group message chi goi ro bot khac ${otherBotNames.length ? otherBotNames.map((n) => `\`${n}\``).join(', ') : '`bot khac`'} thi khong cuop loi.\n-
|
|
2478
|
-
: `\n\n## When To Reply\n- If metadata does not clearly say this is a group/supergroup, treat it as a private DM and reply normally.\n- In group chats, only reply when the message contains one of your aliases: ${dockerOwnAliases.map((a) => `\`${a}\``).join(', ')} or your Telegram username.\n- The stay-silent rule for messages not addressed to you applies only to group chats, never to DMs/private chats.\n- If a group message is clearly calling another bot such as ${otherBotNames.length ? otherBotNames.map((n) => `\`${n}\``).join(', ') : '`another bot`'}, do not hijack it.\n- Before EVERY user-visible Telegram reply, if the current turn exposes a native reaction tool/action, use it to add
|
|
2480
|
+
? `\n\n## Khi nao nen tra loi\n- Neu metadata khong noi ro day la group/supergroup, mac dinh xem la chat rieng/DM va tra loi binh thuong.\n- Trong group, chi tra loi khi tin nhan co alias cua ban: ${dockerOwnAliases.map((a) => `\`${a}\``).join(', ')} hoac username Telegram cua ban.\n- Quy tac im lang khi tin nhan khong goi ban chi ap dung cho group chat, khong ap dung cho DM/chat rieng.\n- Neu group message chi goi ro bot khac ${otherBotNames.length ? otherBotNames.map((n) => `\`${n}\``).join(', ') : '`bot khac`'} thi khong cuop loi.\n- Trước Má»–I phản hồi Telegram nhìn thấy bởi user, nếu turn hiện tại có tool/action reaction native thì ưu tiên dùng nó để thả \`ðŸ‘\` trước, trừ khi \`ðŸ‘\` đã có sẵn.\n- Khong duoc noi la minh da tha like neu turn hien tai chua goi duoc tool reaction hoac reaction khong hien that tren chat.\n- Khi can phoi hop noi bo, dung dung agent id ky thuat trong \`AGENTS.md\`, khong dung ten hien thi.\n- Khi hoi ve vai tro cac bot, dung \`AGENTS.md\` lam nguon su that.`
|
|
2481
|
+
: `\n\n## When To Reply\n- If metadata does not clearly say this is a group/supergroup, treat it as a private DM and reply normally.\n- In group chats, only reply when the message contains one of your aliases: ${dockerOwnAliases.map((a) => `\`${a}\``).join(', ')} or your Telegram username.\n- The stay-silent rule for messages not addressed to you applies only to group chats, never to DMs/private chats.\n- If a group message is clearly calling another bot such as ${otherBotNames.length ? otherBotNames.map((n) => `\`${n}\``).join(', ') : '`another bot`'}, do not hijack it.\n- Before EVERY user-visible Telegram reply, if the current turn exposes a native reaction tool/action, use it to add \`ðŸ‘\` first unless \`ðŸ‘\` is already present.\n- Never claim that you already reacted unless the current turn actually executed the reaction and it is visible on the chat.\n- When you need internal coordination, use the exact technical agent id from \`AGENTS.md\`, not the display name.\n- Use \`AGENTS.md\` as the source of truth for team roles.`;
|
|
2479
2482
|
await fs.appendFile(path.join(dockerWorkspaceDir, 'AGENTS.md'), extraAgentsMd);
|
|
2480
2483
|
}
|
|
2481
2484
|
} // END FOR LOOP
|
|
2482
2485
|
}
|
|
2483
2486
|
|
|
2484
|
-
//
|
|
2487
|
+
// ── Chrome Debug scripts — via shared builder (same content as wizard ZIP) ─
|
|
2485
2488
|
await writeGeneratedArtifacts(projectDir, buildCliChromeDebugArtifacts());
|
|
2486
2489
|
|
|
2487
|
-
//
|
|
2490
|
+
// ── Uninstall scripts ───────────────────────────────────────────────────────
|
|
2488
2491
|
await writeGeneratedArtifacts(projectDir, buildCliUninstallArtifacts({
|
|
2489
2492
|
deployMode,
|
|
2490
2493
|
osChoice: detectedOS,
|
|
@@ -2494,10 +2497,10 @@ async function main() {
|
|
|
2494
2497
|
: botName,
|
|
2495
2498
|
}));
|
|
2496
2499
|
|
|
2497
|
-
//
|
|
2500
|
+
// ── Upgrade scripts ─────────────────────────────────────────────────────────
|
|
2498
2501
|
await writeGeneratedArtifacts(projectDir, buildCliUpgradeArtifacts());
|
|
2499
2502
|
|
|
2500
|
-
//
|
|
2503
|
+
// ── start-bot.bat / start-bot.sh — one-click restart scripts ─────────────
|
|
2501
2504
|
// Generated for native deployments only (docker has docker compose up)
|
|
2502
2505
|
if (deployMode !== 'docker') {
|
|
2503
2506
|
await writeGeneratedArtifacts(projectDir, buildCliStartBotArtifacts({
|
|
@@ -2513,31 +2516,31 @@ async function main() {
|
|
|
2513
2516
|
console.log(chalk.cyan(
|
|
2514
2517
|
process.platform === 'win32'
|
|
2515
2518
|
? (isVi
|
|
2516
|
-
? `\n
|
|
2517
|
-
: `\n
|
|
2519
|
+
? `\n🚀 start-bot.bat / start-bot.sh đã tạo — double-click để restart bot.`
|
|
2520
|
+
: `\n🚀 start-bot.bat / start-bot.sh created — double-click to restart the bot.`)
|
|
2518
2521
|
: (isVi
|
|
2519
|
-
? `\n
|
|
2520
|
-
: `\n
|
|
2522
|
+
? `\n🚀 start-bot.sh đã tạo — chạy ./start-bot.sh để restart bot.`
|
|
2523
|
+
: `\n🚀 start-bot.sh created — run ./start-bot.sh to restart the bot.`)
|
|
2521
2524
|
));
|
|
2522
2525
|
}
|
|
2523
2526
|
|
|
2524
|
-
console.log(chalk.green(
|
|
2527
|
+
console.log(chalk.green(`✅ ${isVi ? 'Tạo cấu hình thà nh công!' : 'Configs created successfully!'}`));
|
|
2525
2528
|
|
|
2526
2529
|
installLatestOpenClaw({ isVi, osChoice });
|
|
2527
2530
|
|
|
2528
2531
|
// 7. Auto Run
|
|
2529
2532
|
const autoRun = deployMode === 'docker' ? await confirm({
|
|
2530
|
-
message: isVi ? '
|
|
2533
|
+
message: isVi ? 'Bạn có muốn tự động build Docker và khởi động Bot luôn không?' : 'Do you want to run Docker compose and start the bot now?',
|
|
2531
2534
|
default: true
|
|
2532
2535
|
}) : false;
|
|
2533
2536
|
|
|
2534
2537
|
if (deployMode === 'docker' && autoRun) {
|
|
2535
|
-
console.log(chalk.yellow(`\n
|
|
2538
|
+
console.log(chalk.yellow(`\n🳠${isVi ? 'Äang khởi động Docker (có thể mất và i phút)...' : 'Starting Docker (might take a few minutes)...'}`));
|
|
2536
2539
|
const dockerPath = path.join(projectDir, 'docker', 'openclaw');
|
|
2537
2540
|
|
|
2538
2541
|
// Auto-detect Docker Compose V2 (plugin) vs V1 (standalone docker-compose).
|
|
2539
2542
|
// On Ubuntu 24.04 installed via `apt install docker.io`, the Compose V2 plugin
|
|
2540
|
-
// is NOT included
|
|
2543
|
+
// is NOT included — `docker compose` subcommand may not exist or may be broken.
|
|
2541
2544
|
// We test both and use whichever actually works.
|
|
2542
2545
|
let composeCmd, composeArgs;
|
|
2543
2546
|
const detectCompose = () => {
|
|
@@ -2556,8 +2559,8 @@ async function main() {
|
|
|
2556
2559
|
const detected = detectCompose();
|
|
2557
2560
|
if (!detected) {
|
|
2558
2561
|
console.log(chalk.red(isVi
|
|
2559
|
-
? '\n
|
|
2560
|
-
: '\n
|
|
2562
|
+
? '\n⌠Không tìm thấy Docker Compose!\n Cà i bằng lệnh: sudo apt-get install docker-compose-plugin'
|
|
2563
|
+
: '\n⌠Docker Compose not found!\n Install: sudo apt-get install docker-compose-plugin'));
|
|
2561
2564
|
process.exit(1);
|
|
2562
2565
|
}
|
|
2563
2566
|
composeCmd = detected.cmd;
|
|
@@ -2570,87 +2573,87 @@ async function main() {
|
|
|
2570
2573
|
|
|
2571
2574
|
child.on('close', (code) => {
|
|
2572
2575
|
if (code === 0) {
|
|
2573
|
-
console.log(chalk.green(`\n
|
|
2576
|
+
console.log(chalk.green(`\n🎉 ${isVi ? 'Setup hoà n tất! Bot đang chạy.' : 'Setup complete! Bot is running.'}`));
|
|
2574
2577
|
|
|
2575
2578
|
if (providerKey === '9router') {
|
|
2576
|
-
console.log(chalk.yellow(`\n
|
|
2579
|
+
console.log(chalk.yellow(`\n🔀 ${isVi
|
|
2577
2580
|
? '9Router Dashboard: http://localhost:20128/dashboard'
|
|
2578
2581
|
: '9Router Dashboard: http://localhost:20128/dashboard'}`));
|
|
2579
2582
|
console.log(chalk.gray(isVi
|
|
2580
|
-
? '
|
|
2581
|
-
: '
|
|
2583
|
+
? ' → Mở dashboard → đăng nháºp OAuth để kết nối các Provider (iFlow, Gemini CLI, Claude Code...)'
|
|
2584
|
+
: ' → Open dashboard → OAuth login to connect Providers (iFlow, Gemini CLI, Claude Code...)'));
|
|
2582
2585
|
console.log(chalk.gray(isVi
|
|
2583
|
-
? '
|
|
2584
|
-
: '
|
|
2586
|
+
? ' → Sau khi kết nối provider, bot sẽ tự động hoạt động qua combo "smart-route"'
|
|
2587
|
+
: ' → After connecting providers, bot works automatically via "smart-route" combo'));
|
|
2585
2588
|
}
|
|
2586
2589
|
|
|
2587
2590
|
if (channelKey === 'telegram') {
|
|
2588
|
-
console.log(chalk.cyan(`\n
|
|
2589
|
-
? '
|
|
2591
|
+
console.log(chalk.cyan(`\n💬 ${isVi
|
|
2592
|
+
? 'Nhắn tin cho bot trên Telegram là dùng được ngay!'
|
|
2590
2593
|
: 'Just message your bot on Telegram to start chatting!'}`));
|
|
2591
2594
|
if (isMultiBot) {
|
|
2592
|
-
console.log(chalk.yellow(`\n${isVi ? '
|
|
2595
|
+
console.log(chalk.yellow(`\n${isVi ? '📋 Äá»c hướng dẫn setup bot trong Group:' : '📋 Read setup guide in Group:'} ${TELEGRAM_SETUP_GUIDE_FILENAME}`));
|
|
2593
2596
|
console.log(chalk.gray(isVi
|
|
2594
|
-
? '
|
|
2595
|
-
: '
|
|
2597
|
+
? ' → Chạy scripts/telegram-post-install-check.mjs để lấy link tháºt, kiểm tra group/privacy, rồi má»›i add bot và Disable privacy mode.'
|
|
2598
|
+
: ' → Run scripts/telegram-post-install-check.mjs to get the real links, verify group/privacy, then add the bots and disable privacy mode.'));
|
|
2596
2599
|
}
|
|
2597
2600
|
} else if (channelKey === 'zalo-personal') {
|
|
2598
2601
|
printZaloPersonalLoginInfo({ isVi, deployMode: 'docker', projectDir });
|
|
2599
2602
|
}
|
|
2600
2603
|
} else {
|
|
2601
|
-
console.log(chalk.red(`\n
|
|
2604
|
+
console.log(chalk.red(`\n⌠Docker exited with code ${code}`));
|
|
2602
2605
|
console.log(chalk.yellow(isVi
|
|
2603
|
-
? `\n
|
|
2604
|
-
: `\n
|
|
2606
|
+
? `\n💡 Nếu lỗi "unknown shorthand flag", chạy: sudo apt-get install docker-compose-plugin\n Rồi thỠlại: cd ${dockerPath} && docker compose up -d --build`
|
|
2607
|
+
: `\n💡 If "unknown shorthand flag" error, run: sudo apt-get install docker-compose-plugin\n Then retry: cd ${dockerPath} && docker compose up -d --build`));
|
|
2605
2608
|
}
|
|
2606
2609
|
});
|
|
2607
2610
|
|
|
2608
2611
|
}
|
|
2609
2612
|
if (deployMode === 'docker') {
|
|
2610
2613
|
|
|
2611
|
-
//
|
|
2614
|
+
// ── Auto-install openclaw binary if not present ──────────────────────────
|
|
2612
2615
|
const isOpenClawInstalled = () => { try { execSync('openclaw --version', { stdio: 'ignore' }); return true; } catch { return false; } };
|
|
2613
2616
|
if (!isOpenClawInstalled()) {
|
|
2614
2617
|
console.log(chalk.cyan(isVi
|
|
2615
|
-
? `\n
|
|
2616
|
-
: `\n
|
|
2618
|
+
? `\n📦 Äang cà i openclaw binary (${OPENCLAW_NPM_SPEC})...`
|
|
2619
|
+
: `\n📦 Installing openclaw binary (${OPENCLAW_NPM_SPEC})...`));
|
|
2617
2620
|
try {
|
|
2618
2621
|
execSync(`npm install -g ${OPENCLAW_NPM_SPEC}`, { stdio: 'inherit' });
|
|
2619
|
-
console.log(chalk.green(isVi ? '
|
|
2622
|
+
console.log(chalk.green(isVi ? '✅ openclaw đã cà i xong!' : '✅ openclaw installed!'));
|
|
2620
2623
|
} catch {
|
|
2621
2624
|
console.log(chalk.yellow(isVi
|
|
2622
|
-
?
|
|
2623
|
-
:
|
|
2625
|
+
? `âš ï¸ Không tá»± cà i được. Chạy thá»§ công: sudo npm install -g ${OPENCLAW_NPM_SPEC}`
|
|
2626
|
+
: `âš ï¸ Could not auto-install. Run manually: sudo npm install -g ${OPENCLAW_NPM_SPEC}`));
|
|
2624
2627
|
}
|
|
2625
2628
|
}
|
|
2626
2629
|
|
|
2627
2630
|
if (isMultiBot && channelKey === 'telegram') {
|
|
2628
|
-
console.log(chalk.yellow(`\n${isVi ? '
|
|
2631
|
+
console.log(chalk.yellow(`\n${isVi ? '📋 Xem hướng dẫn sau cà i:' : '📋 Read post-install guide:'} ${path.join(projectDir, TELEGRAM_SETUP_GUIDE_FILENAME)}`));
|
|
2629
2632
|
}
|
|
2630
2633
|
} else {
|
|
2631
2634
|
if (!isOpenClawInstalled()) {
|
|
2632
2635
|
console.log(chalk.cyan(isVi
|
|
2633
|
-
? `\n
|
|
2634
|
-
: `\n
|
|
2636
|
+
? `\n📦 Dang cai openclaw binary (${OPENCLAW_NPM_SPEC})...`
|
|
2637
|
+
: `\n📦 Installing openclaw binary (${OPENCLAW_NPM_SPEC})...`));
|
|
2635
2638
|
if (!installGlobalPackage(OPENCLAW_NPM_SPEC, { isVi, osChoice, displayName: 'openclaw' })) {
|
|
2636
2639
|
process.exit(1);
|
|
2637
2640
|
}
|
|
2638
|
-
console.log(chalk.green(isVi ? '
|
|
2641
|
+
console.log(chalk.green(isVi ? '✅ openclaw da cai xong!' : '✅ openclaw installed!'));
|
|
2639
2642
|
}
|
|
2640
2643
|
|
|
2641
2644
|
if (providerKey === '9router') {
|
|
2642
2645
|
if (shouldReuseInstalledGlobals() && is9RouterInstalled()) {
|
|
2643
2646
|
console.log(chalk.green(isVi
|
|
2644
|
-
? '\n
|
|
2645
|
-
: '\n
|
|
2647
|
+
? '\nâ™»ï¸ Dang dung lai 9Router da cai san de test nhanh.'
|
|
2648
|
+
: '\nâ™»ï¸ Reusing the installed 9Router for a faster test run.'));
|
|
2646
2649
|
} else if (!is9RouterInstalled()) {
|
|
2647
2650
|
console.log(chalk.cyan(isVi
|
|
2648
|
-
? '\n
|
|
2649
|
-
: '\n
|
|
2651
|
+
? '\n📦 Dang cai 9Router binary (npm install -g 9router)...'
|
|
2652
|
+
: '\n📦 Installing 9Router binary (npm install -g 9router)...'));
|
|
2650
2653
|
if (!installGlobalPackage('9router@latest', { isVi, osChoice, displayName: '9Router' })) {
|
|
2651
2654
|
process.exit(1);
|
|
2652
2655
|
}
|
|
2653
|
-
console.log(chalk.green(isVi ? '
|
|
2656
|
+
console.log(chalk.green(isVi ? '✅ 9Router da cai xong!' : '✅ 9Router installed!'));
|
|
2654
2657
|
}
|
|
2655
2658
|
}
|
|
2656
2659
|
|
|
@@ -2676,7 +2679,7 @@ async function main() {
|
|
|
2676
2679
|
|
|
2677
2680
|
if (osChoice === 'vps') {
|
|
2678
2681
|
if (!isPm2Installed()) {
|
|
2679
|
-
console.log(chalk.cyan(isVi ? '\n
|
|
2682
|
+
console.log(chalk.cyan(isVi ? '\n📦 Dang cai PM2...' : '\n📦 Installing PM2...'));
|
|
2680
2683
|
if (!installGlobalPackage('pm2@latest', { isVi, osChoice, displayName: 'PM2' })) {
|
|
2681
2684
|
process.exit(1);
|
|
2682
2685
|
}
|
|
@@ -2691,7 +2694,7 @@ async function main() {
|
|
|
2691
2694
|
stdio: 'inherit',
|
|
2692
2695
|
shell: true
|
|
2693
2696
|
});
|
|
2694
|
-
console.log(chalk.green(`\n
|
|
2697
|
+
console.log(chalk.green(`\n🎉 ${isVi ? 'Setup hoan tat! Multi-bot native dang chay qua PM2.' : 'Setup complete! Native multi-bot is running via PM2.'}`));
|
|
2695
2698
|
console.log(chalk.gray(isVi ? ` Xem log: pm2 logs ${getNativePm2AppName(true)}` : ` View logs: pm2 logs ${getNativePm2AppName(true)}`));
|
|
2696
2699
|
printNativeDashboardAccessInfo({ isVi, providerKey, projectDir });
|
|
2697
2700
|
if (channelKey === 'zalo-personal') {
|
|
@@ -2721,7 +2724,7 @@ async function main() {
|
|
|
2721
2724
|
env: getProjectRuntimeEnv(projectDir)
|
|
2722
2725
|
});
|
|
2723
2726
|
runPm2Save({ projectDir, isVi });
|
|
2724
|
-
console.log(chalk.green(`\n
|
|
2727
|
+
console.log(chalk.green(`\n🎉 ${isVi ? 'Setup hoan tat! Bot native dang chay qua PM2.' : 'Setup complete! Native bot is running via PM2.'}`));
|
|
2725
2728
|
console.log(chalk.gray(isVi ? ` Xem log: pm2 logs ${appName}` : ` View logs: pm2 logs ${appName}`));
|
|
2726
2729
|
printNativeDashboardAccessInfo({ isVi, providerKey, projectDir });
|
|
2727
2730
|
if (channelKey === 'zalo-personal') {
|
|
@@ -2747,8 +2750,8 @@ async function main() {
|
|
|
2747
2750
|
: ' 9Router dashboard: http://localhost:20128/dashboard'));
|
|
2748
2751
|
if (!routerHealth.ok) {
|
|
2749
2752
|
console.log(chalk.yellow(isVi
|
|
2750
|
-
? `
|
|
2751
|
-
: `
|
|
2753
|
+
? ` âš ï¸ 9Router da mo cong 20128 nhung admin API chua san sang. Kiem tra them: ${routerHealth.url}`
|
|
2754
|
+
: ` âš ï¸ 9Router opened port 20128 but the admin API is not ready yet. Check: ${routerHealth.url}`));
|
|
2752
2755
|
}
|
|
2753
2756
|
}
|
|
2754
2757
|
if (hasZaloPersonal(channelKey)) {
|
|
@@ -2780,9 +2783,9 @@ async function main() {
|
|
|
2780
2783
|
return;
|
|
2781
2784
|
}
|
|
2782
2785
|
|
|
2783
|
-
console.log(chalk.cyan(`\n
|
|
2786
|
+
console.log(chalk.cyan(`\n👉 ${isVi ? 'Native runtime da duoc cai san va khoi dong.' : 'Native runtime is installed and started.'}`));
|
|
2784
2787
|
if (isMultiBot && channelKey === 'telegram') {
|
|
2785
|
-
console.log(chalk.yellow(`\n
|
|
2788
|
+
console.log(chalk.yellow(`\n📋 ${isVi ? 'Xem huong dan sau cai:' : 'Read post-install guide:'} ${path.join(projectDir, TELEGRAM_SETUP_GUIDE_FILENAME)}`));
|
|
2786
2789
|
}
|
|
2787
2790
|
}
|
|
2788
2791
|
}
|