create-openclaw-bot 5.6.1 → 5.6.3
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 +1224 -639
- package/dist/setup/data/header.js +4 -4
- package/dist/setup/shared/common-gen.js +10 -4
- package/dist/setup/shared/docker-gen.js +59 -19
- package/dist/setup/shared/workspace-gen.js +71 -145
- package/dist/setup.js +134 -42
- package/package.json +4 -4
package/dist/cli.js
CHANGED
|
@@ -9,34 +9,35 @@ import { spawn, execSync, execFileSync } from 'child_process';
|
|
|
9
9
|
import { createRequire } from 'module';
|
|
10
10
|
|
|
11
11
|
// ─── Shared generators (dual-mode IIFE + CJS) ────────────────────────────────
|
|
12
|
-
// These modules export via module.exports when required from Node.js
|
|
13
|
-
const _require = createRequire(import.meta.url);
|
|
14
|
-
|
|
15
|
-
function loadSharedModule(modulePath, globalName) {
|
|
16
|
-
const loaded = _require(modulePath);
|
|
17
|
-
if (loaded && Object.keys(loaded).length > 0) {
|
|
18
|
-
return loaded;
|
|
19
|
-
}
|
|
20
|
-
return globalThis[globalName] || loaded || {};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const {
|
|
24
|
-
OPENCLAW_NPM_SPEC,
|
|
25
|
-
OPENCLAW_RUNTIME_PACKAGES,
|
|
26
|
-
TELEGRAM_RELAY_PLUGIN_SPEC,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
12
|
+
// These modules export via module.exports when required from Node.js
|
|
13
|
+
const _require = createRequire(import.meta.url);
|
|
14
|
+
|
|
15
|
+
function loadSharedModule(modulePath, globalName) {
|
|
16
|
+
const loaded = _require(modulePath);
|
|
17
|
+
if (loaded && Object.keys(loaded).length > 0) {
|
|
18
|
+
return loaded;
|
|
19
|
+
}
|
|
20
|
+
return globalThis[globalName] || loaded || {};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const {
|
|
24
|
+
OPENCLAW_NPM_SPEC,
|
|
25
|
+
OPENCLAW_RUNTIME_PACKAGES,
|
|
26
|
+
TELEGRAM_RELAY_PLUGIN_SPEC,
|
|
27
|
+
TELEGRAM_SETUP_GUIDE_FILENAME,
|
|
28
|
+
buildRelayPluginInstallCommand,
|
|
29
|
+
buildTelegramPostInstallChecklist,
|
|
30
|
+
} = loadSharedModule('./setup/shared/common-gen.js', '__openclawCommon');
|
|
31
|
+
|
|
32
|
+
const {
|
|
33
|
+
buildDockerArtifacts,
|
|
34
|
+
} = loadSharedModule('./setup/shared/docker-gen.js', '__openclawDockerGen');
|
|
35
|
+
|
|
36
|
+
const {
|
|
37
|
+
buildWorkspaceFileMap,
|
|
38
|
+
} = loadSharedModule('./setup/shared/workspace-gen.js', '__openclawWorkspace');
|
|
39
|
+
|
|
40
|
+
const dataExport = loadSharedModule('./setup/data/index.js', '__openclawData');
|
|
40
41
|
|
|
41
42
|
const {
|
|
42
43
|
PROVIDERS: _PROVIDERS,
|
|
@@ -45,14 +46,14 @@ const {
|
|
|
45
46
|
OLLAMA_MODELS,
|
|
46
47
|
} = dataExport;
|
|
47
48
|
|
|
48
|
-
const {
|
|
49
|
-
buildCliChromeDebugArtifacts,
|
|
50
|
-
buildCliUninstallArtifacts,
|
|
51
|
-
buildCliUpgradeArtifacts,
|
|
52
|
-
buildCliStartBotArtifacts,
|
|
53
|
-
} = loadSharedModule('./setup/shared/install-gen.js', '__openclawInstall');
|
|
49
|
+
const {
|
|
50
|
+
buildCliChromeDebugArtifacts,
|
|
51
|
+
buildCliUninstallArtifacts,
|
|
52
|
+
buildCliUpgradeArtifacts,
|
|
53
|
+
buildCliStartBotArtifacts,
|
|
54
|
+
} = loadSharedModule('./setup/shared/install-gen.js', '__openclawInstall');
|
|
54
55
|
|
|
55
|
-
function installRelayPluginForProject(projectDir, isVi) {
|
|
56
|
+
function installRelayPluginForProject(projectDir, isVi) {
|
|
56
57
|
try {
|
|
57
58
|
execSync(`openclaw plugins install ${TELEGRAM_RELAY_PLUGIN_SPEC}`, { cwd: projectDir, stdio: 'ignore' });
|
|
58
59
|
return true;
|
|
@@ -393,16 +394,16 @@ function installLatestOpenClaw({ isVi, osChoice }) {
|
|
|
393
394
|
}
|
|
394
395
|
|
|
395
396
|
console.log(chalk.cyan(isVi
|
|
396
|
-
?
|
|
397
|
-
:
|
|
397
|
+
? `\n📦 Dang cai/cap nhat ${OPENCLAW_NPM_SPEC}...`
|
|
398
|
+
: `\n📦 Installing/updating ${OPENCLAW_NPM_SPEC}...`));
|
|
398
399
|
|
|
399
400
|
if (!installGlobalPackage(OPENCLAW_NPM_SPEC, { isVi, osChoice, displayName: 'openclaw' })) {
|
|
400
401
|
process.exit(1);
|
|
401
402
|
}
|
|
402
403
|
|
|
403
404
|
console.log(chalk.green(isVi
|
|
404
|
-
?
|
|
405
|
-
:
|
|
405
|
+
? `✅ openclaw da duoc ghim dung ban ${OPENCLAW_NPM_SPEC}!`
|
|
406
|
+
: `✅ openclaw is now pinned to ${OPENCLAW_NPM_SPEC}!`));
|
|
406
407
|
}
|
|
407
408
|
|
|
408
409
|
// ─── Shared from docker-gen.js ──────────────────────────────────────────────
|
|
@@ -557,28 +558,28 @@ function printZaloPersonalLoginInfo({ isVi, deployMode, projectDir }) {
|
|
|
557
558
|
: ` → If needed, copy the QR into the project folder with: ${copyCmd}`));
|
|
558
559
|
}
|
|
559
560
|
|
|
560
|
-
async function waitForFile(filePath, timeoutMs = 15000, intervalMs = 500) {
|
|
561
|
-
const deadline = Date.now() + timeoutMs;
|
|
562
|
-
while (Date.now() < deadline) {
|
|
563
|
-
if (await fs.pathExists(filePath)) {
|
|
564
|
-
return true;
|
|
561
|
+
async function waitForFile(filePath, timeoutMs = 15000, intervalMs = 500) {
|
|
562
|
+
const deadline = Date.now() + timeoutMs;
|
|
563
|
+
while (Date.now() < deadline) {
|
|
564
|
+
if (await fs.pathExists(filePath)) {
|
|
565
|
+
return true;
|
|
565
566
|
}
|
|
566
567
|
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
567
|
-
}
|
|
568
|
-
return fs.pathExists(filePath);
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
async function writeGeneratedArtifacts(targetDir, artifacts = []) {
|
|
572
|
-
for (const artifact of artifacts.filter(Boolean)) {
|
|
573
|
-
const artifactPath = path.join(targetDir, artifact.name);
|
|
574
|
-
await fs.writeFile(artifactPath, artifact.content, 'utf8');
|
|
575
|
-
if (artifact.executable || artifact.name.endsWith('.sh')) {
|
|
576
|
-
try { await fs.chmod(artifactPath, 0o755); } catch (_) {}
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
function extractZaloPairingCode(text) {
|
|
568
|
+
}
|
|
569
|
+
return fs.pathExists(filePath);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
async function writeGeneratedArtifacts(targetDir, artifacts = []) {
|
|
573
|
+
for (const artifact of artifacts.filter(Boolean)) {
|
|
574
|
+
const artifactPath = path.join(targetDir, artifact.name);
|
|
575
|
+
await fs.writeFile(artifactPath, artifact.content, 'utf8');
|
|
576
|
+
if (artifact.executable || artifact.name.endsWith('.sh')) {
|
|
577
|
+
try { await fs.chmod(artifactPath, 0o755); } catch (_) {}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function extractZaloPairingCode(text) {
|
|
582
583
|
const value = String(text || '');
|
|
583
584
|
const explicitCommandMatch = value.match(/openclaw pairing approve zalouser\s+([A-Z0-9-]+)/i);
|
|
584
585
|
if (explicitCommandMatch) {
|
|
@@ -698,10 +699,10 @@ async function runNativeZaloPersonalLoginFlow({ isVi, projectDir }) {
|
|
|
698
699
|
}
|
|
699
700
|
}
|
|
700
701
|
|
|
701
|
-
function runPm2Save({ projectDir, isVi }) {
|
|
702
|
-
try {
|
|
703
|
-
execSync('pm2 save', {
|
|
704
|
-
cwd: projectDir,
|
|
702
|
+
function runPm2Save({ projectDir, isVi }) {
|
|
703
|
+
try {
|
|
704
|
+
execSync('pm2 save', {
|
|
705
|
+
cwd: projectDir,
|
|
705
706
|
stdio: 'inherit',
|
|
706
707
|
shell: true,
|
|
707
708
|
env: process.env
|
|
@@ -710,138 +711,138 @@ function runPm2Save({ projectDir, isVi }) {
|
|
|
710
711
|
console.log(chalk.yellow(isVi
|
|
711
712
|
? '⚠️ PM2 save khong hoan tat. Bot van co the dang chay, nhung hay thu chay lai `pm2 save` sau.'
|
|
712
713
|
: '⚠️ PM2 save did not complete. The app may still be running, but try `pm2 save` again afterwards.'));
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
function getDetectedOsChoice() {
|
|
717
|
-
const detectedPlatform = process.platform;
|
|
718
|
-
return detectedPlatform === 'win32' ? 'windows'
|
|
719
|
-
: detectedPlatform === 'darwin' ? 'macos'
|
|
720
|
-
: 'vps';
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
function getCliSubcommand() {
|
|
724
|
-
return String(process.argv[2] || '').trim().toLowerCase();
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
function findProjectDir(startDir = process.cwd()) {
|
|
728
|
-
let currentDir = path.resolve(startDir);
|
|
729
|
-
|
|
730
|
-
while (true) {
|
|
731
|
-
if (
|
|
732
|
-
fs.existsSync(path.join(currentDir, '.openclaw'))
|
|
733
|
-
|| fs.existsSync(path.join(currentDir, 'docker', 'openclaw'))
|
|
734
|
-
) {
|
|
735
|
-
return currentDir;
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
const isDockerOpenClawDir =
|
|
739
|
-
path.basename(currentDir).toLowerCase() === 'openclaw'
|
|
740
|
-
&& path.basename(path.dirname(currentDir)).toLowerCase() === 'docker';
|
|
741
|
-
if (isDockerOpenClawDir) {
|
|
742
|
-
const projectDir = path.dirname(path.dirname(currentDir));
|
|
743
|
-
if (
|
|
744
|
-
fs.existsSync(path.join(projectDir, '.openclaw'))
|
|
745
|
-
|| fs.existsSync(path.join(currentDir, 'docker-compose.yml'))
|
|
746
|
-
) {
|
|
747
|
-
return projectDir;
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
const parentDir = path.dirname(currentDir);
|
|
752
|
-
if (parentDir === currentDir) {
|
|
753
|
-
return null;
|
|
754
|
-
}
|
|
755
|
-
currentDir = parentDir;
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
function detectProjectDeployMode(projectDir) {
|
|
760
|
-
const dockerDir = path.join(projectDir, 'docker', 'openclaw');
|
|
761
|
-
if (
|
|
762
|
-
fs.existsSync(path.join(dockerDir, 'docker-compose.yml'))
|
|
763
|
-
|| fs.existsSync(path.join(dockerDir, '.env'))
|
|
764
|
-
) {
|
|
765
|
-
return 'docker';
|
|
766
|
-
}
|
|
767
|
-
return 'native';
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
function detectProjectBotName(projectDir) {
|
|
771
|
-
try {
|
|
772
|
-
const configPath = path.join(projectDir, '.openclaw', 'openclaw.json');
|
|
773
|
-
if (fs.existsSync(configPath)) {
|
|
774
|
-
const config = fs.readJsonSync(configPath);
|
|
775
|
-
const firstAgentId = config?.agents?.list?.[0]?.id;
|
|
776
|
-
if (firstAgentId) {
|
|
777
|
-
return firstAgentId;
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
} catch {
|
|
781
|
-
// fallback below
|
|
782
|
-
}
|
|
783
|
-
return path.basename(projectDir);
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
function detectProjectUses9Router(projectDir) {
|
|
787
|
-
try {
|
|
788
|
-
const configPath = path.join(projectDir, '.openclaw', 'openclaw.json');
|
|
789
|
-
if (fs.existsSync(configPath)) {
|
|
790
|
-
const config = fs.readJsonSync(configPath);
|
|
791
|
-
if (config?.models?.providers?.['9router']) {
|
|
792
|
-
return true;
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
} catch {
|
|
796
|
-
// fallback below
|
|
797
|
-
}
|
|
798
|
-
return fs.existsSync(path.join(projectDir, '.9router'));
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
async function runUpgradeCommand() {
|
|
802
|
-
const projectDir = findProjectDir();
|
|
803
|
-
if (!projectDir) {
|
|
804
|
-
console.error(chalk.red('Error: no OpenClaw project found in the current directory tree.'));
|
|
805
|
-
console.error(chalk.yellow('Run this inside the bot project folder that contains .openclaw or docker/openclaw.'));
|
|
806
|
-
process.exit(1);
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
const deployMode = detectProjectDeployMode(projectDir);
|
|
810
|
-
const osChoice = getDetectedOsChoice();
|
|
811
|
-
const botName = detectProjectBotName(projectDir);
|
|
812
|
-
const is9Router = detectProjectUses9Router(projectDir);
|
|
813
|
-
|
|
814
|
-
console.log(chalk.cyan('\nRefreshing generated OpenClaw project artifacts...'));
|
|
815
|
-
console.log(chalk.gray(` Project: ${projectDir}`));
|
|
816
|
-
console.log(chalk.gray(` Mode: ${deployMode}`));
|
|
817
|
-
|
|
818
|
-
await writeGeneratedArtifacts(projectDir, buildCliChromeDebugArtifacts());
|
|
819
|
-
await writeGeneratedArtifacts(projectDir, buildCliUninstallArtifacts({
|
|
820
|
-
deployMode,
|
|
821
|
-
osChoice,
|
|
822
|
-
projectDir,
|
|
823
|
-
botName,
|
|
824
|
-
}));
|
|
825
|
-
await writeGeneratedArtifacts(projectDir, buildCliUpgradeArtifacts());
|
|
826
|
-
|
|
827
|
-
if (deployMode !== 'docker') {
|
|
828
|
-
await writeGeneratedArtifacts(projectDir, buildCliStartBotArtifacts({
|
|
829
|
-
projectDir,
|
|
830
|
-
openclawHome: path.join(projectDir, '.openclaw'),
|
|
831
|
-
is9Router,
|
|
832
|
-
isVi: false,
|
|
833
|
-
}));
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
console.log(chalk.green('\nUpgrade artifacts refreshed successfully.'));
|
|
837
|
-
if (deployMode === 'docker') {
|
|
838
|
-
console.log(chalk.white(` Next: cd ${path.join(projectDir, 'docker', 'openclaw')} && docker compose up -d --build`));
|
|
839
|
-
} else {
|
|
840
|
-
console.log(chalk.white(` Next: run ${process.platform === 'win32' ? '.\\start-bot.bat' : './start-bot.sh'} from ${projectDir}`));
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
function startNative9RouterPm2({ isVi, projectDir, appName, syncScriptPath }) {
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function getDetectedOsChoice() {
|
|
718
|
+
const detectedPlatform = process.platform;
|
|
719
|
+
return detectedPlatform === 'win32' ? 'windows'
|
|
720
|
+
: detectedPlatform === 'darwin' ? 'macos'
|
|
721
|
+
: 'vps';
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function getCliSubcommand() {
|
|
725
|
+
return String(process.argv[2] || '').trim().toLowerCase();
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
function findProjectDir(startDir = process.cwd()) {
|
|
729
|
+
let currentDir = path.resolve(startDir);
|
|
730
|
+
|
|
731
|
+
while (true) {
|
|
732
|
+
if (
|
|
733
|
+
fs.existsSync(path.join(currentDir, '.openclaw'))
|
|
734
|
+
|| fs.existsSync(path.join(currentDir, 'docker', 'openclaw'))
|
|
735
|
+
) {
|
|
736
|
+
return currentDir;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
const isDockerOpenClawDir =
|
|
740
|
+
path.basename(currentDir).toLowerCase() === 'openclaw'
|
|
741
|
+
&& path.basename(path.dirname(currentDir)).toLowerCase() === 'docker';
|
|
742
|
+
if (isDockerOpenClawDir) {
|
|
743
|
+
const projectDir = path.dirname(path.dirname(currentDir));
|
|
744
|
+
if (
|
|
745
|
+
fs.existsSync(path.join(projectDir, '.openclaw'))
|
|
746
|
+
|| fs.existsSync(path.join(currentDir, 'docker-compose.yml'))
|
|
747
|
+
) {
|
|
748
|
+
return projectDir;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
const parentDir = path.dirname(currentDir);
|
|
753
|
+
if (parentDir === currentDir) {
|
|
754
|
+
return null;
|
|
755
|
+
}
|
|
756
|
+
currentDir = parentDir;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
function detectProjectDeployMode(projectDir) {
|
|
761
|
+
const dockerDir = path.join(projectDir, 'docker', 'openclaw');
|
|
762
|
+
if (
|
|
763
|
+
fs.existsSync(path.join(dockerDir, 'docker-compose.yml'))
|
|
764
|
+
|| fs.existsSync(path.join(dockerDir, '.env'))
|
|
765
|
+
) {
|
|
766
|
+
return 'docker';
|
|
767
|
+
}
|
|
768
|
+
return 'native';
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
function detectProjectBotName(projectDir) {
|
|
772
|
+
try {
|
|
773
|
+
const configPath = path.join(projectDir, '.openclaw', 'openclaw.json');
|
|
774
|
+
if (fs.existsSync(configPath)) {
|
|
775
|
+
const config = fs.readJsonSync(configPath);
|
|
776
|
+
const firstAgentId = config?.agents?.list?.[0]?.id;
|
|
777
|
+
if (firstAgentId) {
|
|
778
|
+
return firstAgentId;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
} catch {
|
|
782
|
+
// fallback below
|
|
783
|
+
}
|
|
784
|
+
return path.basename(projectDir);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function detectProjectUses9Router(projectDir) {
|
|
788
|
+
try {
|
|
789
|
+
const configPath = path.join(projectDir, '.openclaw', 'openclaw.json');
|
|
790
|
+
if (fs.existsSync(configPath)) {
|
|
791
|
+
const config = fs.readJsonSync(configPath);
|
|
792
|
+
if (config?.models?.providers?.['9router']) {
|
|
793
|
+
return true;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
} catch {
|
|
797
|
+
// fallback below
|
|
798
|
+
}
|
|
799
|
+
return fs.existsSync(path.join(projectDir, '.9router'));
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
async function runUpgradeCommand() {
|
|
803
|
+
const projectDir = findProjectDir();
|
|
804
|
+
if (!projectDir) {
|
|
805
|
+
console.error(chalk.red('Error: no OpenClaw project found in the current directory tree.'));
|
|
806
|
+
console.error(chalk.yellow('Run this inside the bot project folder that contains .openclaw or docker/openclaw.'));
|
|
807
|
+
process.exit(1);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
const deployMode = detectProjectDeployMode(projectDir);
|
|
811
|
+
const osChoice = getDetectedOsChoice();
|
|
812
|
+
const botName = detectProjectBotName(projectDir);
|
|
813
|
+
const is9Router = detectProjectUses9Router(projectDir);
|
|
814
|
+
|
|
815
|
+
console.log(chalk.cyan('\nRefreshing generated OpenClaw project artifacts...'));
|
|
816
|
+
console.log(chalk.gray(` Project: ${projectDir}`));
|
|
817
|
+
console.log(chalk.gray(` Mode: ${deployMode}`));
|
|
818
|
+
|
|
819
|
+
await writeGeneratedArtifacts(projectDir, buildCliChromeDebugArtifacts());
|
|
820
|
+
await writeGeneratedArtifacts(projectDir, buildCliUninstallArtifacts({
|
|
821
|
+
deployMode,
|
|
822
|
+
osChoice,
|
|
823
|
+
projectDir,
|
|
824
|
+
botName,
|
|
825
|
+
}));
|
|
826
|
+
await writeGeneratedArtifacts(projectDir, buildCliUpgradeArtifacts());
|
|
827
|
+
|
|
828
|
+
if (deployMode !== 'docker') {
|
|
829
|
+
await writeGeneratedArtifacts(projectDir, buildCliStartBotArtifacts({
|
|
830
|
+
projectDir,
|
|
831
|
+
openclawHome: path.join(projectDir, '.openclaw'),
|
|
832
|
+
is9Router,
|
|
833
|
+
isVi: false,
|
|
834
|
+
}));
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
console.log(chalk.green('\nUpgrade artifacts refreshed successfully.'));
|
|
838
|
+
if (deployMode === 'docker') {
|
|
839
|
+
console.log(chalk.white(` Next: cd ${path.join(projectDir, 'docker', 'openclaw')} && docker compose up -d --build`));
|
|
840
|
+
} else {
|
|
841
|
+
console.log(chalk.white(` Next: run ${process.platform === 'win32' ? '.\\start-bot.bat' : './start-bot.sh'} from ${projectDir}`));
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
function startNative9RouterPm2({ isVi, projectDir, appName, syncScriptPath }) {
|
|
845
846
|
const routerAppName = `${appName}-9router`;
|
|
846
847
|
const routerLaunch = resolveNative9RouterDesktopLaunch();
|
|
847
848
|
const normalizedProjectDir = projectDir.replace(/\\/g, '/');
|
|
@@ -934,9 +935,9 @@ function providerSupportsMemoryEmbeddings(providerKey) {
|
|
|
934
935
|
return !!PROVIDERS[providerKey]?.supportsEmbeddings;
|
|
935
936
|
}
|
|
936
937
|
|
|
937
|
-
function getCliSkillChoices({ providerKey, isVi }) {
|
|
938
|
-
const memoryRecommended = providerSupportsMemoryEmbeddings(providerKey);
|
|
939
|
-
return SKILLS
|
|
938
|
+
function getCliSkillChoices({ providerKey, isVi }) {
|
|
939
|
+
const memoryRecommended = providerSupportsMemoryEmbeddings(providerKey);
|
|
940
|
+
return SKILLS
|
|
940
941
|
.filter((skill) => skill.value !== 'memory' || providerSupportsMemoryEmbeddings(providerKey) || skill.id === 'memory')
|
|
941
942
|
.map((skill) => {
|
|
942
943
|
const value = skill.value || skill.id;
|
|
@@ -951,8 +952,677 @@ function getCliSkillChoices({ providerKey, isVi }) {
|
|
|
951
952
|
value,
|
|
952
953
|
checked: value === 'browser' || value === 'scheduler' || (value === 'memory' && memoryRecommended),
|
|
953
954
|
};
|
|
954
|
-
});
|
|
955
|
-
}
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
const CLI_BACK = '__openclaw_cli_back__';
|
|
959
|
+
|
|
960
|
+
function getBackChoice(isVi) {
|
|
961
|
+
return {
|
|
962
|
+
name: isVi ? '← Quay lại' : '← Back',
|
|
963
|
+
value: CLI_BACK,
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
function withBackHint(message, isVi) {
|
|
968
|
+
return `${message} ${isVi ? '(gõ "back" để quay lại)' : '(type "back" to go back)'}`;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
async function selectWithBack({ message, choices, defaultValue, allowBack = false, isVi = true }) {
|
|
972
|
+
const finalChoices = allowBack ? [...choices, getBackChoice(isVi)] : choices;
|
|
973
|
+
return select({
|
|
974
|
+
message,
|
|
975
|
+
choices: finalChoices,
|
|
976
|
+
...(defaultValue ? { default: defaultValue } : {}),
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
async function inputWithBack({ message, defaultValue = '', required = false, allowBack = false, isVi = true }) {
|
|
981
|
+
const value = await input({
|
|
982
|
+
message: allowBack ? withBackHint(message, isVi) : message,
|
|
983
|
+
default: defaultValue,
|
|
984
|
+
required,
|
|
985
|
+
});
|
|
986
|
+
if (allowBack && String(value || '').trim().toLowerCase() === 'back') {
|
|
987
|
+
return CLI_BACK;
|
|
988
|
+
}
|
|
989
|
+
return value;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
async function checkboxWithBack({ message, choices, isVi = true, allowBack = false }) {
|
|
993
|
+
const finalChoices = allowBack ? [...choices, getBackChoice(isVi)] : choices;
|
|
994
|
+
const value = await checkbox({
|
|
995
|
+
message,
|
|
996
|
+
choices: finalChoices,
|
|
997
|
+
});
|
|
998
|
+
return allowBack && value.includes(CLI_BACK) ? CLI_BACK : value;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
async function collectBotSetupStep({
|
|
1002
|
+
isVi,
|
|
1003
|
+
channelKey,
|
|
1004
|
+
channel,
|
|
1005
|
+
existingBots = [],
|
|
1006
|
+
existingBotCount = 1,
|
|
1007
|
+
existingGroupId = '',
|
|
1008
|
+
}) {
|
|
1009
|
+
let botCount = channelKey === 'telegram' ? existingBotCount : 1;
|
|
1010
|
+
let groupId = existingGroupId;
|
|
1011
|
+
const bots = [];
|
|
1012
|
+
|
|
1013
|
+
if (channelKey === 'telegram') {
|
|
1014
|
+
const botCountValue = await selectWithBack({
|
|
1015
|
+
message: isVi ? 'Bạn muốn cài bao nhiêu Telegram bot?' : 'How many Telegram bots do you want to deploy?',
|
|
1016
|
+
choices: [
|
|
1017
|
+
{ name: '1 bot (single)', value: '1' },
|
|
1018
|
+
{ name: '2 bots (Department Room)', value: '2' },
|
|
1019
|
+
{ name: '3 bots', value: '3' },
|
|
1020
|
+
{ name: '4 bots', value: '4' },
|
|
1021
|
+
{ name: '5 bots', value: '5' },
|
|
1022
|
+
],
|
|
1023
|
+
defaultValue: String(existingBotCount || 1),
|
|
1024
|
+
allowBack: true,
|
|
1025
|
+
isVi,
|
|
1026
|
+
});
|
|
1027
|
+
if (botCountValue === CLI_BACK) {
|
|
1028
|
+
return { back: true };
|
|
1029
|
+
}
|
|
1030
|
+
botCount = parseInt(botCountValue, 10);
|
|
1031
|
+
|
|
1032
|
+
if (botCount > 1) {
|
|
1033
|
+
const groupOption = await selectWithBack({
|
|
1034
|
+
message: isVi ? 'Bạn có sẵn Telegram Group chưa?' : 'Do you already have a Telegram Group?',
|
|
1035
|
+
choices: [
|
|
1036
|
+
{ 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' },
|
|
1037
|
+
{ name: isVi ? '🔗 Đã có group — nhập Group ID ngay' : '🔗 Already have a group — enter Group ID now', value: 'existing' }
|
|
1038
|
+
],
|
|
1039
|
+
defaultValue: groupId ? 'existing' : 'create',
|
|
1040
|
+
allowBack: true,
|
|
1041
|
+
isVi,
|
|
1042
|
+
});
|
|
1043
|
+
if (groupOption === CLI_BACK) {
|
|
1044
|
+
return { back: true };
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
if (groupOption === 'existing') {
|
|
1048
|
+
console.log(chalk.dim(isVi
|
|
1049
|
+
? '\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'
|
|
1050
|
+
: '\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'));
|
|
1051
|
+
const nextGroupId = await inputWithBack({
|
|
1052
|
+
message: isVi ? 'Telegram Group ID (VD: -1001234567890):' : 'Telegram Group ID (e.g. -1001234567890):',
|
|
1053
|
+
defaultValue: groupId,
|
|
1054
|
+
allowBack: true,
|
|
1055
|
+
isVi,
|
|
1056
|
+
});
|
|
1057
|
+
if (nextGroupId === CLI_BACK) {
|
|
1058
|
+
return { back: true };
|
|
1059
|
+
}
|
|
1060
|
+
groupId = nextGroupId;
|
|
1061
|
+
} else {
|
|
1062
|
+
groupId = '';
|
|
1063
|
+
}
|
|
1064
|
+
} else {
|
|
1065
|
+
groupId = '';
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
for (let i = 0; i < botCount; i++) {
|
|
1069
|
+
console.log(chalk.bold(`\n${isVi ? `─── Bot ${i + 1} / ${botCount} ───` : `─── Bot ${i + 1} / ${botCount} ───`}`));
|
|
1070
|
+
const defaults = existingBots[i] || {};
|
|
1071
|
+
const fields = [
|
|
1072
|
+
{
|
|
1073
|
+
key: 'name',
|
|
1074
|
+
message: isVi ? `Tên Bot ${i + 1}:` : `Bot ${i + 1} name:`,
|
|
1075
|
+
defaultValue: defaults.name || `Bot ${i + 1}`,
|
|
1076
|
+
required: true,
|
|
1077
|
+
},
|
|
1078
|
+
{
|
|
1079
|
+
key: 'slashCmd',
|
|
1080
|
+
message: isVi ? `Slash command (VD: /bot${i + 1}):` : `Slash command (e.g. /bot${i + 1}):`,
|
|
1081
|
+
defaultValue: defaults.slashCmd || `/bot${i + 1}`,
|
|
1082
|
+
required: true,
|
|
1083
|
+
},
|
|
1084
|
+
{
|
|
1085
|
+
key: 'desc',
|
|
1086
|
+
message: isVi ? `Mô tả Bot ${i + 1} (VD: Trợ lý AI cá nhân):` : `Bot ${i + 1} description (e.g. Personal AI assistant):`,
|
|
1087
|
+
defaultValue: defaults.desc || (isVi ? 'Trợ lý AI cá nhân' : 'Personal AI assistant'),
|
|
1088
|
+
required: true,
|
|
1089
|
+
},
|
|
1090
|
+
{
|
|
1091
|
+
key: 'persona',
|
|
1092
|
+
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):`,
|
|
1093
|
+
defaultValue: defaults.persona || '',
|
|
1094
|
+
required: false,
|
|
1095
|
+
},
|
|
1096
|
+
{
|
|
1097
|
+
key: 'token',
|
|
1098
|
+
message: isVi ? 'Bot Token (từ @BotFather):' : 'Bot Token (from @BotFather):',
|
|
1099
|
+
defaultValue: defaults.token || '',
|
|
1100
|
+
required: true,
|
|
1101
|
+
},
|
|
1102
|
+
];
|
|
1103
|
+
|
|
1104
|
+
const draft = { ...defaults };
|
|
1105
|
+
let fieldIndex = 0;
|
|
1106
|
+
while (fieldIndex < fields.length) {
|
|
1107
|
+
const field = fields[fieldIndex];
|
|
1108
|
+
const value = await inputWithBack({
|
|
1109
|
+
message: field.message,
|
|
1110
|
+
defaultValue: draft[field.key] || field.defaultValue,
|
|
1111
|
+
required: field.required,
|
|
1112
|
+
allowBack: true,
|
|
1113
|
+
isVi,
|
|
1114
|
+
});
|
|
1115
|
+
if (value === CLI_BACK) {
|
|
1116
|
+
if (fieldIndex > 0) {
|
|
1117
|
+
fieldIndex--;
|
|
1118
|
+
continue;
|
|
1119
|
+
}
|
|
1120
|
+
return { back: true };
|
|
1121
|
+
}
|
|
1122
|
+
draft[field.key] = value;
|
|
1123
|
+
fieldIndex++;
|
|
1124
|
+
}
|
|
1125
|
+
bots.push(draft);
|
|
1126
|
+
}
|
|
1127
|
+
} else if (channelKey !== 'zalo-personal') {
|
|
1128
|
+
const defaults = existingBots[0] || {};
|
|
1129
|
+
const fields = [
|
|
1130
|
+
{
|
|
1131
|
+
key: 'name',
|
|
1132
|
+
message: isVi ? 'Tên Bot:' : 'Bot Name:',
|
|
1133
|
+
defaultValue: defaults.name || 'Chat Bot',
|
|
1134
|
+
required: true,
|
|
1135
|
+
},
|
|
1136
|
+
{
|
|
1137
|
+
key: 'desc',
|
|
1138
|
+
message: isVi ? 'Mô tả Bot:' : 'Bot Description:',
|
|
1139
|
+
defaultValue: defaults.desc || (isVi ? 'Trợ lý AI cá nhân' : 'Personal AI assistant'),
|
|
1140
|
+
required: true,
|
|
1141
|
+
},
|
|
1142
|
+
{
|
|
1143
|
+
key: 'persona',
|
|
1144
|
+
message: isVi ? 'Tính cách & quy tắc (VD: gọn gàng, thân thiện):' : 'Persona & rules (e.g. concise, friendly):',
|
|
1145
|
+
defaultValue: defaults.persona || '',
|
|
1146
|
+
required: false,
|
|
1147
|
+
},
|
|
1148
|
+
{
|
|
1149
|
+
key: 'token',
|
|
1150
|
+
message: isVi ? `Nhập ${channel.name} Token:` : `Enter ${channel.name} Token:`,
|
|
1151
|
+
defaultValue: defaults.token || '',
|
|
1152
|
+
required: true,
|
|
1153
|
+
},
|
|
1154
|
+
];
|
|
1155
|
+
const draft = { ...defaults, slashCmd: '' };
|
|
1156
|
+
let fieldIndex = 0;
|
|
1157
|
+
while (fieldIndex < fields.length) {
|
|
1158
|
+
const field = fields[fieldIndex];
|
|
1159
|
+
const value = await inputWithBack({
|
|
1160
|
+
message: field.message,
|
|
1161
|
+
defaultValue: draft[field.key] || field.defaultValue,
|
|
1162
|
+
required: field.required,
|
|
1163
|
+
allowBack: true,
|
|
1164
|
+
isVi,
|
|
1165
|
+
});
|
|
1166
|
+
if (value === CLI_BACK) {
|
|
1167
|
+
if (fieldIndex > 0) {
|
|
1168
|
+
fieldIndex--;
|
|
1169
|
+
continue;
|
|
1170
|
+
}
|
|
1171
|
+
return { back: true };
|
|
1172
|
+
}
|
|
1173
|
+
draft[field.key] = value;
|
|
1174
|
+
fieldIndex++;
|
|
1175
|
+
}
|
|
1176
|
+
bots.push(draft);
|
|
1177
|
+
} else {
|
|
1178
|
+
bots.push({ name: 'Bot', slashCmd: '', desc: '', persona: '', token: '' });
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
return {
|
|
1182
|
+
back: false,
|
|
1183
|
+
botCount,
|
|
1184
|
+
groupId,
|
|
1185
|
+
bots,
|
|
1186
|
+
botToken: bots[0]?.token || '',
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
async function collectBotSetupStepWithGroupBack(options) {
|
|
1191
|
+
const {
|
|
1192
|
+
isVi,
|
|
1193
|
+
channelKey,
|
|
1194
|
+
channel,
|
|
1195
|
+
existingBots = [],
|
|
1196
|
+
existingBotCount = 1,
|
|
1197
|
+
existingGroupId = '',
|
|
1198
|
+
} = options;
|
|
1199
|
+
|
|
1200
|
+
let botCount = channelKey === 'telegram' ? existingBotCount : 1;
|
|
1201
|
+
let groupId = existingGroupId;
|
|
1202
|
+
const bots = [];
|
|
1203
|
+
|
|
1204
|
+
if (channelKey === 'telegram') {
|
|
1205
|
+
const botCountValue = await selectWithBack({
|
|
1206
|
+
message: isVi ? 'Bạn muốn cài bao nhiêu Telegram bot?' : 'How many Telegram bots do you want to deploy?',
|
|
1207
|
+
choices: [
|
|
1208
|
+
{ name: '1 bot (single)', value: '1' },
|
|
1209
|
+
{ name: '2 bots (Department Room)', value: '2' },
|
|
1210
|
+
{ name: '3 bots', value: '3' },
|
|
1211
|
+
{ name: '4 bots', value: '4' },
|
|
1212
|
+
{ name: '5 bots', value: '5' },
|
|
1213
|
+
],
|
|
1214
|
+
defaultValue: String(existingBotCount || 1),
|
|
1215
|
+
allowBack: true,
|
|
1216
|
+
isVi,
|
|
1217
|
+
});
|
|
1218
|
+
if (botCountValue === CLI_BACK) {
|
|
1219
|
+
return { back: true };
|
|
1220
|
+
}
|
|
1221
|
+
botCount = parseInt(botCountValue, 10);
|
|
1222
|
+
|
|
1223
|
+
if (botCount > 1) {
|
|
1224
|
+
while (true) {
|
|
1225
|
+
const groupOption = await selectWithBack({
|
|
1226
|
+
message: isVi ? 'Bạn có sẵn Telegram Group chưa?' : 'Do you already have a Telegram Group?',
|
|
1227
|
+
choices: [
|
|
1228
|
+
{ 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' },
|
|
1229
|
+
{ name: isVi ? '🔗 Đã có group — nhập Group ID ngay' : '🔗 Already have a group — enter Group ID now', value: 'existing' }
|
|
1230
|
+
],
|
|
1231
|
+
defaultValue: groupId ? 'existing' : 'create',
|
|
1232
|
+
allowBack: true,
|
|
1233
|
+
isVi,
|
|
1234
|
+
});
|
|
1235
|
+
if (groupOption === CLI_BACK) {
|
|
1236
|
+
return { back: true };
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
if (groupOption === 'existing') {
|
|
1240
|
+
console.log(chalk.dim(isVi
|
|
1241
|
+
? '\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'
|
|
1242
|
+
: '\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'));
|
|
1243
|
+
const nextGroupId = await inputWithBack({
|
|
1244
|
+
message: isVi ? 'Telegram Group ID (VD: -1001234567890):' : 'Telegram Group ID (e.g. -1001234567890):',
|
|
1245
|
+
defaultValue: groupId,
|
|
1246
|
+
allowBack: true,
|
|
1247
|
+
isVi,
|
|
1248
|
+
});
|
|
1249
|
+
if (nextGroupId === CLI_BACK) {
|
|
1250
|
+
continue;
|
|
1251
|
+
}
|
|
1252
|
+
groupId = nextGroupId;
|
|
1253
|
+
break;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
groupId = '';
|
|
1257
|
+
break;
|
|
1258
|
+
}
|
|
1259
|
+
} else {
|
|
1260
|
+
groupId = '';
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
for (let i = 0; i < botCount; i++) {
|
|
1264
|
+
console.log(chalk.bold(`\n${isVi ? `─── Bot ${i + 1} / ${botCount} ───` : `─── Bot ${i + 1} / ${botCount} ───`}`));
|
|
1265
|
+
const defaults = existingBots[i] || {};
|
|
1266
|
+
const fields = [
|
|
1267
|
+
{
|
|
1268
|
+
key: 'name',
|
|
1269
|
+
message: isVi ? `Tên Bot ${i + 1}:` : `Bot ${i + 1} name:`,
|
|
1270
|
+
defaultValue: defaults.name || `Bot ${i + 1}`,
|
|
1271
|
+
required: true,
|
|
1272
|
+
},
|
|
1273
|
+
{
|
|
1274
|
+
key: 'slashCmd',
|
|
1275
|
+
message: isVi ? `Slash command (VD: /bot${i + 1}):` : `Slash command (e.g. /bot${i + 1}):`,
|
|
1276
|
+
defaultValue: defaults.slashCmd || `/bot${i + 1}`,
|
|
1277
|
+
required: true,
|
|
1278
|
+
},
|
|
1279
|
+
{
|
|
1280
|
+
key: 'desc',
|
|
1281
|
+
message: isVi ? `Mô tả Bot ${i + 1} (VD: Trợ lý AI cá nhân):` : `Bot ${i + 1} description (e.g. Personal AI assistant):`,
|
|
1282
|
+
defaultValue: defaults.desc || (isVi ? 'Trợ lý AI cá nhân' : 'Personal AI assistant'),
|
|
1283
|
+
required: true,
|
|
1284
|
+
},
|
|
1285
|
+
{
|
|
1286
|
+
key: 'persona',
|
|
1287
|
+
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):`,
|
|
1288
|
+
defaultValue: defaults.persona || '',
|
|
1289
|
+
required: false,
|
|
1290
|
+
},
|
|
1291
|
+
{
|
|
1292
|
+
key: 'token',
|
|
1293
|
+
message: isVi ? 'Bot Token (từ @BotFather):' : 'Bot Token (from @BotFather):',
|
|
1294
|
+
defaultValue: defaults.token || '',
|
|
1295
|
+
required: true,
|
|
1296
|
+
},
|
|
1297
|
+
];
|
|
1298
|
+
|
|
1299
|
+
const draft = { ...defaults };
|
|
1300
|
+
let fieldIndex = 0;
|
|
1301
|
+
while (fieldIndex < fields.length) {
|
|
1302
|
+
const field = fields[fieldIndex];
|
|
1303
|
+
const value = await inputWithBack({
|
|
1304
|
+
message: field.message,
|
|
1305
|
+
defaultValue: draft[field.key] || field.defaultValue,
|
|
1306
|
+
required: field.required,
|
|
1307
|
+
allowBack: true,
|
|
1308
|
+
isVi,
|
|
1309
|
+
});
|
|
1310
|
+
if (value === CLI_BACK) {
|
|
1311
|
+
if (fieldIndex > 0) {
|
|
1312
|
+
fieldIndex--;
|
|
1313
|
+
continue;
|
|
1314
|
+
}
|
|
1315
|
+
return { back: true };
|
|
1316
|
+
}
|
|
1317
|
+
draft[field.key] = value;
|
|
1318
|
+
fieldIndex++;
|
|
1319
|
+
}
|
|
1320
|
+
bots.push(draft);
|
|
1321
|
+
}
|
|
1322
|
+
} else if (channelKey !== 'zalo-personal') {
|
|
1323
|
+
const defaults = existingBots[0] || {};
|
|
1324
|
+
const fields = [
|
|
1325
|
+
{
|
|
1326
|
+
key: 'name',
|
|
1327
|
+
message: isVi ? 'Tên Bot:' : 'Bot Name:',
|
|
1328
|
+
defaultValue: defaults.name || 'Chat Bot',
|
|
1329
|
+
required: true,
|
|
1330
|
+
},
|
|
1331
|
+
{
|
|
1332
|
+
key: 'desc',
|
|
1333
|
+
message: isVi ? 'Mô tả Bot:' : 'Bot Description:',
|
|
1334
|
+
defaultValue: defaults.desc || (isVi ? 'Trợ lý AI cá nhân' : 'Personal AI assistant'),
|
|
1335
|
+
required: true,
|
|
1336
|
+
},
|
|
1337
|
+
{
|
|
1338
|
+
key: 'persona',
|
|
1339
|
+
message: isVi ? 'Tính cách & quy tắc (VD: gọn gàng, thân thiện):' : 'Persona & rules (e.g. concise, friendly):',
|
|
1340
|
+
defaultValue: defaults.persona || '',
|
|
1341
|
+
required: false,
|
|
1342
|
+
},
|
|
1343
|
+
{
|
|
1344
|
+
key: 'token',
|
|
1345
|
+
message: isVi ? `Nhập ${channel.name} Token:` : `Enter ${channel.name} Token:`,
|
|
1346
|
+
defaultValue: defaults.token || '',
|
|
1347
|
+
required: true,
|
|
1348
|
+
},
|
|
1349
|
+
];
|
|
1350
|
+
const draft = { ...defaults, slashCmd: '' };
|
|
1351
|
+
let fieldIndex = 0;
|
|
1352
|
+
while (fieldIndex < fields.length) {
|
|
1353
|
+
const field = fields[fieldIndex];
|
|
1354
|
+
const value = await inputWithBack({
|
|
1355
|
+
message: field.message,
|
|
1356
|
+
defaultValue: draft[field.key] || field.defaultValue,
|
|
1357
|
+
required: field.required,
|
|
1358
|
+
allowBack: true,
|
|
1359
|
+
isVi,
|
|
1360
|
+
});
|
|
1361
|
+
if (value === CLI_BACK) {
|
|
1362
|
+
if (fieldIndex > 0) {
|
|
1363
|
+
fieldIndex--;
|
|
1364
|
+
continue;
|
|
1365
|
+
}
|
|
1366
|
+
return { back: true };
|
|
1367
|
+
}
|
|
1368
|
+
draft[field.key] = value;
|
|
1369
|
+
fieldIndex++;
|
|
1370
|
+
}
|
|
1371
|
+
bots.push(draft);
|
|
1372
|
+
} else {
|
|
1373
|
+
bots.push({ name: 'Bot', slashCmd: '', desc: '', persona: '', token: '' });
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
return {
|
|
1377
|
+
back: false,
|
|
1378
|
+
botCount,
|
|
1379
|
+
groupId,
|
|
1380
|
+
bots,
|
|
1381
|
+
botToken: bots[0]?.token || '',
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
async function collectProviderStep({
|
|
1386
|
+
isVi,
|
|
1387
|
+
existingProviderKey = '',
|
|
1388
|
+
existingProviderKeyVal = '',
|
|
1389
|
+
existingOllamaModel = 'gemma4:e2b',
|
|
1390
|
+
}) {
|
|
1391
|
+
const providerKey = await selectWithBack({
|
|
1392
|
+
message: isVi ? 'Chọn AI Provider:' : 'Select AI Provider:',
|
|
1393
|
+
choices: Object.entries(PROVIDERS).map(([k, v]) => ({ name: `${v.icon} ${v.name}`, value: k })),
|
|
1394
|
+
defaultValue: existingProviderKey || undefined,
|
|
1395
|
+
allowBack: true,
|
|
1396
|
+
isVi,
|
|
1397
|
+
});
|
|
1398
|
+
if (providerKey === CLI_BACK) {
|
|
1399
|
+
return { back: true };
|
|
1400
|
+
}
|
|
1401
|
+
const provider = PROVIDERS[providerKey];
|
|
1402
|
+
|
|
1403
|
+
let providerKeyVal = existingProviderKey === providerKey ? existingProviderKeyVal : '';
|
|
1404
|
+
if (!provider.isProxy && !provider.isLocal) {
|
|
1405
|
+
const keyValue = await inputWithBack({
|
|
1406
|
+
message: isVi ? `Nhập ${provider.envKey}:` : `Enter ${provider.envKey}:`,
|
|
1407
|
+
defaultValue: providerKeyVal,
|
|
1408
|
+
required: true,
|
|
1409
|
+
allowBack: true,
|
|
1410
|
+
isVi,
|
|
1411
|
+
});
|
|
1412
|
+
if (keyValue === CLI_BACK) {
|
|
1413
|
+
return { back: true };
|
|
1414
|
+
}
|
|
1415
|
+
providerKeyVal = keyValue;
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
let selectedOllamaModel = existingProviderKey === 'ollama' ? existingOllamaModel : 'gemma4:e2b';
|
|
1419
|
+
if (providerKey === 'ollama') {
|
|
1420
|
+
console.log(chalk.yellow(isVi
|
|
1421
|
+
? '\n💡 Gemma 4 (02/04/2026) — chọn kích thước phù hợp với RAM máy bạn:'
|
|
1422
|
+
: '\n💡 Gemma 4 (April 2, 2026) — pick a size that fits your RAM:'));
|
|
1423
|
+
const modelValue = await selectWithBack({
|
|
1424
|
+
message: isVi ? 'Chọn model Ollama:' : 'Select Ollama model:',
|
|
1425
|
+
choices: [
|
|
1426
|
+
{
|
|
1427
|
+
name: isVi
|
|
1428
|
+
? '🟢 gemma4:e2b — Nhẹ nhất (~4-6 GB RAM) — Laptop / test nhanh ★ Khuyên dùng'
|
|
1429
|
+
: '🟢 gemma4:e2b — Lightest (~4-6 GB RAM) — Laptop / fastest test ★ Recommended',
|
|
1430
|
+
value: 'gemma4:e2b'
|
|
1431
|
+
},
|
|
1432
|
+
{
|
|
1433
|
+
name: isVi
|
|
1434
|
+
? '🟡 gemma4:e4b — Cân bằng (~8-10 GB RAM) — Dùng hằng ngày'
|
|
1435
|
+
: '🟡 gemma4:e4b — Balanced (~8-10 GB RAM) — Daily use',
|
|
1436
|
+
value: 'gemma4:e4b'
|
|
1437
|
+
},
|
|
1438
|
+
{
|
|
1439
|
+
name: isVi
|
|
1440
|
+
? '🟠 gemma4:26b — Mạnh (~18-24 GB RAM/VRAM) — Máy mạnh'
|
|
1441
|
+
: '🟠 gemma4:26b — Powerful (~18-24 GB RAM/VRAM) — High-end machine',
|
|
1442
|
+
value: 'gemma4:26b'
|
|
1443
|
+
},
|
|
1444
|
+
{
|
|
1445
|
+
name: isVi
|
|
1446
|
+
? '🔴 gemma4:31b — Mạnh nhất (~24+ GB RAM/VRAM) — GPU workstation'
|
|
1447
|
+
: '🔴 gemma4:31b — Most powerful (~24+ GB RAM/VRAM) — GPU workstation',
|
|
1448
|
+
value: 'gemma4:31b'
|
|
1449
|
+
},
|
|
1450
|
+
],
|
|
1451
|
+
defaultValue: selectedOllamaModel,
|
|
1452
|
+
allowBack: true,
|
|
1453
|
+
isVi,
|
|
1454
|
+
});
|
|
1455
|
+
if (modelValue === CLI_BACK) {
|
|
1456
|
+
return { back: true };
|
|
1457
|
+
}
|
|
1458
|
+
selectedOllamaModel = modelValue;
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
return {
|
|
1462
|
+
back: false,
|
|
1463
|
+
providerKey,
|
|
1464
|
+
provider,
|
|
1465
|
+
providerKeyVal,
|
|
1466
|
+
selectedOllamaModel,
|
|
1467
|
+
};
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
async function collectSkillsStep({
|
|
1471
|
+
isVi,
|
|
1472
|
+
providerKey,
|
|
1473
|
+
existingSelectedSkills = [],
|
|
1474
|
+
existingBrowserMode = 'server',
|
|
1475
|
+
existingTtsOpenaiKey = '',
|
|
1476
|
+
existingTtsElevenKey = '',
|
|
1477
|
+
existingSmtpHost = 'smtp.gmail.com',
|
|
1478
|
+
existingSmtpPort = '587',
|
|
1479
|
+
existingSmtpUser = '',
|
|
1480
|
+
existingSmtpPass = '',
|
|
1481
|
+
}) {
|
|
1482
|
+
const selectedSkills = await checkboxWithBack({
|
|
1483
|
+
message: isVi ? 'Bật tính năng bổ sung (Space để chọn):' : 'Enable extra skills (Space to select):',
|
|
1484
|
+
choices: getCliSkillChoices({ providerKey, isVi }).map((choice) => ({
|
|
1485
|
+
...choice,
|
|
1486
|
+
checked: existingSelectedSkills.includes(choice.value),
|
|
1487
|
+
})),
|
|
1488
|
+
allowBack: true,
|
|
1489
|
+
isVi,
|
|
1490
|
+
});
|
|
1491
|
+
if (selectedSkills === CLI_BACK) {
|
|
1492
|
+
return { back: true };
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
let browserMode = existingBrowserMode;
|
|
1496
|
+
if (selectedSkills.includes('browser')) {
|
|
1497
|
+
const isLinux = process.platform === 'linux';
|
|
1498
|
+
const browserValue = await selectWithBack({
|
|
1499
|
+
message: isVi ? 'Chế độ Browser Automation:' : 'Browser Automation mode:',
|
|
1500
|
+
choices: [
|
|
1501
|
+
{
|
|
1502
|
+
name: isVi
|
|
1503
|
+
? '🖥️ Dùng Chrome trên máy tính (Windows/Mac — Bypass Cloudflare tốt hơn)'
|
|
1504
|
+
: '🖥️ Use Host Chrome (Windows/Mac — Better Cloudflare bypass)',
|
|
1505
|
+
value: 'desktop'
|
|
1506
|
+
},
|
|
1507
|
+
{
|
|
1508
|
+
name: isVi
|
|
1509
|
+
? '🐳 Headless Chromium trong Docker (Ubuntu Server / VPS — không cần GUI)'
|
|
1510
|
+
: '🐳 Headless Chromium inside Docker (Ubuntu Server / VPS — No GUI)',
|
|
1511
|
+
value: 'server'
|
|
1512
|
+
}
|
|
1513
|
+
],
|
|
1514
|
+
defaultValue: browserMode || (isLinux ? 'server' : 'desktop'),
|
|
1515
|
+
allowBack: true,
|
|
1516
|
+
isVi,
|
|
1517
|
+
});
|
|
1518
|
+
if (browserValue === CLI_BACK) {
|
|
1519
|
+
return { back: true };
|
|
1520
|
+
}
|
|
1521
|
+
browserMode = browserValue;
|
|
1522
|
+
} else {
|
|
1523
|
+
browserMode = 'server';
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
let ttsOpenaiKey = existingTtsOpenaiKey;
|
|
1527
|
+
let ttsElevenKey = existingTtsElevenKey;
|
|
1528
|
+
if (selectedSkills.includes('tts')) {
|
|
1529
|
+
const openaiKey = await inputWithBack({
|
|
1530
|
+
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):',
|
|
1531
|
+
defaultValue: ttsOpenaiKey,
|
|
1532
|
+
allowBack: true,
|
|
1533
|
+
isVi,
|
|
1534
|
+
});
|
|
1535
|
+
if (openaiKey === CLI_BACK) {
|
|
1536
|
+
return { back: true };
|
|
1537
|
+
}
|
|
1538
|
+
ttsOpenaiKey = openaiKey;
|
|
1539
|
+
|
|
1540
|
+
const elevenKey = await inputWithBack({
|
|
1541
|
+
message: isVi ? 'Nhập ELEVENLABS_API_KEY (hoặc bỏ trống):' : 'Enter ELEVENLABS_API_KEY (or leave empty):',
|
|
1542
|
+
defaultValue: ttsElevenKey,
|
|
1543
|
+
allowBack: true,
|
|
1544
|
+
isVi,
|
|
1545
|
+
});
|
|
1546
|
+
if (elevenKey === CLI_BACK) {
|
|
1547
|
+
return { back: true };
|
|
1548
|
+
}
|
|
1549
|
+
ttsElevenKey = elevenKey;
|
|
1550
|
+
} else {
|
|
1551
|
+
ttsOpenaiKey = '';
|
|
1552
|
+
ttsElevenKey = '';
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
let smtpHost = existingSmtpHost;
|
|
1556
|
+
let smtpPort = existingSmtpPort;
|
|
1557
|
+
let smtpUser = existingSmtpUser;
|
|
1558
|
+
let smtpPass = existingSmtpPass;
|
|
1559
|
+
if (selectedSkills.includes('email')) {
|
|
1560
|
+
const smtpHostValue = await inputWithBack({
|
|
1561
|
+
message: isVi ? 'SMTP Host (VD: smtp.gmail.com):' : 'SMTP Host (e.g. smtp.gmail.com):',
|
|
1562
|
+
defaultValue: smtpHost,
|
|
1563
|
+
required: true,
|
|
1564
|
+
allowBack: true,
|
|
1565
|
+
isVi,
|
|
1566
|
+
});
|
|
1567
|
+
if (smtpHostValue === CLI_BACK) {
|
|
1568
|
+
return { back: true };
|
|
1569
|
+
}
|
|
1570
|
+
smtpHost = smtpHostValue;
|
|
1571
|
+
|
|
1572
|
+
const smtpPortValue = await inputWithBack({
|
|
1573
|
+
message: 'SMTP Port:',
|
|
1574
|
+
defaultValue: smtpPort,
|
|
1575
|
+
required: true,
|
|
1576
|
+
allowBack: true,
|
|
1577
|
+
isVi,
|
|
1578
|
+
});
|
|
1579
|
+
if (smtpPortValue === CLI_BACK) {
|
|
1580
|
+
return { back: true };
|
|
1581
|
+
}
|
|
1582
|
+
smtpPort = smtpPortValue;
|
|
1583
|
+
|
|
1584
|
+
const smtpUserValue = await inputWithBack({
|
|
1585
|
+
message: isVi ? 'SMTP Email:' : 'SMTP Email:',
|
|
1586
|
+
defaultValue: smtpUser,
|
|
1587
|
+
required: true,
|
|
1588
|
+
allowBack: true,
|
|
1589
|
+
isVi,
|
|
1590
|
+
});
|
|
1591
|
+
if (smtpUserValue === CLI_BACK) {
|
|
1592
|
+
return { back: true };
|
|
1593
|
+
}
|
|
1594
|
+
smtpUser = smtpUserValue;
|
|
1595
|
+
|
|
1596
|
+
const smtpPassValue = await inputWithBack({
|
|
1597
|
+
message: isVi ? 'SMTP App Password:' : 'SMTP App Password:',
|
|
1598
|
+
defaultValue: smtpPass,
|
|
1599
|
+
required: true,
|
|
1600
|
+
allowBack: true,
|
|
1601
|
+
isVi,
|
|
1602
|
+
});
|
|
1603
|
+
if (smtpPassValue === CLI_BACK) {
|
|
1604
|
+
return { back: true };
|
|
1605
|
+
}
|
|
1606
|
+
smtpPass = smtpPassValue;
|
|
1607
|
+
} else {
|
|
1608
|
+
smtpHost = 'smtp.gmail.com';
|
|
1609
|
+
smtpPort = '587';
|
|
1610
|
+
smtpUser = '';
|
|
1611
|
+
smtpPass = '';
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
return {
|
|
1615
|
+
back: false,
|
|
1616
|
+
selectedSkills,
|
|
1617
|
+
browserMode,
|
|
1618
|
+
ttsOpenaiKey,
|
|
1619
|
+
ttsElevenKey,
|
|
1620
|
+
smtpHost,
|
|
1621
|
+
smtpPort,
|
|
1622
|
+
smtpUser,
|
|
1623
|
+
smtpPass,
|
|
1624
|
+
};
|
|
1625
|
+
}
|
|
956
1626
|
|
|
957
1627
|
|
|
958
1628
|
// ─── Shared workspace file writer ─────────────────────────────────────────────
|
|
@@ -971,11 +1641,11 @@ async function writeWorkspaceFiles({
|
|
|
971
1641
|
ownAliases = [],
|
|
972
1642
|
otherAgents = [], // [{ name, agentId }]
|
|
973
1643
|
teamRoster = [],
|
|
974
|
-
userInfo = '',
|
|
975
|
-
agentWorkspaceDir = 'workspace',
|
|
976
|
-
isRelayBot = false,
|
|
977
|
-
replyToDirectMessages = true,
|
|
978
|
-
}) {
|
|
1644
|
+
userInfo = '',
|
|
1645
|
+
agentWorkspaceDir = 'workspace',
|
|
1646
|
+
isRelayBot = false,
|
|
1647
|
+
replyToDirectMessages = true,
|
|
1648
|
+
}) {
|
|
979
1649
|
const skillListStr = SKILLS
|
|
980
1650
|
.filter((s) => selectedSkills.includes(s.value))
|
|
981
1651
|
.map((s) => {
|
|
@@ -986,46 +1656,46 @@ async function writeWorkspaceFiles({
|
|
|
986
1656
|
})
|
|
987
1657
|
.join('\n') || (isVi ? '- _(Chưa có skill nào)_' : '- _(No skills installed)_');
|
|
988
1658
|
|
|
989
|
-
const workspacePath = `.openclaw/${agentWorkspaceDir}/`;
|
|
990
|
-
const teamRosterFormatted = teamRoster
|
|
991
|
-
.map((peer, idx) => {
|
|
992
|
-
const agentId = peer.agentId || String(peer.name || `Bot ${idx + 1}`).toLowerCase().replace(/[^a-z0-9]+/g, '-');
|
|
993
|
-
const desc = peer.desc || (isVi ? 'Tro ly AI ca nhan' : 'Personal AI assistant');
|
|
994
|
-
const accountId = peer.accountId ? `, accountId: ${peer.accountId}` : '';
|
|
995
|
-
const slashCmd = peer.slashCmd ? `, slash: ${peer.slashCmd}` : '';
|
|
996
|
-
return `- \`${agentId}\`: ${peer.name || `Bot ${idx + 1}`} - ${desc}${accountId}${slashCmd}`;
|
|
997
|
-
})
|
|
998
|
-
.join('\n');
|
|
999
|
-
|
|
1000
|
-
const files = buildWorkspaceFileMap({
|
|
1001
|
-
isVi,
|
|
1002
|
-
variant: isRelayBot ? 'relay' : 'single',
|
|
1003
|
-
botName,
|
|
1004
|
-
botDesc,
|
|
1005
|
-
ownAliases,
|
|
1006
|
-
otherAgents,
|
|
1007
|
-
replyToDirectMessages,
|
|
1008
|
-
skillListStr,
|
|
1009
|
-
workspacePath,
|
|
1010
|
-
agentWorkspaceDir,
|
|
1011
|
-
persona,
|
|
1012
|
-
userInfo,
|
|
1013
|
-
hasBrowser: isDesktop || isServer,
|
|
1014
|
-
soulVariant: isRelayBot ? 'cli-simple' : 'cli-rich',
|
|
1015
|
-
userVariant: isRelayBot ? 'cli-multi' : 'cli-single',
|
|
1016
|
-
memoryVariant: isRelayBot ? 'cli-multi' : 'cli-single',
|
|
1017
|
-
browserDocVariant: isServer ? 'cli-server' : 'cli-desktop',
|
|
1018
|
-
browserToolVariant: 'cli',
|
|
1019
|
-
includeBrowserTool: isDesktop,
|
|
1020
|
-
teamRosterFormatted,
|
|
1021
|
-
hasScheduler: selectedSkills.includes('scheduler'),
|
|
1022
|
-
});
|
|
1023
|
-
|
|
1024
|
-
await fs.ensureDir(workspaceDir);
|
|
1025
|
-
for (const [name, content] of Object.entries(files)) {
|
|
1026
|
-
await fs.writeFile(path.join(workspaceDir, name), content, 'utf8');
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1659
|
+
const workspacePath = `.openclaw/${agentWorkspaceDir}/`;
|
|
1660
|
+
const teamRosterFormatted = teamRoster
|
|
1661
|
+
.map((peer, idx) => {
|
|
1662
|
+
const agentId = peer.agentId || String(peer.name || `Bot ${idx + 1}`).toLowerCase().replace(/[^a-z0-9]+/g, '-');
|
|
1663
|
+
const desc = peer.desc || (isVi ? 'Tro ly AI ca nhan' : 'Personal AI assistant');
|
|
1664
|
+
const accountId = peer.accountId ? `, accountId: ${peer.accountId}` : '';
|
|
1665
|
+
const slashCmd = peer.slashCmd ? `, slash: ${peer.slashCmd}` : '';
|
|
1666
|
+
return `- \`${agentId}\`: ${peer.name || `Bot ${idx + 1}`} - ${desc}${accountId}${slashCmd}`;
|
|
1667
|
+
})
|
|
1668
|
+
.join('\n');
|
|
1669
|
+
|
|
1670
|
+
const files = buildWorkspaceFileMap({
|
|
1671
|
+
isVi,
|
|
1672
|
+
variant: isRelayBot ? 'relay' : 'single',
|
|
1673
|
+
botName,
|
|
1674
|
+
botDesc,
|
|
1675
|
+
ownAliases,
|
|
1676
|
+
otherAgents,
|
|
1677
|
+
replyToDirectMessages,
|
|
1678
|
+
skillListStr,
|
|
1679
|
+
workspacePath,
|
|
1680
|
+
agentWorkspaceDir,
|
|
1681
|
+
persona,
|
|
1682
|
+
userInfo,
|
|
1683
|
+
hasBrowser: isDesktop || isServer,
|
|
1684
|
+
soulVariant: isRelayBot ? 'cli-simple' : 'cli-rich',
|
|
1685
|
+
userVariant: isRelayBot ? 'cli-multi' : 'cli-single',
|
|
1686
|
+
memoryVariant: isRelayBot ? 'cli-multi' : 'cli-single',
|
|
1687
|
+
browserDocVariant: isServer ? 'cli-server' : 'cli-desktop',
|
|
1688
|
+
browserToolVariant: 'cli',
|
|
1689
|
+
includeBrowserTool: isDesktop,
|
|
1690
|
+
teamRosterFormatted,
|
|
1691
|
+
hasScheduler: selectedSkills.includes('scheduler'),
|
|
1692
|
+
});
|
|
1693
|
+
|
|
1694
|
+
await fs.ensureDir(workspaceDir);
|
|
1695
|
+
for (const [name, content] of Object.entries(files)) {
|
|
1696
|
+
await fs.writeFile(path.join(workspaceDir, name), content, 'utf8');
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1029
1699
|
|
|
1030
1700
|
|
|
1031
1701
|
async function main() {
|
|
@@ -1038,316 +1708,218 @@ async function main() {
|
|
|
1038
1708
|
console.log(chalk.red('\n=================================='));
|
|
1039
1709
|
console.log(chalk.redBright(LOGO));
|
|
1040
1710
|
console.log(chalk.greenBright(' OpenClaw Auto Setup CLI '));
|
|
1041
|
-
console.log(chalk.red('==================================\n'));
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
: '
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
if (
|
|
1170
|
-
console.log(chalk.
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
console.log(chalk.yellow(isVi
|
|
1254
|
-
? '\n💡 Gemma 4 (02/04/2026) — chọn kích thước phù hợp với RAM máy bạn:'
|
|
1255
|
-
: '\n💡 Gemma 4 (April 2, 2026) — pick a size that fits your RAM:'));
|
|
1256
|
-
selectedOllamaModel = await select({
|
|
1257
|
-
message: isVi ? 'Chọn model Ollama:' : 'Select Ollama model:',
|
|
1258
|
-
choices: [
|
|
1259
|
-
{
|
|
1260
|
-
name: isVi
|
|
1261
|
-
? '🟢 gemma4:e2b — Nhẹ nhất (~4-6 GB RAM) — Laptop / test nhanh ★ Khuyên dùng'
|
|
1262
|
-
: '🟢 gemma4:e2b — Lightest (~4-6 GB RAM) — Laptop / fastest test ★ Recommended',
|
|
1263
|
-
value: 'gemma4:e2b'
|
|
1264
|
-
},
|
|
1265
|
-
{
|
|
1266
|
-
name: isVi
|
|
1267
|
-
? '🟡 gemma4:e4b — Cân bằng (~8-10 GB RAM) — Dùng hằng ngày'
|
|
1268
|
-
: '🟡 gemma4:e4b — Balanced (~8-10 GB RAM) — Daily use',
|
|
1269
|
-
value: 'gemma4:e4b'
|
|
1270
|
-
},
|
|
1271
|
-
{
|
|
1272
|
-
name: isVi
|
|
1273
|
-
? '🟠 gemma4:26b — Mạnh (~18-24 GB RAM/VRAM) — Máy mạnh'
|
|
1274
|
-
: '🟠 gemma4:26b — Powerful (~18-24 GB RAM/VRAM) — High-end machine',
|
|
1275
|
-
value: 'gemma4:26b'
|
|
1276
|
-
},
|
|
1277
|
-
{
|
|
1278
|
-
name: isVi
|
|
1279
|
-
? '🔴 gemma4:31b — Mạnh nhất (~24+ GB RAM/VRAM) — GPU workstation'
|
|
1280
|
-
: '🔴 gemma4:31b — Most powerful (~24+ GB RAM/VRAM) — GPU workstation',
|
|
1281
|
-
value: 'gemma4:31b'
|
|
1282
|
-
},
|
|
1283
|
-
],
|
|
1284
|
-
default: 'gemma4:e2b'
|
|
1285
|
-
});
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
// 4. Skills
|
|
1289
|
-
const selectedSkills = await checkbox({
|
|
1290
|
-
message: isVi ? 'Bật tính năng bổ sung (Space để chọn):' : 'Enable extra skills (Space to select):',
|
|
1291
|
-
choices: getCliSkillChoices({ providerKey, isVi })
|
|
1292
|
-
});
|
|
1293
|
-
|
|
1294
|
-
let tavilyKey = '';
|
|
1295
|
-
// (web-search removed — native search built-in)
|
|
1296
|
-
|
|
1297
|
-
// Browser mode: Desktop (host Chrome via CDP) vs Server (headless Chromium inside Docker)
|
|
1298
|
-
let browserMode = 'server';
|
|
1299
|
-
if (selectedSkills.includes('browser')) {
|
|
1300
|
-
const isLinux = process.platform === 'linux';
|
|
1301
|
-
browserMode = await select({
|
|
1302
|
-
message: isVi ? 'Chế độ Browser Automation:' : 'Browser Automation mode:',
|
|
1303
|
-
choices: [
|
|
1304
|
-
{
|
|
1305
|
-
name: isVi
|
|
1306
|
-
? '🖥️ Dùng Chrome trên máy tính (Windows/Mac — Bypass Cloudflare tốt hơn)'
|
|
1307
|
-
: '🖥️ Use Host Chrome (Windows/Mac — Better Cloudflare bypass)',
|
|
1308
|
-
value: 'desktop'
|
|
1309
|
-
},
|
|
1310
|
-
{
|
|
1311
|
-
name: isVi
|
|
1312
|
-
? '🐳 Headless Chromium trong Docker (Ubuntu Server / VPS — không cần GUI)'
|
|
1313
|
-
: '🐳 Headless Chromium inside Docker (Ubuntu Server / VPS — No GUI)',
|
|
1314
|
-
value: 'server'
|
|
1315
|
-
}
|
|
1316
|
-
],
|
|
1317
|
-
default: isLinux ? 'server' : 'desktop'
|
|
1318
|
-
});
|
|
1319
|
-
}
|
|
1320
|
-
const hasBrowserDesktop = selectedSkills.includes('browser') && browserMode === 'desktop';
|
|
1321
|
-
const hasBrowserServer = selectedSkills.includes('browser') && browserMode === 'server';
|
|
1322
|
-
|
|
1323
|
-
let ttsOpenaiKey = '';
|
|
1324
|
-
let ttsElevenKey = '';
|
|
1325
|
-
if (selectedSkills.includes('tts')) {
|
|
1326
|
-
ttsOpenaiKey = await input({ 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):' });
|
|
1327
|
-
ttsElevenKey = await input({ message: isVi ? 'Nhập ELEVENLABS_API_KEY (hoặc bỏ trống):' : 'Enter ELEVENLABS_API_KEY (or leave empty):', default: '' });
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
let smtpHost = 'smtp.gmail.com', smtpPort = '587', smtpUser = '', smtpPass = '';
|
|
1331
|
-
if (selectedSkills.includes('email')) {
|
|
1332
|
-
smtpHost = await input({ message: isVi ? 'SMTP Host (VD: smtp.gmail.com):' : 'SMTP Host (e.g. smtp.gmail.com):', default: 'smtp.gmail.com' });
|
|
1333
|
-
smtpPort = await input({ message: 'SMTP Port:', default: '587' });
|
|
1334
|
-
smtpUser = await input({ message: isVi ? 'SMTP Email:' : 'SMTP Email:' });
|
|
1335
|
-
smtpPass = await input({ message: isVi ? 'SMTP App Password:' : 'SMTP App Password:' });
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
// 6. Project Dir
|
|
1342
|
-
let defaultDir = process.cwd();
|
|
1343
|
-
if (!defaultDir.endsWith('openclaw-setup') && !defaultDir.endsWith('openclaw')) {
|
|
1344
|
-
defaultDir = path.join(defaultDir, 'openclaw-setup');
|
|
1345
|
-
}
|
|
1346
|
-
const projectDir = await input({
|
|
1347
|
-
message: isVi ? 'Thư mục cài đặt project:' : 'Project install directory:',
|
|
1348
|
-
default: defaultDir
|
|
1349
|
-
});
|
|
1350
|
-
|
|
1711
|
+
console.log(chalk.red('==================================\n'));
|
|
1712
|
+
|
|
1713
|
+
let lang = 'vi';
|
|
1714
|
+
let isVi = true;
|
|
1715
|
+
const detectedPlatform = process.platform;
|
|
1716
|
+
const detectedOS = detectedPlatform === 'win32' ? 'windows'
|
|
1717
|
+
: detectedPlatform === 'darwin' ? 'macos'
|
|
1718
|
+
: 'linux';
|
|
1719
|
+
let osChoice = detectedOS === 'linux' ? 'vps' : detectedOS;
|
|
1720
|
+
let deployMode = 'docker';
|
|
1721
|
+
let channelKey = 'telegram';
|
|
1722
|
+
let channel = CHANNELS[channelKey];
|
|
1723
|
+
let botToken = '';
|
|
1724
|
+
let botCount = 1;
|
|
1725
|
+
let bots = [];
|
|
1726
|
+
let groupId = '';
|
|
1727
|
+
let userInfo = '';
|
|
1728
|
+
let providerKey = '9router';
|
|
1729
|
+
let provider = PROVIDERS[providerKey];
|
|
1730
|
+
let providerKeyVal = '';
|
|
1731
|
+
let selectedOllamaModel = 'gemma4:e2b';
|
|
1732
|
+
let selectedSkills = [];
|
|
1733
|
+
let tavilyKey = '';
|
|
1734
|
+
let browserMode = 'server';
|
|
1735
|
+
let ttsOpenaiKey = '';
|
|
1736
|
+
let ttsElevenKey = '';
|
|
1737
|
+
let smtpHost = 'smtp.gmail.com', smtpPort = '587', smtpUser = '', smtpPass = '';
|
|
1738
|
+
let defaultDir = process.cwd();
|
|
1739
|
+
if (!defaultDir.endsWith('openclaw-setup') && !defaultDir.endsWith('openclaw')) {
|
|
1740
|
+
defaultDir = path.join(defaultDir, 'openclaw-setup');
|
|
1741
|
+
}
|
|
1742
|
+
let projectDir = defaultDir;
|
|
1743
|
+
|
|
1744
|
+
let setupStep = 'language';
|
|
1745
|
+
while (true) {
|
|
1746
|
+
if (setupStep === 'language') {
|
|
1747
|
+
lang = await select({
|
|
1748
|
+
message: 'Select language / Chọn ngôn ngữ:',
|
|
1749
|
+
choices: [
|
|
1750
|
+
{ name: 'Tiếng Việt', value: 'vi' },
|
|
1751
|
+
{ name: 'English', value: 'en' }
|
|
1752
|
+
],
|
|
1753
|
+
default: lang
|
|
1754
|
+
});
|
|
1755
|
+
isVi = lang === 'vi';
|
|
1756
|
+
setupStep = 'os';
|
|
1757
|
+
continue;
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
if (setupStep === 'os') {
|
|
1761
|
+
const nextOsChoice = await selectWithBack({
|
|
1762
|
+
message: isVi ? 'Bạn đang chạy trên hệ điều hành nào?' : 'What OS are you running on?',
|
|
1763
|
+
choices: [
|
|
1764
|
+
{ name: isVi ? '🪟 Windows' : '🪟 Windows', value: 'windows' },
|
|
1765
|
+
{ name: isVi ? '🍎 macOS' : '🍎 macOS', value: 'macos' },
|
|
1766
|
+
{ name: isVi ? '🐧 Ubuntu Desktop' : '🐧 Ubuntu Desktop', value: 'ubuntu' },
|
|
1767
|
+
{ name: isVi ? '🖥️ VPS / Ubuntu Server' : '🖥️ VPS / Ubuntu Server', value: 'vps' },
|
|
1768
|
+
],
|
|
1769
|
+
defaultValue: osChoice,
|
|
1770
|
+
allowBack: true,
|
|
1771
|
+
isVi,
|
|
1772
|
+
});
|
|
1773
|
+
if (nextOsChoice === CLI_BACK) {
|
|
1774
|
+
setupStep = 'language';
|
|
1775
|
+
continue;
|
|
1776
|
+
}
|
|
1777
|
+
osChoice = nextOsChoice;
|
|
1778
|
+
setupStep = 'deploy';
|
|
1779
|
+
continue;
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
if (setupStep === 'deploy') {
|
|
1783
|
+
const deployModeDefault = (osChoice === 'ubuntu' || osChoice === 'vps') ? 'native' : 'docker';
|
|
1784
|
+
const nextDeployMode = await selectWithBack({
|
|
1785
|
+
message: isVi ? 'Chọn cách chạy bot:' : 'How do you want to run the bot?',
|
|
1786
|
+
choices: [
|
|
1787
|
+
{ 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' },
|
|
1788
|
+
{ 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' }
|
|
1789
|
+
],
|
|
1790
|
+
defaultValue: deployMode || deployModeDefault,
|
|
1791
|
+
allowBack: true,
|
|
1792
|
+
isVi,
|
|
1793
|
+
});
|
|
1794
|
+
if (nextDeployMode === CLI_BACK) {
|
|
1795
|
+
setupStep = 'os';
|
|
1796
|
+
continue;
|
|
1797
|
+
}
|
|
1798
|
+
deployMode = nextDeployMode;
|
|
1799
|
+
if (deployMode === 'docker' && !isDockerInstalled()) {
|
|
1800
|
+
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...'));
|
|
1801
|
+
try {
|
|
1802
|
+
const platform = process.platform;
|
|
1803
|
+
if (platform === 'win32') {
|
|
1804
|
+
execSync('winget install -e --id Docker.DockerDesktop --accept-source-agreements --accept-package-agreements', { stdio: 'inherit' });
|
|
1805
|
+
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.'));
|
|
1806
|
+
process.exit(0);
|
|
1807
|
+
} else if (platform === 'darwin') {
|
|
1808
|
+
execSync('brew install --cask docker', { stdio: 'inherit' });
|
|
1809
|
+
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.'));
|
|
1810
|
+
process.exit(0);
|
|
1811
|
+
} else {
|
|
1812
|
+
execSync('curl -fsSL https://get.docker.com | sh', { stdio: 'inherit', shell: true });
|
|
1813
|
+
try { execSync('apt-get install -y docker-compose-plugin', { stdio: 'ignore', shell: true }); } catch {}
|
|
1814
|
+
console.log(chalk.green(isVi ? '✅ Docker Engine + Compose plugin đã cài xong.' : '✅ Docker Engine + Compose plugin installed.'));
|
|
1815
|
+
}
|
|
1816
|
+
} catch {
|
|
1817
|
+
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/'));
|
|
1818
|
+
process.exit(1);
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
setupStep = 'channel';
|
|
1822
|
+
continue;
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
if (setupStep === 'channel') {
|
|
1826
|
+
const nextChannelKey = await selectWithBack({
|
|
1827
|
+
message: isVi ? 'Chọn nền tảng bot:' : 'Select bot platform:',
|
|
1828
|
+
choices: Object.entries(CHANNELS).map(([k, v]) => ({ name: v.icon + ' ' + v.name, value: k })),
|
|
1829
|
+
defaultValue: channelKey,
|
|
1830
|
+
allowBack: true,
|
|
1831
|
+
isVi,
|
|
1832
|
+
});
|
|
1833
|
+
if (nextChannelKey === CLI_BACK) {
|
|
1834
|
+
setupStep = 'deploy';
|
|
1835
|
+
continue;
|
|
1836
|
+
}
|
|
1837
|
+
channelKey = nextChannelKey;
|
|
1838
|
+
channel = CHANNELS[channelKey];
|
|
1839
|
+
if (channelKey === 'zalo-bot') {
|
|
1840
|
+
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.')));
|
|
1841
|
+
}
|
|
1842
|
+
setupStep = 'botSetup';
|
|
1843
|
+
continue;
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
if (setupStep === 'botSetup') {
|
|
1847
|
+
const botSetup = await collectBotSetupStepWithGroupBack({ isVi, channelKey, channel, existingBots: bots, existingBotCount: botCount, existingGroupId: groupId });
|
|
1848
|
+
if (botSetup.back) {
|
|
1849
|
+
setupStep = 'channel';
|
|
1850
|
+
continue;
|
|
1851
|
+
}
|
|
1852
|
+
botCount = botSetup.botCount;
|
|
1853
|
+
groupId = botSetup.groupId;
|
|
1854
|
+
bots = botSetup.bots;
|
|
1855
|
+
botToken = botSetup.botToken;
|
|
1856
|
+
setupStep = 'userInfo';
|
|
1857
|
+
continue;
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
if (setupStep === 'userInfo') {
|
|
1861
|
+
console.log(chalk.bold('\n' + (isVi ? '👤 Thông tin của bạn 👤' : '👤 About You 👤')));
|
|
1862
|
+
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 });
|
|
1863
|
+
if (nextUserInfo === CLI_BACK) {
|
|
1864
|
+
setupStep = 'botSetup';
|
|
1865
|
+
continue;
|
|
1866
|
+
}
|
|
1867
|
+
userInfo = nextUserInfo;
|
|
1868
|
+
setupStep = 'provider';
|
|
1869
|
+
continue;
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
if (setupStep === 'provider') {
|
|
1873
|
+
const providerSetup = await collectProviderStep({ isVi, existingProviderKey: providerKey, existingProviderKeyVal: providerKeyVal, existingOllamaModel: selectedOllamaModel });
|
|
1874
|
+
if (providerSetup.back) {
|
|
1875
|
+
setupStep = 'userInfo';
|
|
1876
|
+
continue;
|
|
1877
|
+
}
|
|
1878
|
+
providerKey = providerSetup.providerKey;
|
|
1879
|
+
provider = providerSetup.provider;
|
|
1880
|
+
providerKeyVal = providerSetup.providerKeyVal;
|
|
1881
|
+
selectedOllamaModel = providerSetup.selectedOllamaModel;
|
|
1882
|
+
setupStep = 'skills';
|
|
1883
|
+
continue;
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
if (setupStep === 'skills') {
|
|
1887
|
+
const skillSetup = await collectSkillsStep({ isVi, providerKey, existingSelectedSkills: selectedSkills, existingBrowserMode: browserMode, existingTtsOpenaiKey: ttsOpenaiKey, existingTtsElevenKey: ttsElevenKey, existingSmtpHost: smtpHost, existingSmtpPort: smtpPort, existingSmtpUser: smtpUser, existingSmtpPass: smtpPass });
|
|
1888
|
+
if (skillSetup.back) {
|
|
1889
|
+
setupStep = 'provider';
|
|
1890
|
+
continue;
|
|
1891
|
+
}
|
|
1892
|
+
selectedSkills = skillSetup.selectedSkills;
|
|
1893
|
+
browserMode = skillSetup.browserMode;
|
|
1894
|
+
ttsOpenaiKey = skillSetup.ttsOpenaiKey;
|
|
1895
|
+
ttsElevenKey = skillSetup.ttsElevenKey;
|
|
1896
|
+
smtpHost = skillSetup.smtpHost;
|
|
1897
|
+
smtpPort = skillSetup.smtpPort;
|
|
1898
|
+
smtpUser = skillSetup.smtpUser;
|
|
1899
|
+
smtpPass = skillSetup.smtpPass;
|
|
1900
|
+
setupStep = 'projectDir';
|
|
1901
|
+
continue;
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
if (setupStep === 'projectDir') {
|
|
1905
|
+
const nextProjectDir = await inputWithBack({ message: isVi ? 'Thư mục cài đặt project:' : 'Project install directory:', defaultValue: projectDir, allowBack: true, isVi });
|
|
1906
|
+
if (nextProjectDir === CLI_BACK) {
|
|
1907
|
+
setupStep = 'skills';
|
|
1908
|
+
continue;
|
|
1909
|
+
}
|
|
1910
|
+
projectDir = nextProjectDir;
|
|
1911
|
+
break;
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
const isMultiBot = botCount > 1 && channelKey === 'telegram';
|
|
1916
|
+
const botName = bots[0].name;
|
|
1917
|
+
const botDesc = bots[0].desc;
|
|
1918
|
+
const botPersona = bots[0].persona;
|
|
1919
|
+
const agentId = String(botName || 'chat').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'chat';
|
|
1920
|
+
const modelsPrimary = providerKey === 'ollama' ? selectedOllamaModel : providerKey === '9router' ? 'smart-route' : provider.models?.[0]?.id || 'gpt-4o-mini';
|
|
1921
|
+
const hasBrowserDesktop = selectedSkills.includes('browser') && browserMode === 'desktop';
|
|
1922
|
+
const hasBrowserServer = selectedSkills.includes('browser') && browserMode === 'server';
|
|
1351
1923
|
console.log(chalk.cyan(`\n🚀 ${isVi ? 'Đang tạo thư mục và file cấu hình...' : 'Generating directories and configurations...'}`));
|
|
1352
1924
|
|
|
1353
1925
|
await fs.ensureDir(projectDir);
|
|
@@ -1430,7 +2002,7 @@ async function main() {
|
|
|
1430
2002
|
? buildRelayPluginInstallCommand('openclaw')
|
|
1431
2003
|
: '';
|
|
1432
2004
|
const socatBridge = hasBrowserDesktop ? 'socat TCP-LISTEN:9222,fork,reuseaddr TCP:host.docker.internal:9222 &' : '';
|
|
1433
|
-
const deviceApproveLoop = '
|
|
2005
|
+
const deviceApproveLoop = 'while true; do sleep 5; openclaw devices approve --latest 2>/dev/null || true; done >/dev/null 2>&1 &';
|
|
1434
2006
|
|
|
1435
2007
|
// buildDockerArtifacts joins runtimeCommandParts with spaces, then appends 'openclaw gateway run'
|
|
1436
2008
|
// Each part should be a standalone command fragment (no trailing &&)
|
|
@@ -1449,7 +2021,7 @@ async function main() {
|
|
|
1449
2021
|
socatBridge,
|
|
1450
2022
|
deviceApproveLoop,
|
|
1451
2023
|
].filter(Boolean),
|
|
1452
|
-
volumeMount: '../../.openclaw:/root/.openclaw',
|
|
2024
|
+
volumeMount: '../../.openclaw:/root/project/.openclaw',
|
|
1453
2025
|
singleComposeName: `oc-${agentId}`,
|
|
1454
2026
|
multiComposeName: 'oc-multibot',
|
|
1455
2027
|
singleAppContainerName: `openclaw-${agentId}`,
|
|
@@ -1541,10 +2113,9 @@ async function main() {
|
|
|
1541
2113
|
workspaceDir: `workspace-${agentSlug}`,
|
|
1542
2114
|
};
|
|
1543
2115
|
});
|
|
1544
|
-
const telegramAccounts = Object.fromEntries(agentMetas.map((meta) => [meta.accountId, {
|
|
1545
|
-
botToken: meta.token,
|
|
1546
|
-
|
|
1547
|
-
}]));
|
|
2116
|
+
const telegramAccounts = Object.fromEntries(agentMetas.map((meta) => [meta.accountId, {
|
|
2117
|
+
botToken: meta.token,
|
|
2118
|
+
}]));
|
|
1548
2119
|
const telegramChannelConfig = {
|
|
1549
2120
|
enabled: true,
|
|
1550
2121
|
defaultAccount: 'default',
|
|
@@ -1579,13 +2150,13 @@ async function main() {
|
|
|
1579
2150
|
timeoutSeconds: provider.isLocal ? 900 : 120,
|
|
1580
2151
|
...(provider.isLocal ? { llm: { idleTimeoutSeconds: 300 } } : {}),
|
|
1581
2152
|
},
|
|
1582
|
-
list: agentMetas.map((meta) => ({
|
|
1583
|
-
id: meta.agentId,
|
|
1584
|
-
name: meta.name,
|
|
1585
|
-
workspace: `.openclaw/${meta.workspaceDir}`,
|
|
1586
|
-
agentDir: `agents/${meta.agentId}/agent`,
|
|
1587
|
-
model: { primary: modelsPrimary, fallbacks: [] },
|
|
1588
|
-
})),
|
|
2153
|
+
list: agentMetas.map((meta) => ({
|
|
2154
|
+
id: meta.agentId,
|
|
2155
|
+
name: meta.name,
|
|
2156
|
+
workspace: `.openclaw/${meta.workspaceDir}`,
|
|
2157
|
+
agentDir: `agents/${meta.agentId}/agent`,
|
|
2158
|
+
model: { primary: modelsPrimary, fallbacks: [] },
|
|
2159
|
+
})),
|
|
1589
2160
|
},
|
|
1590
2161
|
...(providerKey === '9router' ? {
|
|
1591
2162
|
models: {
|
|
@@ -1641,7 +2212,7 @@ async function main() {
|
|
|
1641
2212
|
auth: { mode: 'token', token: 'cli-dummy-token-xyz123' },
|
|
1642
2213
|
},
|
|
1643
2214
|
};
|
|
1644
|
-
sharedConfig.plugins = { entries: {} };
|
|
2215
|
+
sharedConfig.plugins = { entries: {} };
|
|
1645
2216
|
|
|
1646
2217
|
if (hasBrowserDesktop) {
|
|
1647
2218
|
sharedConfig.browser = {
|
|
@@ -1658,7 +2229,7 @@ async function main() {
|
|
|
1658
2229
|
|
|
1659
2230
|
await fs.writeJson(path.join(rootClawDir, 'openclaw.json'), sharedConfig, { spaces: 2 });
|
|
1660
2231
|
await fs.writeFile(
|
|
1661
|
-
path.join(projectDir,
|
|
2232
|
+
path.join(projectDir, TELEGRAM_SETUP_GUIDE_FILENAME),
|
|
1662
2233
|
buildTelegramPostInstallChecklist({ isVi, bots, groupId }),
|
|
1663
2234
|
'utf8',
|
|
1664
2235
|
);
|
|
@@ -1727,11 +2298,11 @@ async function main() {
|
|
|
1727
2298
|
selectedSkills, deployMode,
|
|
1728
2299
|
isDesktop: hasBrowserDesktop, isServer: hasBrowserServer,
|
|
1729
2300
|
isMultiBot: true, ownAliases, otherAgents,
|
|
1730
|
-
teamRoster: teamMdRoster, userInfo,
|
|
1731
|
-
agentWorkspaceDir: meta.workspaceDir,
|
|
1732
|
-
isRelayBot: true,
|
|
1733
|
-
replyToDirectMessages: true,
|
|
1734
|
-
});
|
|
2301
|
+
teamRoster: teamMdRoster, userInfo,
|
|
2302
|
+
agentWorkspaceDir: meta.workspaceDir,
|
|
2303
|
+
isRelayBot: true,
|
|
2304
|
+
replyToDirectMessages: true,
|
|
2305
|
+
});
|
|
1735
2306
|
}
|
|
1736
2307
|
} else {
|
|
1737
2308
|
const numBotsToConfigure = 1;
|
|
@@ -1748,9 +2319,9 @@ async function main() {
|
|
|
1748
2319
|
}));
|
|
1749
2320
|
const ownAliases = [loopBotName, bots[bIndex]?.slashCmd || '', `bot ${bIndex + 1}`].filter(Boolean);
|
|
1750
2321
|
const otherBotNames = teamRoster.filter((peer) => peer.idx !== bIndex).map((peer) => peer.name);
|
|
1751
|
-
const loopAgentId = loopBotName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || `bot${bIndex + 1}`;
|
|
1752
|
-
const loopWorkspaceDir = `workspace-${loopAgentId}`;
|
|
1753
|
-
const loopBotDir = isMultiBot ? path.join(projectDir, `bot${bIndex+1}`) : projectDir;
|
|
2322
|
+
const loopAgentId = loopBotName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || `bot${bIndex + 1}`;
|
|
2323
|
+
const loopWorkspaceDir = `workspace-${loopAgentId}`;
|
|
2324
|
+
const loopBotDir = isMultiBot ? path.join(projectDir, `bot${bIndex+1}`) : projectDir;
|
|
1754
2325
|
|
|
1755
2326
|
await fs.ensureDir(path.join(loopBotDir, '.openclaw', 'agents', loopAgentId, 'agent'));
|
|
1756
2327
|
if (Object.keys(authProfilesJson).length > 0) {
|
|
@@ -1767,13 +2338,13 @@ async function main() {
|
|
|
1767
2338
|
compaction: { mode: 'safeguard' },
|
|
1768
2339
|
timeoutSeconds: provider.isLocal ? 900 : 120,
|
|
1769
2340
|
...(provider.isLocal ? { llm: { idleTimeoutSeconds: 300 } } : {}),
|
|
1770
|
-
},
|
|
1771
|
-
list: [{
|
|
1772
|
-
id: loopAgentId,
|
|
1773
|
-
workspace: `.openclaw/${loopWorkspaceDir}`,
|
|
1774
|
-
agentDir: `agents/${loopAgentId}/agent`,
|
|
1775
|
-
model: { primary: modelsPrimary, fallbacks: [] }
|
|
1776
|
-
}]
|
|
2341
|
+
},
|
|
2342
|
+
list: [{
|
|
2343
|
+
id: loopAgentId,
|
|
2344
|
+
workspace: `.openclaw/${loopWorkspaceDir}`,
|
|
2345
|
+
agentDir: `agents/${loopAgentId}/agent`,
|
|
2346
|
+
model: { primary: modelsPrimary, fallbacks: [] }
|
|
2347
|
+
}]
|
|
1777
2348
|
},
|
|
1778
2349
|
...(providerKey === '9router' ? {
|
|
1779
2350
|
models: {
|
|
@@ -1834,13 +2405,29 @@ async function main() {
|
|
|
1834
2405
|
botConfig.skills = { entries: skillEntries };
|
|
1835
2406
|
}
|
|
1836
2407
|
|
|
1837
|
-
if (channelKey === 'telegram') {
|
|
1838
|
-
const telegramConfig = {
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
2408
|
+
if (channelKey === 'telegram') {
|
|
2409
|
+
const telegramConfig = {
|
|
2410
|
+
enabled: true,
|
|
2411
|
+
dmPolicy: 'open',
|
|
2412
|
+
allowFrom: ['*'],
|
|
2413
|
+
defaultAccount: 'default',
|
|
2414
|
+
replyToMode: 'first',
|
|
2415
|
+
reactionLevel: 'minimal',
|
|
2416
|
+
actions: {
|
|
2417
|
+
sendMessage: true,
|
|
2418
|
+
reactions: true,
|
|
2419
|
+
},
|
|
2420
|
+
accounts: {
|
|
2421
|
+
default: {
|
|
2422
|
+
botToken: loopBotToken || '<your_bot_token>',
|
|
2423
|
+
},
|
|
2424
|
+
},
|
|
2425
|
+
};
|
|
2426
|
+
if (isMultiBot) {
|
|
2427
|
+
telegramConfig.groupPolicy = groupId ? 'allowlist' : 'open';
|
|
2428
|
+
telegramConfig.groupAllowFrom = ['*'];
|
|
2429
|
+
telegramConfig.groups = {
|
|
2430
|
+
[groupId || '*']: { enabled: true, requireMention: false }
|
|
1844
2431
|
};
|
|
1845
2432
|
}
|
|
1846
2433
|
botConfig.channels['telegram'] = telegramConfig;
|
|
@@ -1857,7 +2444,7 @@ async function main() {
|
|
|
1857
2444
|
await fs.writeJson(path.join(loopBotDir, '.openclaw', 'openclaw.json'), botConfig, { spaces: 2 });
|
|
1858
2445
|
|
|
1859
2446
|
// ── Workspace files: use shared writeWorkspaceFiles() ──────────────────────
|
|
1860
|
-
const dockerWorkspaceDir = path.join(loopBotDir, '.openclaw', loopWorkspaceDir);
|
|
2447
|
+
const dockerWorkspaceDir = path.join(loopBotDir, '.openclaw', loopWorkspaceDir);
|
|
1861
2448
|
const dockerOwnAliases = [loopBotName, bots[bIndex]?.slashCmd || '', `bot ${bIndex + 1}`].filter(Boolean);
|
|
1862
2449
|
const dockerOtherAgents = teamRoster
|
|
1863
2450
|
.filter((peer) => peer.idx !== bIndex)
|
|
@@ -1876,44 +2463,44 @@ async function main() {
|
|
|
1876
2463
|
isMultiBot,
|
|
1877
2464
|
ownAliases: dockerOwnAliases,
|
|
1878
2465
|
otherAgents: dockerOtherAgents,
|
|
1879
|
-
teamRoster,
|
|
1880
|
-
userInfo,
|
|
1881
|
-
agentWorkspaceDir: loopWorkspaceDir,
|
|
1882
|
-
isRelayBot: isMultiBot,
|
|
1883
|
-
replyToDirectMessages: true,
|
|
1884
|
-
});
|
|
2466
|
+
teamRoster,
|
|
2467
|
+
userInfo,
|
|
2468
|
+
agentWorkspaceDir: loopWorkspaceDir,
|
|
2469
|
+
isRelayBot: isMultiBot,
|
|
2470
|
+
replyToDirectMessages: true,
|
|
2471
|
+
});
|
|
1885
2472
|
|
|
1886
2473
|
if (isMultiBot) {
|
|
1887
2474
|
// Append per-bot reply rules to AGENTS.md
|
|
1888
2475
|
const otherBotNames = teamRoster.filter((p) => p.idx !== bIndex).map((p) => p.name);
|
|
1889
2476
|
const extraAgentsMd = isVi
|
|
1890
|
-
? `\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-
|
|
1891
|
-
: `\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-
|
|
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- 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.`
|
|
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 \`👍\` 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.`;
|
|
1892
2479
|
await fs.appendFile(path.join(dockerWorkspaceDir, 'AGENTS.md'), extraAgentsMd);
|
|
1893
2480
|
}
|
|
1894
2481
|
} // END FOR LOOP
|
|
1895
2482
|
}
|
|
1896
2483
|
|
|
1897
2484
|
// ── Chrome Debug scripts — via shared builder (same content as wizard ZIP) ─
|
|
1898
|
-
await writeGeneratedArtifacts(projectDir, buildCliChromeDebugArtifacts());
|
|
2485
|
+
await writeGeneratedArtifacts(projectDir, buildCliChromeDebugArtifacts());
|
|
1899
2486
|
|
|
1900
|
-
// ── Uninstall scripts ───────────────────────────────────────────────────────
|
|
1901
|
-
await writeGeneratedArtifacts(projectDir, buildCliUninstallArtifacts({
|
|
1902
|
-
deployMode, osChoice: detectedOS, projectDir, botName,
|
|
1903
|
-
}));
|
|
1904
|
-
|
|
1905
|
-
// ── Upgrade scripts ─────────────────────────────────────────────────────────
|
|
1906
|
-
await writeGeneratedArtifacts(projectDir, buildCliUpgradeArtifacts());
|
|
2487
|
+
// ── Uninstall scripts ───────────────────────────────────────────────────────
|
|
2488
|
+
await writeGeneratedArtifacts(projectDir, buildCliUninstallArtifacts({
|
|
2489
|
+
deployMode, osChoice: detectedOS, projectDir, botName,
|
|
2490
|
+
}));
|
|
2491
|
+
|
|
2492
|
+
// ── Upgrade scripts ─────────────────────────────────────────────────────────
|
|
2493
|
+
await writeGeneratedArtifacts(projectDir, buildCliUpgradeArtifacts());
|
|
1907
2494
|
|
|
1908
2495
|
// ── start-bot.bat / start-bot.sh — one-click restart scripts ─────────────
|
|
1909
|
-
// Generated for native deployments only (docker has docker compose up)
|
|
1910
|
-
if (deployMode !== 'docker') {
|
|
1911
|
-
await writeGeneratedArtifacts(projectDir, buildCliStartBotArtifacts({
|
|
1912
|
-
projectDir,
|
|
1913
|
-
openclawHome: path.join(projectDir, '.openclaw'),
|
|
1914
|
-
is9Router: providerKey === '9router',
|
|
1915
|
-
isVi,
|
|
1916
|
-
}));
|
|
2496
|
+
// Generated for native deployments only (docker has docker compose up)
|
|
2497
|
+
if (deployMode !== 'docker') {
|
|
2498
|
+
await writeGeneratedArtifacts(projectDir, buildCliStartBotArtifacts({
|
|
2499
|
+
projectDir,
|
|
2500
|
+
openclawHome: path.join(projectDir, '.openclaw'),
|
|
2501
|
+
is9Router: providerKey === '9router',
|
|
2502
|
+
isVi,
|
|
2503
|
+
}));
|
|
1917
2504
|
|
|
1918
2505
|
console.log(chalk.cyan(
|
|
1919
2506
|
isVi
|
|
@@ -1922,10 +2509,12 @@ async function main() {
|
|
|
1922
2509
|
));
|
|
1923
2510
|
}
|
|
1924
2511
|
|
|
1925
|
-
console.log(chalk.green(`✅ ${isVi ? 'Tạo cấu hình thành công!' : 'Configs created successfully!'}`));
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
2512
|
+
console.log(chalk.green(`✅ ${isVi ? 'Tạo cấu hình thành công!' : 'Configs created successfully!'}`));
|
|
2513
|
+
|
|
2514
|
+
installLatestOpenClaw({ isVi, osChoice });
|
|
2515
|
+
|
|
2516
|
+
// 7. Auto Run
|
|
2517
|
+
const autoRun = deployMode === 'docker' ? await confirm({
|
|
1929
2518
|
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?',
|
|
1930
2519
|
default: true
|
|
1931
2520
|
}) : false;
|
|
@@ -1988,7 +2577,7 @@ async function main() {
|
|
|
1988
2577
|
? 'Nhắn tin cho bot trên Telegram là dùng được ngay!'
|
|
1989
2578
|
: 'Just message your bot on Telegram to start chatting!'}`));
|
|
1990
2579
|
if (isMultiBot) {
|
|
1991
|
-
console.log(chalk.yellow(`\n${isVi ? '📋
|
|
2580
|
+
console.log(chalk.yellow(`\n${isVi ? '📋 Đọc hướng dẫn setup bot trong Group:' : '📋 Read setup guide in Group:'} ${TELEGRAM_SETUP_GUIDE_FILENAME}`));
|
|
1992
2581
|
console.log(chalk.gray(isVi
|
|
1993
2582
|
? ' → 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.'
|
|
1994
2583
|
: ' → Run scripts/telegram-post-install-check.mjs to get the real links, verify group/privacy, then add the bots and disable privacy mode.'));
|
|
@@ -2005,38 +2594,32 @@ async function main() {
|
|
|
2005
2594
|
});
|
|
2006
2595
|
|
|
2007
2596
|
}
|
|
2597
|
+
if (deployMode === 'docker') {
|
|
2008
2598
|
|
|
2009
|
-
installLatestOpenClaw({ isVi, osChoice });
|
|
2010
|
-
|
|
2011
|
-
if (deployMode === 'docker') {
|
|
2012
|
-
|
|
2013
|
-
if (isMultiBot && channelKey === 'telegram') {
|
|
2014
|
-
console.log(chalk.yellow(`\n${isVi ? '📋 Xem hướng dẫn sau cài:' : '📋 Read post-install guide:'} ${path.join(projectDir, 'TELEGRAM-POST-INSTALL.md')}`));
|
|
2015
|
-
}
|
|
2016
2599
|
// ── Auto-install openclaw binary if not present ──────────────────────────
|
|
2017
2600
|
const isOpenClawInstalled = () => { try { execSync('openclaw --version', { stdio: 'ignore' }); return true; } catch { return false; } };
|
|
2018
2601
|
if (!isOpenClawInstalled()) {
|
|
2019
2602
|
console.log(chalk.cyan(isVi
|
|
2020
|
-
?
|
|
2021
|
-
:
|
|
2603
|
+
? `\n📦 Đang cài openclaw binary (${OPENCLAW_NPM_SPEC})...`
|
|
2604
|
+
: `\n📦 Installing openclaw binary (${OPENCLAW_NPM_SPEC})...`));
|
|
2022
2605
|
try {
|
|
2023
|
-
execSync(
|
|
2606
|
+
execSync(`npm install -g ${OPENCLAW_NPM_SPEC}`, { stdio: 'inherit' });
|
|
2024
2607
|
console.log(chalk.green(isVi ? '✅ openclaw đã cài xong!' : '✅ openclaw installed!'));
|
|
2025
2608
|
} catch {
|
|
2026
2609
|
console.log(chalk.yellow(isVi
|
|
2027
|
-
?
|
|
2028
|
-
:
|
|
2610
|
+
? `⚠️ Không tự cài được. Chạy thủ công: sudo npm install -g ${OPENCLAW_NPM_SPEC}`
|
|
2611
|
+
: `⚠️ Could not auto-install. Run manually: sudo npm install -g ${OPENCLAW_NPM_SPEC}`));
|
|
2029
2612
|
}
|
|
2030
2613
|
}
|
|
2031
2614
|
|
|
2032
2615
|
if (isMultiBot && channelKey === 'telegram') {
|
|
2033
|
-
console.log(chalk.yellow(`\n${isVi ? '📋 Xem hướng dẫn sau cài:' : '📋 Read post-install guide:'} ${path.join(projectDir,
|
|
2616
|
+
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)}`));
|
|
2034
2617
|
}
|
|
2035
2618
|
} else {
|
|
2036
2619
|
if (!isOpenClawInstalled()) {
|
|
2037
2620
|
console.log(chalk.cyan(isVi
|
|
2038
|
-
?
|
|
2039
|
-
:
|
|
2621
|
+
? `\n📦 Dang cai openclaw binary (${OPENCLAW_NPM_SPEC})...`
|
|
2622
|
+
: `\n📦 Installing openclaw binary (${OPENCLAW_NPM_SPEC})...`));
|
|
2040
2623
|
if (!installGlobalPackage(OPENCLAW_NPM_SPEC, { isVi, osChoice, displayName: 'openclaw' })) {
|
|
2041
2624
|
process.exit(1);
|
|
2042
2625
|
}
|
|
@@ -2178,10 +2761,12 @@ async function main() {
|
|
|
2178
2761
|
|
|
2179
2762
|
console.log(chalk.cyan(`\n👉 ${isVi ? 'Native runtime da duoc cai san va khoi dong.' : 'Native runtime is installed and started.'}`));
|
|
2180
2763
|
if (isMultiBot && channelKey === 'telegram') {
|
|
2181
|
-
console.log(chalk.yellow(`\n📋 ${isVi ? 'Xem huong dan sau cai:' : 'Read post-install guide:'} ${path.join(projectDir,
|
|
2764
|
+
console.log(chalk.yellow(`\n📋 ${isVi ? 'Xem huong dan sau cai:' : 'Read post-install guide:'} ${path.join(projectDir, TELEGRAM_SETUP_GUIDE_FILENAME)}`));
|
|
2182
2765
|
}
|
|
2183
2766
|
}
|
|
2184
|
-
}
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
|
|
2185
2770
|
|
|
2186
2771
|
main().catch(err => {
|
|
2187
2772
|
console.error(chalk.red('Error:'), err);
|