create-openclaw-bot 5.7.1 → 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/README.md +3 -3
- package/README.vi.md +3 -3
- package/dist/cli.js +242 -206
- package/dist/setup/shared/bot-config-gen.js +4 -5
- package/dist/setup/shared/docker-gen.js +66 -10
- package/dist/setup/shared/workspace-gen.js +35 -14
- package/dist/setup.js +206 -48
- 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') {
|
|
@@ -613,26 +613,54 @@ function printNativeDashboardAccessInfo({ isVi, providerKey, projectDir, gateway
|
|
|
613
613
|
|
|
614
614
|
function printZaloPersonalLoginInfo({ isVi, deployMode, projectDir }) {
|
|
615
615
|
const nativeCmd = 'openclaw channels login --channel zalouser --verbose';
|
|
616
|
-
const dockerCmd = 'docker
|
|
616
|
+
const dockerCmd = 'docker exec -it openclaw-bot openclaw channels login --channel zalouser --verbose';
|
|
617
617
|
const cmd = deployMode === 'native' ? nativeCmd : dockerCmd;
|
|
618
618
|
const qrPath = deployMode === 'native'
|
|
619
619
|
? path.join(os.tmpdir(), 'openclaw', 'openclaw-zalouser-qr-default.png')
|
|
620
620
|
: '/tmp/openclaw/openclaw-zalouser-qr-default.png';
|
|
621
|
-
const projectQrPath = path.join(projectDir, 'zalo-login-qr.png');
|
|
622
621
|
const copyCmd = deployMode === 'native'
|
|
623
622
|
? (process.platform === 'win32'
|
|
624
|
-
? `Copy-Item "${qrPath}" "${
|
|
625
|
-
: `cp "${qrPath}" "${
|
|
626
|
-
: `docker
|
|
623
|
+
? `Copy-Item "${qrPath}" "${path.join(projectDir, 'zalo-login-qr.png')}"`
|
|
624
|
+
: `cp "${qrPath}" "${path.join(projectDir, 'zalo-login-qr.png')}"`)
|
|
625
|
+
: `docker cp openclaw-bot:${qrPath} ./zalo-qr.png`;
|
|
627
626
|
|
|
628
|
-
console.log(chalk.yellow(`\n
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
627
|
+
console.log(chalk.yellow(`\n📱 ${isVi ? 'Äăng nháºp Zalo Personal (1 lần):' : 'Zalo Personal login (one time):'}`));
|
|
628
|
+
if (deployMode === 'docker') {
|
|
629
|
+
console.log(chalk.white(isVi
|
|
630
|
+
? ` 1. cd ${projectDir}/docker/openclaw`
|
|
631
|
+
: ` 1. cd ${projectDir}/docker/openclaw`));
|
|
632
|
+
console.log(chalk.white(isVi
|
|
633
|
+
? ` 2. ${cmd}`
|
|
634
|
+
: ` 2. ${cmd}`));
|
|
635
|
+
console.log(chalk.white(isVi
|
|
636
|
+
? ` 3. Tìm file QR trong container: ${qrPath}`
|
|
637
|
+
: ` 3. Find QR image in container: ${qrPath}`));
|
|
638
|
+
console.log(chalk.gray(isVi
|
|
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
|
+
console.log(chalk.gray(isVi
|
|
642
|
+
? ` → Hoặc chạy: ${copyCmd}`
|
|
643
|
+
: ` → Or run: ${copyCmd}`));
|
|
644
|
+
console.log(chalk.white(isVi
|
|
645
|
+
? ' 4. Mở app Zalo > Quét QR > quét mã trong file QR'
|
|
646
|
+
: ' 4. Open Zalo app > Scan QR > scan the QR image'));
|
|
647
|
+
console.log(chalk.white(isVi
|
|
648
|
+
? ' 5. Äợi thấy "Login successful" trong terminal'
|
|
649
|
+
: ' 5. Wait for "Login successful" in terminal'));
|
|
650
|
+
console.log(chalk.white(isVi
|
|
651
|
+
? ' 6. docker compose restart'
|
|
652
|
+
: ' 6. docker compose restart'));
|
|
653
|
+
} else {
|
|
654
|
+
console.log(chalk.white(` cd ${projectDir} ${process.platform === 'win32' ? ';' : '&&'} ${cmd}`));
|
|
655
|
+
console.log(chalk.gray(isVi
|
|
656
|
+
? ` → File QR sẽ tạo tại: ${qrPath}`
|
|
657
|
+
: ` → QR file will be created at: ${qrPath}`));
|
|
658
|
+
if (process.platform === 'win32') {
|
|
659
|
+
console.log(chalk.gray(isVi
|
|
660
|
+
? ` → Copy QR ra project: ${copyCmd}`
|
|
661
|
+
: ` → Copy QR to project: ${copyCmd}`));
|
|
662
|
+
}
|
|
663
|
+
}
|
|
636
664
|
}
|
|
637
665
|
|
|
638
666
|
async function waitForFile(filePath, timeoutMs = 15000, intervalMs = 500) {
|
|
@@ -680,13 +708,13 @@ function approveZaloPairingCode({ pairingCode, projectDir, isVi }) {
|
|
|
680
708
|
env: process.env
|
|
681
709
|
});
|
|
682
710
|
console.log(chalk.green(isVi
|
|
683
|
-
?
|
|
684
|
-
:
|
|
711
|
+
? `✅ Da tu dong approve pairing code Zalo: ${pairingCode}`
|
|
712
|
+
: `✅ Automatically approved the Zalo pairing code: ${pairingCode}`));
|
|
685
713
|
return true;
|
|
686
714
|
} catch {
|
|
687
715
|
console.log(chalk.yellow(isVi
|
|
688
|
-
?
|
|
689
|
-
:
|
|
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}`));
|
|
690
718
|
return false;
|
|
691
719
|
}
|
|
692
720
|
}
|
|
@@ -694,7 +722,7 @@ function approveZaloPairingCode({ pairingCode, projectDir, isVi }) {
|
|
|
694
722
|
async function runNativeZaloPersonalLoginFlow({ isVi, projectDir }) {
|
|
695
723
|
const qrSourcePath = path.join(os.tmpdir(), 'openclaw', 'openclaw-zalouser-qr-default.png');
|
|
696
724
|
const qrProjectPath = path.join(projectDir, 'zalo-login-qr.png');
|
|
697
|
-
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...'}`));
|
|
698
726
|
const loginStartedAt = Date.now();
|
|
699
727
|
|
|
700
728
|
try {
|
|
@@ -748,8 +776,8 @@ async function runNativeZaloPersonalLoginFlow({ isVi, projectDir }) {
|
|
|
748
776
|
await fs.copy(qrSourcePath, qrProjectPath, { overwrite: true });
|
|
749
777
|
qrCopied = true;
|
|
750
778
|
console.log(chalk.green(isVi
|
|
751
|
-
?
|
|
752
|
-
:
|
|
779
|
+
? `✅ QR đã được copy và o: ${qrProjectPath}`
|
|
780
|
+
: `✅ QR copied to: ${qrProjectPath}`));
|
|
753
781
|
}
|
|
754
782
|
};
|
|
755
783
|
|
|
@@ -766,13 +794,13 @@ async function runNativeZaloPersonalLoginFlow({ isVi, projectDir }) {
|
|
|
766
794
|
|
|
767
795
|
if (exitCode !== 0 && !loginSucceeded) {
|
|
768
796
|
console.log(chalk.yellow(isVi
|
|
769
|
-
? '
|
|
770
|
-
: '
|
|
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.'));
|
|
771
799
|
printZaloPersonalLoginInfo({ isVi, deployMode: 'native', projectDir });
|
|
772
800
|
} else if (loginSucceeded && exitCode !== 0) {
|
|
773
801
|
console.log(chalk.green(isVi
|
|
774
|
-
? '
|
|
775
|
-
: '
|
|
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.'));
|
|
776
804
|
}
|
|
777
805
|
}
|
|
778
806
|
|
|
@@ -786,8 +814,8 @@ function runPm2Save({ projectDir, isVi }) {
|
|
|
786
814
|
});
|
|
787
815
|
} catch {
|
|
788
816
|
console.log(chalk.yellow(isVi
|
|
789
|
-
? '
|
|
790
|
-
: '
|
|
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.'));
|
|
791
819
|
}
|
|
792
820
|
}
|
|
793
821
|
|
|
@@ -1019,26 +1047,26 @@ function startNative9RouterPm2({ isVi, projectDir, appName, syncScriptPath }) {
|
|
|
1019
1047
|
});
|
|
1020
1048
|
} catch (syncErr) {
|
|
1021
1049
|
console.log(chalk.yellow(isVi
|
|
1022
|
-
? `\
|
|
1023
|
-
: `\
|
|
1050
|
+
? `\nâš ï¸ Khong the tu dong khoi dong sync script qua PM2.`
|
|
1051
|
+
: `\nâš ï¸ Could not auto-start 9router sync script via PM2.`));
|
|
1024
1052
|
}
|
|
1025
1053
|
}
|
|
1026
1054
|
runPm2Save({ projectDir, isVi });
|
|
1027
|
-
console.log(chalk.green(`\n
|
|
1055
|
+
console.log(chalk.green(`\n✅ ${isVi ? '9Router da duoc khoi dong qua PM2.' : '9Router is running via PM2.'}`));
|
|
1028
1056
|
console.log(chalk.gray(isVi ? ` Xem log: pm2 logs ${routerAppName}` : ` View logs: pm2 logs ${routerAppName}`));
|
|
1029
1057
|
}
|
|
1030
1058
|
|
|
1031
1059
|
async function ensureProjectRuntimeDirs(projectDir, isVi) {
|
|
1032
1060
|
await fs.ensureDir(path.join(projectDir, '.openclaw'));
|
|
1033
1061
|
await fs.ensureDir(getProject9RouterDataDir(projectDir));
|
|
1034
|
-
console.log(chalk.green(`\n
|
|
1062
|
+
console.log(chalk.green(`\n✅ ${isVi
|
|
1035
1063
|
? 'Da chuan bi runtime directories local trong project.'
|
|
1036
1064
|
: 'Prepared project-local runtime directories.'}`));
|
|
1037
1065
|
}
|
|
1038
1066
|
|
|
1039
1067
|
// buildTelegramPostInstallChecklist is imported from setup/shared/common-gen.js
|
|
1040
1068
|
|
|
1041
|
-
//
|
|
1069
|
+
// ─── Docker Auto-Detection ───────────────────────────────────────────────────
|
|
1042
1070
|
function isDockerInstalled() {
|
|
1043
1071
|
try {
|
|
1044
1072
|
execSync('docker --version', { stdio: 'ignore' });
|
|
@@ -1049,15 +1077,15 @@ function isDockerInstalled() {
|
|
|
1049
1077
|
|
|
1050
1078
|
|
|
1051
1079
|
const LOGO = `
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1080
|
+
████████╗██╗ ██╗ █████╗ ███╗ ██╗███╗ ███╗██╗███╗ ██╗██╗ ██╗██╗ ██╗ ██████╗ ██╗ ███████╗
|
|
1081
|
+
╚â•â•██╔â•â•â•██║ ██║██╔â•â•██╗████╗ ██║████╗ ████║██║████╗ ██║██║ ██║██║ ██║██╔â•â•â•██╗██║ ██╔â•â•â•â•â•
|
|
1082
|
+
██║ ██║ ██║███████║██╔██╗ ██║██╔████╔██║██║██╔██╗ ██║███████║███████║██║ ██║██║ █████╗
|
|
1083
|
+
██║ ██║ ██║██╔â•â•██║██║╚██╗██║██║╚██╔â•██║██║██║╚██╗██║██╔â•â•██║██╔â•â•██║██║ ██║██║ ██╔â•â•â•
|
|
1084
|
+
██║ ╚██████╔â•██║ ██║██║ ╚████║██║ ╚â•╠██║██║██║ ╚████║██║ ██║██║ ██║╚██████╔â•███████╗███████╗
|
|
1085
|
+
╚â•╠╚â•â•â•â•â•╠╚â•╠╚â•â•╚â•╠╚â•â•â•â•╚â•╠╚â•â•╚â•â•╚â•╠╚â•â•â•â•╚â•╠╚â•â•╚â•╠╚â•╠╚â•â•â•â•â•╠╚â•â•â•â•â•â•â•╚â•â•â•â•â•â•â•
|
|
1058
1086
|
`;
|
|
1059
1087
|
|
|
1060
|
-
//
|
|
1088
|
+
// ── Data constants from setup/data/index.js (single source of truth) ──────────
|
|
1061
1089
|
const CHANNELS = _CHANNELS;
|
|
1062
1090
|
const PROVIDERS = _PROVIDERS;
|
|
1063
1091
|
const SKILLS = _SKILLS;
|
|
@@ -1086,8 +1114,8 @@ function getCliSkillChoices({ providerKey, isVi }) {
|
|
|
1086
1114
|
let name = `${skill.icon || ''} ${isVi ? (skill.nameVi || skill.name) : (skill.nameEn || skill.name)}`.trim();
|
|
1087
1115
|
if (value === 'memory') {
|
|
1088
1116
|
name = isVi
|
|
1089
|
-
? (memoryRecommended ? '
|
|
1090
|
-
: (memoryRecommended ? '
|
|
1117
|
+
? (memoryRecommended ? '🧠Long-term Memory â(Khuyên dùng)' : '🧠Long-term Memory')
|
|
1118
|
+
: (memoryRecommended ? '🧠Long-term Memory â(Recommended)' : '🧠Long-term Memory');
|
|
1091
1119
|
}
|
|
1092
1120
|
return {
|
|
1093
1121
|
name,
|
|
@@ -1101,13 +1129,13 @@ const CLI_BACK = '__openclaw_cli_back__';
|
|
|
1101
1129
|
|
|
1102
1130
|
function getBackChoice(isVi) {
|
|
1103
1131
|
return {
|
|
1104
|
-
name: isVi ? '
|
|
1132
|
+
name: isVi ? '↠Quay lại' : '↠Back',
|
|
1105
1133
|
value: CLI_BACK,
|
|
1106
1134
|
};
|
|
1107
1135
|
}
|
|
1108
1136
|
|
|
1109
1137
|
function withBackHint(message, isVi) {
|
|
1110
|
-
return `${message} ${isVi ? '(
|
|
1138
|
+
return `${message} ${isVi ? '(gõ "back" để quay lại)' : '(type "back" to go back)'}`;
|
|
1111
1139
|
}
|
|
1112
1140
|
|
|
1113
1141
|
async function selectWithBack({ message, choices, defaultValue, allowBack = false, isVi = true }) {
|
|
@@ -1154,7 +1182,7 @@ async function collectBotSetupStep({
|
|
|
1154
1182
|
|
|
1155
1183
|
if (channelKey === 'telegram') {
|
|
1156
1184
|
const botCountValue = await selectWithBack({
|
|
1157
|
-
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?',
|
|
1158
1186
|
choices: [
|
|
1159
1187
|
{ name: '1 bot (single)', value: '1' },
|
|
1160
1188
|
{ name: '2 bots (Department Room)', value: '2' },
|
|
@@ -1173,10 +1201,10 @@ async function collectBotSetupStep({
|
|
|
1173
1201
|
|
|
1174
1202
|
if (botCount > 1) {
|
|
1175
1203
|
const groupOption = await selectWithBack({
|
|
1176
|
-
message: isVi ? '
|
|
1204
|
+
message: isVi ? 'Bạn có sẵn Telegram Group chưa?' : 'Do you already have a Telegram Group?',
|
|
1177
1205
|
choices: [
|
|
1178
|
-
{ name: isVi ? '
|
|
1179
|
-
{ 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' }
|
|
1180
1208
|
],
|
|
1181
1209
|
defaultValue: groupId ? 'existing' : 'create',
|
|
1182
1210
|
allowBack: true,
|
|
@@ -1188,8 +1216,8 @@ async function collectBotSetupStep({
|
|
|
1188
1216
|
|
|
1189
1217
|
if (groupOption === 'existing') {
|
|
1190
1218
|
console.log(chalk.dim(isVi
|
|
1191
|
-
? '\n
|
|
1192
|
-
: '\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'));
|
|
1193
1221
|
const nextGroupId = await inputWithBack({
|
|
1194
1222
|
message: isVi ? 'Telegram Group ID (VD: -1001234567890):' : 'Telegram Group ID (e.g. -1001234567890):',
|
|
1195
1223
|
defaultValue: groupId,
|
|
@@ -1208,12 +1236,12 @@ async function collectBotSetupStep({
|
|
|
1208
1236
|
}
|
|
1209
1237
|
|
|
1210
1238
|
for (let i = 0; i < botCount; i++) {
|
|
1211
|
-
console.log(chalk.bold(`\n${isVi ?
|
|
1239
|
+
console.log(chalk.bold(`\n${isVi ? `─── Bot ${i + 1} / ${botCount} ───` : `─── Bot ${i + 1} / ${botCount} ───`}`));
|
|
1212
1240
|
const defaults = existingBots[i] || {};
|
|
1213
1241
|
const fields = [
|
|
1214
1242
|
{
|
|
1215
1243
|
key: 'name',
|
|
1216
|
-
message: isVi ? `
|
|
1244
|
+
message: isVi ? `Tên Bot ${i + 1}:` : `Bot ${i + 1} name:`,
|
|
1217
1245
|
defaultValue: defaults.name || `Bot ${i + 1}`,
|
|
1218
1246
|
required: true,
|
|
1219
1247
|
},
|
|
@@ -1225,19 +1253,19 @@ async function collectBotSetupStep({
|
|
|
1225
1253
|
},
|
|
1226
1254
|
{
|
|
1227
1255
|
key: 'desc',
|
|
1228
|
-
message: isVi ? `
|
|
1229
|
-
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'),
|
|
1230
1258
|
required: true,
|
|
1231
1259
|
},
|
|
1232
1260
|
{
|
|
1233
1261
|
key: 'persona',
|
|
1234
|
-
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):`,
|
|
1235
1263
|
defaultValue: defaults.persona || '',
|
|
1236
1264
|
required: false,
|
|
1237
1265
|
},
|
|
1238
1266
|
{
|
|
1239
1267
|
key: 'token',
|
|
1240
|
-
message: isVi ? 'Bot Token (
|
|
1268
|
+
message: isVi ? 'Bot Token (từ @BotFather):' : 'Bot Token (from @BotFather):',
|
|
1241
1269
|
defaultValue: defaults.token || '',
|
|
1242
1270
|
required: true,
|
|
1243
1271
|
},
|
|
@@ -1271,25 +1299,25 @@ async function collectBotSetupStep({
|
|
|
1271
1299
|
const fields = [
|
|
1272
1300
|
{
|
|
1273
1301
|
key: 'name',
|
|
1274
|
-
message: isVi ? '
|
|
1302
|
+
message: isVi ? 'Tên Bot:' : 'Bot Name:',
|
|
1275
1303
|
defaultValue: defaults.name || 'Chat Bot',
|
|
1276
1304
|
required: true,
|
|
1277
1305
|
},
|
|
1278
1306
|
{
|
|
1279
1307
|
key: 'desc',
|
|
1280
|
-
message: isVi ? '
|
|
1281
|
-
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'),
|
|
1282
1310
|
required: true,
|
|
1283
1311
|
},
|
|
1284
1312
|
{
|
|
1285
1313
|
key: 'persona',
|
|
1286
|
-
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):',
|
|
1287
1315
|
defaultValue: defaults.persona || '',
|
|
1288
1316
|
required: false,
|
|
1289
1317
|
},
|
|
1290
1318
|
{
|
|
1291
1319
|
key: 'token',
|
|
1292
|
-
message: isVi ? `
|
|
1320
|
+
message: isVi ? `Nháºp ${channel.name} Token:` : `Enter ${channel.name} Token:`,
|
|
1293
1321
|
defaultValue: defaults.token || '',
|
|
1294
1322
|
required: true,
|
|
1295
1323
|
},
|
|
@@ -1345,7 +1373,7 @@ async function collectBotSetupStepWithGroupBack(options) {
|
|
|
1345
1373
|
|
|
1346
1374
|
if (channelKey === 'telegram') {
|
|
1347
1375
|
const botCountValue = await selectWithBack({
|
|
1348
|
-
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?',
|
|
1349
1377
|
choices: [
|
|
1350
1378
|
{ name: '1 bot (single)', value: '1' },
|
|
1351
1379
|
{ name: '2 bots (Department Room)', value: '2' },
|
|
@@ -1365,10 +1393,10 @@ async function collectBotSetupStepWithGroupBack(options) {
|
|
|
1365
1393
|
if (botCount > 1) {
|
|
1366
1394
|
while (true) {
|
|
1367
1395
|
const groupOption = await selectWithBack({
|
|
1368
|
-
message: isVi ? '
|
|
1396
|
+
message: isVi ? 'Bạn có sẵn Telegram Group chưa?' : 'Do you already have a Telegram Group?',
|
|
1369
1397
|
choices: [
|
|
1370
|
-
{ name: isVi ? '
|
|
1371
|
-
{ 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' }
|
|
1372
1400
|
],
|
|
1373
1401
|
defaultValue: groupId ? 'existing' : 'create',
|
|
1374
1402
|
allowBack: true,
|
|
@@ -1380,8 +1408,8 @@ async function collectBotSetupStepWithGroupBack(options) {
|
|
|
1380
1408
|
|
|
1381
1409
|
if (groupOption === 'existing') {
|
|
1382
1410
|
console.log(chalk.dim(isVi
|
|
1383
|
-
? '\n
|
|
1384
|
-
: '\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'));
|
|
1385
1413
|
const nextGroupId = await inputWithBack({
|
|
1386
1414
|
message: isVi ? 'Telegram Group ID (VD: -1001234567890):' : 'Telegram Group ID (e.g. -1001234567890):',
|
|
1387
1415
|
defaultValue: groupId,
|
|
@@ -1403,12 +1431,12 @@ async function collectBotSetupStepWithGroupBack(options) {
|
|
|
1403
1431
|
}
|
|
1404
1432
|
|
|
1405
1433
|
for (let i = 0; i < botCount; i++) {
|
|
1406
|
-
console.log(chalk.bold(`\n${isVi ?
|
|
1434
|
+
console.log(chalk.bold(`\n${isVi ? `─── Bot ${i + 1} / ${botCount} ───` : `─── Bot ${i + 1} / ${botCount} ───`}`));
|
|
1407
1435
|
const defaults = existingBots[i] || {};
|
|
1408
1436
|
const fields = [
|
|
1409
1437
|
{
|
|
1410
1438
|
key: 'name',
|
|
1411
|
-
message: isVi ? `
|
|
1439
|
+
message: isVi ? `Tên Bot ${i + 1}:` : `Bot ${i + 1} name:`,
|
|
1412
1440
|
defaultValue: defaults.name || `Bot ${i + 1}`,
|
|
1413
1441
|
required: true,
|
|
1414
1442
|
},
|
|
@@ -1420,19 +1448,19 @@ async function collectBotSetupStepWithGroupBack(options) {
|
|
|
1420
1448
|
},
|
|
1421
1449
|
{
|
|
1422
1450
|
key: 'desc',
|
|
1423
|
-
message: isVi ? `
|
|
1424
|
-
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'),
|
|
1425
1453
|
required: true,
|
|
1426
1454
|
},
|
|
1427
1455
|
{
|
|
1428
1456
|
key: 'persona',
|
|
1429
|
-
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):`,
|
|
1430
1458
|
defaultValue: defaults.persona || '',
|
|
1431
1459
|
required: false,
|
|
1432
1460
|
},
|
|
1433
1461
|
{
|
|
1434
1462
|
key: 'token',
|
|
1435
|
-
message: isVi ? 'Bot Token (
|
|
1463
|
+
message: isVi ? 'Bot Token (từ @BotFather):' : 'Bot Token (from @BotFather):',
|
|
1436
1464
|
defaultValue: defaults.token || '',
|
|
1437
1465
|
required: true,
|
|
1438
1466
|
},
|
|
@@ -1466,25 +1494,25 @@ async function collectBotSetupStepWithGroupBack(options) {
|
|
|
1466
1494
|
const fields = [
|
|
1467
1495
|
{
|
|
1468
1496
|
key: 'name',
|
|
1469
|
-
message: isVi ? '
|
|
1497
|
+
message: isVi ? 'Tên Bot:' : 'Bot Name:',
|
|
1470
1498
|
defaultValue: defaults.name || 'Chat Bot',
|
|
1471
1499
|
required: true,
|
|
1472
1500
|
},
|
|
1473
1501
|
{
|
|
1474
1502
|
key: 'desc',
|
|
1475
|
-
message: isVi ? '
|
|
1476
|
-
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'),
|
|
1477
1505
|
required: true,
|
|
1478
1506
|
},
|
|
1479
1507
|
{
|
|
1480
1508
|
key: 'persona',
|
|
1481
|
-
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):',
|
|
1482
1510
|
defaultValue: defaults.persona || '',
|
|
1483
1511
|
required: false,
|
|
1484
1512
|
},
|
|
1485
1513
|
{
|
|
1486
1514
|
key: 'token',
|
|
1487
|
-
message: isVi ? `
|
|
1515
|
+
message: isVi ? `Nháºp ${channel.name} Token:` : `Enter ${channel.name} Token:`,
|
|
1488
1516
|
defaultValue: defaults.token || '',
|
|
1489
1517
|
required: true,
|
|
1490
1518
|
},
|
|
@@ -1531,7 +1559,7 @@ async function collectProviderStep({
|
|
|
1531
1559
|
existingOllamaModel = 'gemma4:e2b',
|
|
1532
1560
|
}) {
|
|
1533
1561
|
const providerKey = await selectWithBack({
|
|
1534
|
-
message: isVi ? '
|
|
1562
|
+
message: isVi ? 'Chá»n AI Provider:' : 'Select AI Provider:',
|
|
1535
1563
|
choices: Object.entries(PROVIDERS).map(([k, v]) => ({ name: `${v.icon} ${v.name}`, value: k })),
|
|
1536
1564
|
defaultValue: existingProviderKey || undefined,
|
|
1537
1565
|
allowBack: true,
|
|
@@ -1545,7 +1573,7 @@ async function collectProviderStep({
|
|
|
1545
1573
|
let providerKeyVal = existingProviderKey === providerKey ? existingProviderKeyVal : '';
|
|
1546
1574
|
if (!provider.isProxy && !provider.isLocal) {
|
|
1547
1575
|
const keyValue = await inputWithBack({
|
|
1548
|
-
message: isVi ? `
|
|
1576
|
+
message: isVi ? `Nháºp ${provider.envKey}:` : `Enter ${provider.envKey}:`,
|
|
1549
1577
|
defaultValue: providerKeyVal,
|
|
1550
1578
|
required: true,
|
|
1551
1579
|
allowBack: true,
|
|
@@ -1560,33 +1588,33 @@ async function collectProviderStep({
|
|
|
1560
1588
|
let selectedOllamaModel = existingProviderKey === 'ollama' ? existingOllamaModel : 'gemma4:e2b';
|
|
1561
1589
|
if (providerKey === 'ollama') {
|
|
1562
1590
|
console.log(chalk.yellow(isVi
|
|
1563
|
-
? '\n
|
|
1564
|
-
: '\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:'));
|
|
1565
1593
|
const modelValue = await selectWithBack({
|
|
1566
|
-
message: isVi ? '
|
|
1594
|
+
message: isVi ? 'Chá»n model Ollama:' : 'Select Ollama model:',
|
|
1567
1595
|
choices: [
|
|
1568
1596
|
{
|
|
1569
1597
|
name: isVi
|
|
1570
|
-
? '
|
|
1571
|
-
: '
|
|
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',
|
|
1572
1600
|
value: 'gemma4:e2b'
|
|
1573
1601
|
},
|
|
1574
1602
|
{
|
|
1575
1603
|
name: isVi
|
|
1576
|
-
? '
|
|
1577
|
-
: '
|
|
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',
|
|
1578
1606
|
value: 'gemma4:e4b'
|
|
1579
1607
|
},
|
|
1580
1608
|
{
|
|
1581
1609
|
name: isVi
|
|
1582
|
-
? '
|
|
1583
|
-
: '
|
|
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',
|
|
1584
1612
|
value: 'gemma4:26b'
|
|
1585
1613
|
},
|
|
1586
1614
|
{
|
|
1587
1615
|
name: isVi
|
|
1588
|
-
? '
|
|
1589
|
-
: '
|
|
1616
|
+
? '🔴 gemma4:31b — Mạnh nhất (~24+ GB RAM/VRAM) — GPU workstation'
|
|
1617
|
+
: '🔴 gemma4:31b — Most powerful (~24+ GB RAM/VRAM) — GPU workstation',
|
|
1590
1618
|
value: 'gemma4:31b'
|
|
1591
1619
|
},
|
|
1592
1620
|
],
|
|
@@ -1622,7 +1650,7 @@ async function collectSkillsStep({
|
|
|
1622
1650
|
existingSmtpPass = '',
|
|
1623
1651
|
}) {
|
|
1624
1652
|
const selectedSkills = await checkboxWithBack({
|
|
1625
|
-
message: isVi ? '
|
|
1653
|
+
message: isVi ? 'Báºt tÃnh năng bổ sung (Space để chá»n):' : 'Enable extra skills (Space to select):',
|
|
1626
1654
|
choices: getCliSkillChoices({ providerKey, isVi }).map((choice) => ({
|
|
1627
1655
|
...choice,
|
|
1628
1656
|
checked: existingSelectedSkills.includes(choice.value),
|
|
@@ -1638,18 +1666,18 @@ async function collectSkillsStep({
|
|
|
1638
1666
|
if (selectedSkills.includes('browser')) {
|
|
1639
1667
|
const isLinux = process.platform === 'linux';
|
|
1640
1668
|
const browserValue = await selectWithBack({
|
|
1641
|
-
message: isVi ? '
|
|
1669
|
+
message: isVi ? 'Chế độ Browser Automation:' : 'Browser Automation mode:',
|
|
1642
1670
|
choices: [
|
|
1643
1671
|
{
|
|
1644
1672
|
name: isVi
|
|
1645
|
-
? '
|
|
1646
|
-
: '
|
|
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)',
|
|
1647
1675
|
value: 'desktop'
|
|
1648
1676
|
},
|
|
1649
1677
|
{
|
|
1650
1678
|
name: isVi
|
|
1651
|
-
? '
|
|
1652
|
-
: '
|
|
1679
|
+
? '🳠Headless Chromium trong Docker (Ubuntu Server / VPS — không cần GUI)'
|
|
1680
|
+
: '🳠Headless Chromium inside Docker (Ubuntu Server / VPS — No GUI)',
|
|
1653
1681
|
value: 'server'
|
|
1654
1682
|
}
|
|
1655
1683
|
],
|
|
@@ -1669,7 +1697,7 @@ async function collectSkillsStep({
|
|
|
1669
1697
|
let ttsElevenKey = existingTtsElevenKey;
|
|
1670
1698
|
if (selectedSkills.includes('tts')) {
|
|
1671
1699
|
const openaiKey = await inputWithBack({
|
|
1672
|
-
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):',
|
|
1673
1701
|
defaultValue: ttsOpenaiKey,
|
|
1674
1702
|
allowBack: true,
|
|
1675
1703
|
isVi,
|
|
@@ -1680,7 +1708,7 @@ async function collectSkillsStep({
|
|
|
1680
1708
|
ttsOpenaiKey = openaiKey;
|
|
1681
1709
|
|
|
1682
1710
|
const elevenKey = await inputWithBack({
|
|
1683
|
-
message: isVi ? '
|
|
1711
|
+
message: isVi ? 'Nháºp ELEVENLABS_API_KEY (hoặc bá» trống):' : 'Enter ELEVENLABS_API_KEY (or leave empty):',
|
|
1684
1712
|
defaultValue: ttsElevenKey,
|
|
1685
1713
|
allowBack: true,
|
|
1686
1714
|
isVi,
|
|
@@ -1767,7 +1795,7 @@ async function collectSkillsStep({
|
|
|
1767
1795
|
}
|
|
1768
1796
|
|
|
1769
1797
|
|
|
1770
|
-
//
|
|
1798
|
+
// ─── Shared workspace file writer ─────────────────────────────────────────────
|
|
1771
1799
|
// Used by both native and docker flows to write .md + browser files consistently.
|
|
1772
1800
|
async function writeWorkspaceFiles({
|
|
1773
1801
|
workspaceDir, // absolute path to the workspace dir
|
|
@@ -1787,6 +1815,7 @@ async function writeWorkspaceFiles({
|
|
|
1787
1815
|
agentWorkspaceDir = 'workspace',
|
|
1788
1816
|
isRelayBot = false,
|
|
1789
1817
|
replyToDirectMessages = true,
|
|
1818
|
+
channelKey = 'telegram',
|
|
1790
1819
|
}) {
|
|
1791
1820
|
const skillListStr = SKILLS
|
|
1792
1821
|
.filter((s) => selectedSkills.includes(s.value))
|
|
@@ -1796,7 +1825,7 @@ async function writeWorkspaceFiles({
|
|
|
1796
1825
|
? `- ${label}${s.slug ? ` (${s.slug})` : ' (native)'}`
|
|
1797
1826
|
: `- **${label}**${s.slug ? ` (${s.slug})` : ' (native)'}`;
|
|
1798
1827
|
})
|
|
1799
|
-
.join('\n') || (isVi ? '- _(
|
|
1828
|
+
.join('\n') || (isVi ? '- _(Chưa có skill nà o)_' : '- _(No skills installed)_');
|
|
1800
1829
|
|
|
1801
1830
|
const workspacePath = `.openclaw/${agentWorkspaceDir}/`;
|
|
1802
1831
|
const teamRosterFormatted = teamRoster
|
|
@@ -1831,6 +1860,7 @@ async function writeWorkspaceFiles({
|
|
|
1831
1860
|
includeBrowserTool: isDesktop,
|
|
1832
1861
|
teamRosterFormatted,
|
|
1833
1862
|
hasScheduler: selectedSkills.includes('scheduler'),
|
|
1863
|
+
hasZaloMod: channelKey === 'zalo-personal',
|
|
1834
1864
|
});
|
|
1835
1865
|
|
|
1836
1866
|
await fs.ensureDir(workspaceDir);
|
|
@@ -1887,9 +1917,9 @@ async function main() {
|
|
|
1887
1917
|
while (true) {
|
|
1888
1918
|
if (setupStep === 'language') {
|
|
1889
1919
|
lang = await select({
|
|
1890
|
-
message: 'Select language /
|
|
1920
|
+
message: 'Select language / Chá»n ngôn ngữ:',
|
|
1891
1921
|
choices: [
|
|
1892
|
-
{ name: '
|
|
1922
|
+
{ name: 'Tiếng Việt', value: 'vi' },
|
|
1893
1923
|
{ name: 'English', value: 'en' }
|
|
1894
1924
|
],
|
|
1895
1925
|
default: lang
|
|
@@ -1901,12 +1931,12 @@ async function main() {
|
|
|
1901
1931
|
|
|
1902
1932
|
if (setupStep === 'os') {
|
|
1903
1933
|
const nextOsChoice = await selectWithBack({
|
|
1904
|
-
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?',
|
|
1905
1935
|
choices: [
|
|
1906
|
-
{ name: isVi ? '
|
|
1907
|
-
{ name: isVi ? '
|
|
1908
|
-
{ name: isVi ? '
|
|
1909
|
-
{ 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' },
|
|
1910
1940
|
],
|
|
1911
1941
|
defaultValue: osChoice,
|
|
1912
1942
|
allowBack: true,
|
|
@@ -1924,10 +1954,10 @@ async function main() {
|
|
|
1924
1954
|
if (setupStep === 'deploy') {
|
|
1925
1955
|
const deployModeDefault = (osChoice === 'ubuntu' || osChoice === 'vps') ? 'native' : 'docker';
|
|
1926
1956
|
const nextDeployMode = await selectWithBack({
|
|
1927
|
-
message: isVi ? '
|
|
1957
|
+
message: isVi ? 'Chá»n cách chạy bot:' : 'How do you want to run the bot?',
|
|
1928
1958
|
choices: [
|
|
1929
|
-
{ name: isVi ? '
|
|
1930
|
-
{ 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' }
|
|
1931
1961
|
],
|
|
1932
1962
|
defaultValue: deployMode || deployModeDefault,
|
|
1933
1963
|
allowBack: true,
|
|
@@ -1939,24 +1969,24 @@ async function main() {
|
|
|
1939
1969
|
}
|
|
1940
1970
|
deployMode = nextDeployMode;
|
|
1941
1971
|
if (deployMode === 'docker' && !isDockerInstalled()) {
|
|
1942
|
-
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...'));
|
|
1943
1973
|
try {
|
|
1944
1974
|
const platform = process.platform;
|
|
1945
1975
|
if (platform === 'win32') {
|
|
1946
1976
|
execSync('winget install -e --id Docker.DockerDesktop --accept-source-agreements --accept-package-agreements', { stdio: 'inherit' });
|
|
1947
|
-
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.'));
|
|
1948
1978
|
process.exit(0);
|
|
1949
1979
|
} else if (platform === 'darwin') {
|
|
1950
1980
|
execSync('brew install --cask docker', { stdio: 'inherit' });
|
|
1951
|
-
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.'));
|
|
1952
1982
|
process.exit(0);
|
|
1953
1983
|
} else {
|
|
1954
1984
|
execSync('curl -fsSL https://get.docker.com | sh', { stdio: 'inherit', shell: true });
|
|
1955
1985
|
try { execSync('apt-get install -y docker-compose-plugin', { stdio: 'ignore', shell: true }); } catch {}
|
|
1956
|
-
console.log(chalk.green(isVi ? '
|
|
1986
|
+
console.log(chalk.green(isVi ? '✅ Docker Engine + Compose plugin đã cà i xong.' : '✅ Docker Engine + Compose plugin installed.'));
|
|
1957
1987
|
}
|
|
1958
1988
|
} catch {
|
|
1959
|
-
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/'));
|
|
1960
1990
|
process.exit(1);
|
|
1961
1991
|
}
|
|
1962
1992
|
}
|
|
@@ -1966,7 +1996,7 @@ async function main() {
|
|
|
1966
1996
|
|
|
1967
1997
|
if (setupStep === 'channel') {
|
|
1968
1998
|
const nextChannelKey = await selectWithBack({
|
|
1969
|
-
message: isVi ? '
|
|
1999
|
+
message: isVi ? 'Chá»n ná»n tảng bot:' : 'Select bot platform:',
|
|
1970
2000
|
choices: Object.entries(CHANNELS).map(([k, v]) => ({ name: v.icon + ' ' + v.name, value: k })),
|
|
1971
2001
|
defaultValue: channelKey,
|
|
1972
2002
|
allowBack: true,
|
|
@@ -1979,7 +2009,7 @@ async function main() {
|
|
|
1979
2009
|
channelKey = nextChannelKey;
|
|
1980
2010
|
channel = CHANNELS[channelKey];
|
|
1981
2011
|
if (channelKey === 'zalo-bot') {
|
|
1982
|
-
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.')));
|
|
1983
2013
|
}
|
|
1984
2014
|
setupStep = 'botSetup';
|
|
1985
2015
|
continue;
|
|
@@ -2000,8 +2030,8 @@ async function main() {
|
|
|
2000
2030
|
}
|
|
2001
2031
|
|
|
2002
2032
|
if (setupStep === 'userInfo') {
|
|
2003
|
-
console.log(chalk.bold('\n' + (isVi ? '
|
|
2004
|
-
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 });
|
|
2005
2035
|
if (nextUserInfo === CLI_BACK) {
|
|
2006
2036
|
setupStep = 'botSetup';
|
|
2007
2037
|
continue;
|
|
@@ -2044,7 +2074,7 @@ async function main() {
|
|
|
2044
2074
|
}
|
|
2045
2075
|
|
|
2046
2076
|
if (setupStep === 'projectDir') {
|
|
2047
|
-
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 });
|
|
2048
2078
|
if (nextProjectDir === CLI_BACK) {
|
|
2049
2079
|
setupStep = 'skills';
|
|
2050
2080
|
continue;
|
|
@@ -2062,12 +2092,12 @@ async function main() {
|
|
|
2062
2092
|
const modelsPrimary = providerKey === 'ollama' ? selectedOllamaModel : providerKey === '9router' ? 'smart-route' : provider.models?.[0]?.id || 'gpt-4o-mini';
|
|
2063
2093
|
const hasBrowserDesktop = selectedSkills.includes('browser') && browserMode === 'desktop';
|
|
2064
2094
|
const hasBrowserServer = selectedSkills.includes('browser') && browserMode === 'server';
|
|
2065
|
-
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...'}`));
|
|
2066
2096
|
|
|
2067
2097
|
await fs.ensureDir(projectDir);
|
|
2068
2098
|
|
|
2069
2099
|
|
|
2070
|
-
//
|
|
2100
|
+
// ─── Helper: build .env content per bot ──────────────────────────────────
|
|
2071
2101
|
|
|
2072
2102
|
function buildEnvContentForBot(botIndex) {
|
|
2073
2103
|
const tok = bots[botIndex]?.token || botToken;
|
|
@@ -2108,7 +2138,7 @@ async function main() {
|
|
|
2108
2138
|
});
|
|
2109
2139
|
}
|
|
2110
2140
|
|
|
2111
|
-
//
|
|
2141
|
+
// ─── Create directories and write .env files ─────────────────────────────
|
|
2112
2142
|
if (isMultiBot) {
|
|
2113
2143
|
await fs.ensureDir(path.join(projectDir, '.openclaw'));
|
|
2114
2144
|
if (deployMode === 'docker') {
|
|
@@ -2127,16 +2157,19 @@ async function main() {
|
|
|
2127
2157
|
}
|
|
2128
2158
|
|
|
2129
2159
|
|
|
2130
|
-
//
|
|
2131
|
-
const skillSlugs = SKILLS
|
|
2132
|
-
.filter(s => selectedSkills.includes(s.value) && s.slug)
|
|
2133
|
-
.map(s => s.slug);
|
|
2134
|
-
const skillInstallCmd = skillSlugs.length > 0
|
|
2135
|
-
? skillSlugs.map(s => `
|
|
2136
|
-
: '';
|
|
2160
|
+
// ── Docker artifacts: Dockerfile + docker-compose via shared buildDockerArtifacts() ──────
|
|
2161
|
+
const skillSlugs = SKILLS
|
|
2162
|
+
.filter(s => selectedSkills.includes(s.value) && s.slug)
|
|
2163
|
+
.map(s => s.slug);
|
|
2164
|
+
const skillInstallCmd = skillSlugs.length > 0
|
|
2165
|
+
? skillSlugs.map(s => `ensure_skill ${s}`).join('\n')
|
|
2166
|
+
: '';
|
|
2137
2167
|
const relayInstallCmd = (isMultiBot && channelKey === 'telegram')
|
|
2138
2168
|
? buildRelayPluginInstallCommand('openclaw')
|
|
2139
2169
|
: '';
|
|
2170
|
+
const zaloModInstallCmd = hasZaloPersonal(channelKey)
|
|
2171
|
+
? 'ensure_plugin zalo-mod openclaw-zalo-mod'
|
|
2172
|
+
: '';
|
|
2140
2173
|
const socatBridge = hasBrowserDesktop ? 'socat TCP-LISTEN:9222,fork,reuseaddr TCP:host.docker.internal:9222 &' : '';
|
|
2141
2174
|
const deviceApproveLoop = 'while true; do sleep 5; openclaw devices approve --latest 2>/dev/null || true; done >/dev/null 2>&1 &';
|
|
2142
2175
|
|
|
@@ -2151,11 +2184,12 @@ async function main() {
|
|
|
2151
2184
|
hasBrowser: hasBrowserDesktop || hasBrowserServer,
|
|
2152
2185
|
selectedModel: modelsPrimary,
|
|
2153
2186
|
agentId,
|
|
2154
|
-
runtimeCommandParts: [
|
|
2155
|
-
skillInstallCmd
|
|
2156
|
-
relayInstallCmd
|
|
2157
|
-
|
|
2158
|
-
|
|
2187
|
+
runtimeCommandParts: [
|
|
2188
|
+
skillInstallCmd,
|
|
2189
|
+
relayInstallCmd,
|
|
2190
|
+
zaloModInstallCmd,
|
|
2191
|
+
socatBridge,
|
|
2192
|
+
deviceApproveLoop,
|
|
2159
2193
|
].filter(Boolean),
|
|
2160
2194
|
volumeMount: '../../.openclaw:/root/project/.openclaw',
|
|
2161
2195
|
singleComposeName: `oc-${agentId}`,
|
|
@@ -2276,7 +2310,7 @@ async function main() {
|
|
|
2276
2310
|
);
|
|
2277
2311
|
// Generate ecosystem.config.js for PM2 native multi-bot
|
|
2278
2312
|
if (deployMode === 'native') {
|
|
2279
|
-
// Also write config to ~/.openclaw/
|
|
2313
|
+
// Also write config to ~/.openclaw/ — openclaw binary on Linux/Mac reads from home dir
|
|
2280
2314
|
const homeClawDir = path.join(os.homedir(), '.openclaw');
|
|
2281
2315
|
await fs.ensureDir(homeClawDir);
|
|
2282
2316
|
const homeConfig = JSON.parse(JSON.stringify(sharedConfig));
|
|
@@ -2307,7 +2341,7 @@ async function main() {
|
|
|
2307
2341
|
' }',
|
|
2308
2342
|
].join('\n');
|
|
2309
2343
|
const ecosystemContent = [
|
|
2310
|
-
'// PM2 ecosystem
|
|
2344
|
+
'// PM2 ecosystem — run: pm2 start ecosystem.config.js',
|
|
2311
2345
|
'module.exports = {',
|
|
2312
2346
|
' apps: [',
|
|
2313
2347
|
pm2Apps,
|
|
@@ -2338,12 +2372,12 @@ async function main() {
|
|
|
2338
2372
|
.map((peer) => ({ name: peer.name, agentId: peer.agentId }));
|
|
2339
2373
|
|
|
2340
2374
|
// agentYaml & auth still needed, keep non-workspace writes here
|
|
2341
|
-
// .yaml removed
|
|
2375
|
+
// .yaml removed — OpenClaw reads config exclusively from openclaw.json
|
|
2342
2376
|
if (Object.keys(authProfilesJson).length > 0) {
|
|
2343
2377
|
await fs.writeJson(path.join(rootClawDir, 'agents', meta.agentId, 'agent', 'auth-profiles.json'), authProfilesJson, { spaces: 2 });
|
|
2344
2378
|
}
|
|
2345
2379
|
|
|
2346
|
-
//
|
|
2380
|
+
// ── Workspace files via shared helper ────────────────────────────────
|
|
2347
2381
|
await writeWorkspaceFiles({
|
|
2348
2382
|
workspaceDir,
|
|
2349
2383
|
isVi, botName: meta.name, botDesc: meta.desc, persona: meta.persona,
|
|
@@ -2354,6 +2388,7 @@ async function main() {
|
|
|
2354
2388
|
agentWorkspaceDir: meta.workspaceDir,
|
|
2355
2389
|
isRelayBot: true,
|
|
2356
2390
|
replyToDirectMessages: true,
|
|
2391
|
+
channelKey,
|
|
2357
2392
|
});
|
|
2358
2393
|
}
|
|
2359
2394
|
} else {
|
|
@@ -2410,7 +2445,7 @@ async function main() {
|
|
|
2410
2445
|
|
|
2411
2446
|
await fs.writeJson(path.join(loopBotDir, '.openclaw', 'openclaw.json'), botConfig, { spaces: 2 });
|
|
2412
2447
|
|
|
2413
|
-
//
|
|
2448
|
+
// ── Workspace files: use shared writeWorkspaceFiles() ──────────────────────
|
|
2414
2449
|
const dockerWorkspaceDir = path.join(loopBotDir, '.openclaw', loopWorkspaceDir);
|
|
2415
2450
|
const dockerOwnAliases = [loopBotName, bots[bIndex]?.slashCmd || '', `bot ${bIndex + 1}`].filter(Boolean);
|
|
2416
2451
|
const dockerOtherAgents = teamRoster
|
|
@@ -2435,23 +2470,24 @@ async function main() {
|
|
|
2435
2470
|
agentWorkspaceDir: loopWorkspaceDir,
|
|
2436
2471
|
isRelayBot: isMultiBot,
|
|
2437
2472
|
replyToDirectMessages: true,
|
|
2473
|
+
channelKey,
|
|
2438
2474
|
});
|
|
2439
2475
|
|
|
2440
2476
|
if (isMultiBot) {
|
|
2441
2477
|
// Append per-bot reply rules to AGENTS.md
|
|
2442
2478
|
const otherBotNames = teamRoster.filter((p) => p.idx !== bIndex).map((p) => p.name);
|
|
2443
2479
|
const extraAgentsMd = isVi
|
|
2444
|
-
? `\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-
|
|
2445
|
-
: `\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.`;
|
|
2446
2482
|
await fs.appendFile(path.join(dockerWorkspaceDir, 'AGENTS.md'), extraAgentsMd);
|
|
2447
2483
|
}
|
|
2448
2484
|
} // END FOR LOOP
|
|
2449
2485
|
}
|
|
2450
2486
|
|
|
2451
|
-
//
|
|
2487
|
+
// ── Chrome Debug scripts — via shared builder (same content as wizard ZIP) ─
|
|
2452
2488
|
await writeGeneratedArtifacts(projectDir, buildCliChromeDebugArtifacts());
|
|
2453
2489
|
|
|
2454
|
-
//
|
|
2490
|
+
// ── Uninstall scripts ───────────────────────────────────────────────────────
|
|
2455
2491
|
await writeGeneratedArtifacts(projectDir, buildCliUninstallArtifacts({
|
|
2456
2492
|
deployMode,
|
|
2457
2493
|
osChoice: detectedOS,
|
|
@@ -2461,10 +2497,10 @@ async function main() {
|
|
|
2461
2497
|
: botName,
|
|
2462
2498
|
}));
|
|
2463
2499
|
|
|
2464
|
-
//
|
|
2500
|
+
// ── Upgrade scripts ─────────────────────────────────────────────────────────
|
|
2465
2501
|
await writeGeneratedArtifacts(projectDir, buildCliUpgradeArtifacts());
|
|
2466
2502
|
|
|
2467
|
-
//
|
|
2503
|
+
// ── start-bot.bat / start-bot.sh — one-click restart scripts ─────────────
|
|
2468
2504
|
// Generated for native deployments only (docker has docker compose up)
|
|
2469
2505
|
if (deployMode !== 'docker') {
|
|
2470
2506
|
await writeGeneratedArtifacts(projectDir, buildCliStartBotArtifacts({
|
|
@@ -2480,31 +2516,31 @@ async function main() {
|
|
|
2480
2516
|
console.log(chalk.cyan(
|
|
2481
2517
|
process.platform === 'win32'
|
|
2482
2518
|
? (isVi
|
|
2483
|
-
? `\n
|
|
2484
|
-
: `\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.`)
|
|
2485
2521
|
: (isVi
|
|
2486
|
-
? `\n
|
|
2487
|
-
: `\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.`)
|
|
2488
2524
|
));
|
|
2489
2525
|
}
|
|
2490
2526
|
|
|
2491
|
-
console.log(chalk.green(
|
|
2527
|
+
console.log(chalk.green(`✅ ${isVi ? 'Tạo cấu hình thà nh công!' : 'Configs created successfully!'}`));
|
|
2492
2528
|
|
|
2493
2529
|
installLatestOpenClaw({ isVi, osChoice });
|
|
2494
2530
|
|
|
2495
2531
|
// 7. Auto Run
|
|
2496
2532
|
const autoRun = deployMode === 'docker' ? await confirm({
|
|
2497
|
-
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?',
|
|
2498
2534
|
default: true
|
|
2499
2535
|
}) : false;
|
|
2500
2536
|
|
|
2501
2537
|
if (deployMode === 'docker' && autoRun) {
|
|
2502
|
-
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)...'}`));
|
|
2503
2539
|
const dockerPath = path.join(projectDir, 'docker', 'openclaw');
|
|
2504
2540
|
|
|
2505
2541
|
// Auto-detect Docker Compose V2 (plugin) vs V1 (standalone docker-compose).
|
|
2506
2542
|
// On Ubuntu 24.04 installed via `apt install docker.io`, the Compose V2 plugin
|
|
2507
|
-
// is NOT included
|
|
2543
|
+
// is NOT included — `docker compose` subcommand may not exist or may be broken.
|
|
2508
2544
|
// We test both and use whichever actually works.
|
|
2509
2545
|
let composeCmd, composeArgs;
|
|
2510
2546
|
const detectCompose = () => {
|
|
@@ -2523,8 +2559,8 @@ async function main() {
|
|
|
2523
2559
|
const detected = detectCompose();
|
|
2524
2560
|
if (!detected) {
|
|
2525
2561
|
console.log(chalk.red(isVi
|
|
2526
|
-
? '\n
|
|
2527
|
-
: '\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'));
|
|
2528
2564
|
process.exit(1);
|
|
2529
2565
|
}
|
|
2530
2566
|
composeCmd = detected.cmd;
|
|
@@ -2537,87 +2573,87 @@ async function main() {
|
|
|
2537
2573
|
|
|
2538
2574
|
child.on('close', (code) => {
|
|
2539
2575
|
if (code === 0) {
|
|
2540
|
-
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.'}`));
|
|
2541
2577
|
|
|
2542
2578
|
if (providerKey === '9router') {
|
|
2543
|
-
console.log(chalk.yellow(`\n
|
|
2579
|
+
console.log(chalk.yellow(`\n🔀 ${isVi
|
|
2544
2580
|
? '9Router Dashboard: http://localhost:20128/dashboard'
|
|
2545
2581
|
: '9Router Dashboard: http://localhost:20128/dashboard'}`));
|
|
2546
2582
|
console.log(chalk.gray(isVi
|
|
2547
|
-
? '
|
|
2548
|
-
: '
|
|
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...)'));
|
|
2549
2585
|
console.log(chalk.gray(isVi
|
|
2550
|
-
? '
|
|
2551
|
-
: '
|
|
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'));
|
|
2552
2588
|
}
|
|
2553
2589
|
|
|
2554
2590
|
if (channelKey === 'telegram') {
|
|
2555
|
-
console.log(chalk.cyan(`\n
|
|
2556
|
-
? '
|
|
2591
|
+
console.log(chalk.cyan(`\n💬 ${isVi
|
|
2592
|
+
? 'Nhắn tin cho bot trên Telegram là dùng được ngay!'
|
|
2557
2593
|
: 'Just message your bot on Telegram to start chatting!'}`));
|
|
2558
2594
|
if (isMultiBot) {
|
|
2559
|
-
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}`));
|
|
2560
2596
|
console.log(chalk.gray(isVi
|
|
2561
|
-
? '
|
|
2562
|
-
: '
|
|
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.'));
|
|
2563
2599
|
}
|
|
2564
2600
|
} else if (channelKey === 'zalo-personal') {
|
|
2565
2601
|
printZaloPersonalLoginInfo({ isVi, deployMode: 'docker', projectDir });
|
|
2566
2602
|
}
|
|
2567
2603
|
} else {
|
|
2568
|
-
console.log(chalk.red(`\n
|
|
2604
|
+
console.log(chalk.red(`\n⌠Docker exited with code ${code}`));
|
|
2569
2605
|
console.log(chalk.yellow(isVi
|
|
2570
|
-
? `\n
|
|
2571
|
-
: `\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`));
|
|
2572
2608
|
}
|
|
2573
2609
|
});
|
|
2574
2610
|
|
|
2575
2611
|
}
|
|
2576
2612
|
if (deployMode === 'docker') {
|
|
2577
2613
|
|
|
2578
|
-
//
|
|
2614
|
+
// ── Auto-install openclaw binary if not present ──────────────────────────
|
|
2579
2615
|
const isOpenClawInstalled = () => { try { execSync('openclaw --version', { stdio: 'ignore' }); return true; } catch { return false; } };
|
|
2580
2616
|
if (!isOpenClawInstalled()) {
|
|
2581
2617
|
console.log(chalk.cyan(isVi
|
|
2582
|
-
? `\n
|
|
2583
|
-
: `\n
|
|
2618
|
+
? `\n📦 Äang cà i openclaw binary (${OPENCLAW_NPM_SPEC})...`
|
|
2619
|
+
: `\n📦 Installing openclaw binary (${OPENCLAW_NPM_SPEC})...`));
|
|
2584
2620
|
try {
|
|
2585
2621
|
execSync(`npm install -g ${OPENCLAW_NPM_SPEC}`, { stdio: 'inherit' });
|
|
2586
|
-
console.log(chalk.green(isVi ? '
|
|
2622
|
+
console.log(chalk.green(isVi ? '✅ openclaw đã cà i xong!' : '✅ openclaw installed!'));
|
|
2587
2623
|
} catch {
|
|
2588
2624
|
console.log(chalk.yellow(isVi
|
|
2589
|
-
?
|
|
2590
|
-
:
|
|
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}`));
|
|
2591
2627
|
}
|
|
2592
2628
|
}
|
|
2593
2629
|
|
|
2594
2630
|
if (isMultiBot && channelKey === 'telegram') {
|
|
2595
|
-
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)}`));
|
|
2596
2632
|
}
|
|
2597
2633
|
} else {
|
|
2598
2634
|
if (!isOpenClawInstalled()) {
|
|
2599
2635
|
console.log(chalk.cyan(isVi
|
|
2600
|
-
? `\n
|
|
2601
|
-
: `\n
|
|
2636
|
+
? `\n📦 Dang cai openclaw binary (${OPENCLAW_NPM_SPEC})...`
|
|
2637
|
+
: `\n📦 Installing openclaw binary (${OPENCLAW_NPM_SPEC})...`));
|
|
2602
2638
|
if (!installGlobalPackage(OPENCLAW_NPM_SPEC, { isVi, osChoice, displayName: 'openclaw' })) {
|
|
2603
2639
|
process.exit(1);
|
|
2604
2640
|
}
|
|
2605
|
-
console.log(chalk.green(isVi ? '
|
|
2641
|
+
console.log(chalk.green(isVi ? '✅ openclaw da cai xong!' : '✅ openclaw installed!'));
|
|
2606
2642
|
}
|
|
2607
2643
|
|
|
2608
2644
|
if (providerKey === '9router') {
|
|
2609
2645
|
if (shouldReuseInstalledGlobals() && is9RouterInstalled()) {
|
|
2610
2646
|
console.log(chalk.green(isVi
|
|
2611
|
-
? '\n
|
|
2612
|
-
: '\n
|
|
2647
|
+
? '\nâ™»ï¸ Dang dung lai 9Router da cai san de test nhanh.'
|
|
2648
|
+
: '\nâ™»ï¸ Reusing the installed 9Router for a faster test run.'));
|
|
2613
2649
|
} else if (!is9RouterInstalled()) {
|
|
2614
2650
|
console.log(chalk.cyan(isVi
|
|
2615
|
-
? '\n
|
|
2616
|
-
: '\n
|
|
2651
|
+
? '\n📦 Dang cai 9Router binary (npm install -g 9router)...'
|
|
2652
|
+
: '\n📦 Installing 9Router binary (npm install -g 9router)...'));
|
|
2617
2653
|
if (!installGlobalPackage('9router@latest', { isVi, osChoice, displayName: '9Router' })) {
|
|
2618
2654
|
process.exit(1);
|
|
2619
2655
|
}
|
|
2620
|
-
console.log(chalk.green(isVi ? '
|
|
2656
|
+
console.log(chalk.green(isVi ? '✅ 9Router da cai xong!' : '✅ 9Router installed!'));
|
|
2621
2657
|
}
|
|
2622
2658
|
}
|
|
2623
2659
|
|
|
@@ -2643,7 +2679,7 @@ async function main() {
|
|
|
2643
2679
|
|
|
2644
2680
|
if (osChoice === 'vps') {
|
|
2645
2681
|
if (!isPm2Installed()) {
|
|
2646
|
-
console.log(chalk.cyan(isVi ? '\n
|
|
2682
|
+
console.log(chalk.cyan(isVi ? '\n📦 Dang cai PM2...' : '\n📦 Installing PM2...'));
|
|
2647
2683
|
if (!installGlobalPackage('pm2@latest', { isVi, osChoice, displayName: 'PM2' })) {
|
|
2648
2684
|
process.exit(1);
|
|
2649
2685
|
}
|
|
@@ -2658,7 +2694,7 @@ async function main() {
|
|
|
2658
2694
|
stdio: 'inherit',
|
|
2659
2695
|
shell: true
|
|
2660
2696
|
});
|
|
2661
|
-
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.'}`));
|
|
2662
2698
|
console.log(chalk.gray(isVi ? ` Xem log: pm2 logs ${getNativePm2AppName(true)}` : ` View logs: pm2 logs ${getNativePm2AppName(true)}`));
|
|
2663
2699
|
printNativeDashboardAccessInfo({ isVi, providerKey, projectDir });
|
|
2664
2700
|
if (channelKey === 'zalo-personal') {
|
|
@@ -2688,7 +2724,7 @@ async function main() {
|
|
|
2688
2724
|
env: getProjectRuntimeEnv(projectDir)
|
|
2689
2725
|
});
|
|
2690
2726
|
runPm2Save({ projectDir, isVi });
|
|
2691
|
-
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.'}`));
|
|
2692
2728
|
console.log(chalk.gray(isVi ? ` Xem log: pm2 logs ${appName}` : ` View logs: pm2 logs ${appName}`));
|
|
2693
2729
|
printNativeDashboardAccessInfo({ isVi, providerKey, projectDir });
|
|
2694
2730
|
if (channelKey === 'zalo-personal') {
|
|
@@ -2714,8 +2750,8 @@ async function main() {
|
|
|
2714
2750
|
: ' 9Router dashboard: http://localhost:20128/dashboard'));
|
|
2715
2751
|
if (!routerHealth.ok) {
|
|
2716
2752
|
console.log(chalk.yellow(isVi
|
|
2717
|
-
? `
|
|
2718
|
-
: `
|
|
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}`));
|
|
2719
2755
|
}
|
|
2720
2756
|
}
|
|
2721
2757
|
if (hasZaloPersonal(channelKey)) {
|
|
@@ -2747,9 +2783,9 @@ async function main() {
|
|
|
2747
2783
|
return;
|
|
2748
2784
|
}
|
|
2749
2785
|
|
|
2750
|
-
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.'}`));
|
|
2751
2787
|
if (isMultiBot && channelKey === 'telegram') {
|
|
2752
|
-
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)}`));
|
|
2753
2789
|
}
|
|
2754
2790
|
}
|
|
2755
2791
|
}
|