create-openclaw-bot 5.6.10 → 5.6.12
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 +2 -2
- package/README.vi.md +2 -2
- package/dist/cli.js +1299 -1298
- package/dist/setup/shared/install-gen.js +1 -1
- package/dist/setup.js +3 -3
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -20,25 +20,25 @@ function loadSharedModule(modulePath, globalName) {
|
|
|
20
20
|
return globalThis[globalName] || loaded || {};
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
const {
|
|
24
|
-
OPENCLAW_NPM_SPEC,
|
|
25
|
-
OPENCLAW_RUNTIME_PACKAGES,
|
|
26
|
-
NINE_ROUTER_PROXY_API_KEY,
|
|
27
|
-
NINE_ROUTER_API_BASE_URL,
|
|
28
|
-
SMART_ROUTE_PROVIDER_MODELS,
|
|
29
|
-
SMART_ROUTE_PROVIDER_ORDER,
|
|
30
|
-
TELEGRAM_RELAY_PLUGIN_SPEC,
|
|
31
|
-
TELEGRAM_SETUP_GUIDE_FILENAME,
|
|
32
|
-
buildRelayPluginInstallCommand,
|
|
33
|
-
buildTelegramPostInstallChecklist,
|
|
34
|
-
get9RouterBaseUrl,
|
|
35
|
-
build9RouterProviderConfig,
|
|
36
|
-
} = loadSharedModule('./setup/shared/common-gen.js', '__openclawCommon');
|
|
37
|
-
|
|
38
|
-
const {
|
|
39
|
-
buildDockerArtifacts,
|
|
40
|
-
build9RouterPatchScript,
|
|
41
|
-
} = loadSharedModule('./setup/shared/docker-gen.js', '__openclawDockerGen');
|
|
23
|
+
const {
|
|
24
|
+
OPENCLAW_NPM_SPEC,
|
|
25
|
+
OPENCLAW_RUNTIME_PACKAGES,
|
|
26
|
+
NINE_ROUTER_PROXY_API_KEY,
|
|
27
|
+
NINE_ROUTER_API_BASE_URL,
|
|
28
|
+
SMART_ROUTE_PROVIDER_MODELS,
|
|
29
|
+
SMART_ROUTE_PROVIDER_ORDER,
|
|
30
|
+
TELEGRAM_RELAY_PLUGIN_SPEC,
|
|
31
|
+
TELEGRAM_SETUP_GUIDE_FILENAME,
|
|
32
|
+
buildRelayPluginInstallCommand,
|
|
33
|
+
buildTelegramPostInstallChecklist,
|
|
34
|
+
get9RouterBaseUrl,
|
|
35
|
+
build9RouterProviderConfig,
|
|
36
|
+
} = loadSharedModule('./setup/shared/common-gen.js', '__openclawCommon');
|
|
37
|
+
|
|
38
|
+
const {
|
|
39
|
+
buildDockerArtifacts,
|
|
40
|
+
build9RouterPatchScript,
|
|
41
|
+
} = loadSharedModule('./setup/shared/docker-gen.js', '__openclawDockerGen');
|
|
42
42
|
|
|
43
43
|
const {
|
|
44
44
|
buildWorkspaceFileMap,
|
|
@@ -200,32 +200,32 @@ function resolveNative9RouterDesktopLaunch() {
|
|
|
200
200
|
};
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
function build9RouterSmartRouteSyncScript(dbPath) {
|
|
204
|
-
const safeDbPath = JSON.stringify(dbPath);
|
|
205
|
-
const safeRouterBaseUrl = JSON.stringify(NINE_ROUTER_API_BASE_URL);
|
|
206
|
-
const safeModelPriority = JSON.stringify(SMART_ROUTE_PROVIDER_MODELS);
|
|
207
|
-
const safeProviderOrder = JSON.stringify(SMART_ROUTE_PROVIDER_ORDER);
|
|
208
|
-
return `function bootstrap() {
|
|
209
|
-
const fs = require('fs');
|
|
210
|
-
const path = require('path');
|
|
211
|
-
const dbPath = ${safeDbPath};
|
|
212
|
-
const ROUTER=${safeRouterBaseUrl};
|
|
213
|
-
const MODEL_PRIORITY=${safeModelPriority};
|
|
214
|
-
const PREF=${safeProviderOrder};
|
|
215
|
-
const sync = async () => {
|
|
216
|
-
try {
|
|
217
|
-
const response = await fetch(ROUTER + '/api/providers');
|
|
218
|
-
if (!response.ok) return;
|
|
219
|
-
const payload = await response.json();
|
|
220
|
-
const rawConnections = Array.isArray(payload.connections)
|
|
221
|
-
? payload.connections
|
|
222
|
-
: Array.isArray(payload.providerConnections)
|
|
223
|
-
? payload.providerConnections
|
|
224
|
-
: [];
|
|
225
|
-
const a = [...new Set(rawConnections
|
|
226
|
-
.filter((item) => item && item.provider && item.isActive !== false && !item.disabled)
|
|
227
|
-
.map((item) => item.provider))];
|
|
228
|
-
let db = {};
|
|
203
|
+
function build9RouterSmartRouteSyncScript(dbPath) {
|
|
204
|
+
const safeDbPath = JSON.stringify(dbPath);
|
|
205
|
+
const safeRouterBaseUrl = JSON.stringify(NINE_ROUTER_API_BASE_URL);
|
|
206
|
+
const safeModelPriority = JSON.stringify(SMART_ROUTE_PROVIDER_MODELS);
|
|
207
|
+
const safeProviderOrder = JSON.stringify(SMART_ROUTE_PROVIDER_ORDER);
|
|
208
|
+
return `function bootstrap() {
|
|
209
|
+
const fs = require('fs');
|
|
210
|
+
const path = require('path');
|
|
211
|
+
const dbPath = ${safeDbPath};
|
|
212
|
+
const ROUTER=${safeRouterBaseUrl};
|
|
213
|
+
const MODEL_PRIORITY=${safeModelPriority};
|
|
214
|
+
const PREF=${safeProviderOrder};
|
|
215
|
+
const sync = async () => {
|
|
216
|
+
try {
|
|
217
|
+
const response = await fetch(ROUTER + '/api/providers');
|
|
218
|
+
if (!response.ok) return;
|
|
219
|
+
const payload = await response.json();
|
|
220
|
+
const rawConnections = Array.isArray(payload.connections)
|
|
221
|
+
? payload.connections
|
|
222
|
+
: Array.isArray(payload.providerConnections)
|
|
223
|
+
? payload.providerConnections
|
|
224
|
+
: [];
|
|
225
|
+
const a = [...new Set(rawConnections
|
|
226
|
+
.filter((item) => item && item.provider && item.isActive !== false && !item.disabled)
|
|
227
|
+
.map((item) => item.provider))];
|
|
228
|
+
let db = {};
|
|
229
229
|
try {
|
|
230
230
|
db = JSON.parse(fs.readFileSync(dbPath, 'utf8'));
|
|
231
231
|
} catch {}
|
|
@@ -239,12 +239,12 @@ function build9RouterSmartRouteSyncScript(dbPath) {
|
|
|
239
239
|
console.log('Removed smart-route (no active providers)');
|
|
240
240
|
}
|
|
241
241
|
};
|
|
242
|
-
if (!a.length) {
|
|
243
|
-
removeSmartRoute();
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
a.sort((x, y) => (PREF.indexOf(x) === -1 ? 99 : PREF.indexOf(x)) - (PREF.indexOf(y) === -1 ? 99 : PREF.indexOf(y)));
|
|
247
|
-
const m = a.flatMap((provider) => MODEL_PRIORITY[provider] || []);
|
|
242
|
+
if (!a.length) {
|
|
243
|
+
removeSmartRoute();
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
a.sort((x, y) => (PREF.indexOf(x) === -1 ? 99 : PREF.indexOf(x)) - (PREF.indexOf(y) === -1 ? 99 : PREF.indexOf(y)));
|
|
247
|
+
const m = a.flatMap((provider) => MODEL_PRIORITY[provider] || []);
|
|
248
248
|
if (!m.length) {
|
|
249
249
|
removeSmartRoute();
|
|
250
250
|
return;
|
|
@@ -441,69 +441,69 @@ function resolveCommandOnPath(command) {
|
|
|
441
441
|
}
|
|
442
442
|
}
|
|
443
443
|
|
|
444
|
-
async function writeNative9RouterSyncScript(projectDir) {
|
|
445
|
-
const syncScriptPath = path.join(projectDir, '.openclaw', '9router-smart-route-sync.js');
|
|
446
|
-
await fs.ensureDir(path.dirname(syncScriptPath));
|
|
447
|
-
// Use native home data dir so sync script writes to same place 9router binary reads from
|
|
448
|
-
const nativeDataDir = getNative9RouterDataDir();
|
|
449
|
-
await fs.ensureDir(nativeDataDir);
|
|
450
|
-
await fs.writeFile(syncScriptPath, build9RouterSmartRouteSyncScript(path.join(nativeDataDir, 'db.json')));
|
|
451
|
-
return syncScriptPath;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
async function writeNative9RouterPatchScript(projectDir) {
|
|
455
|
-
const patchScriptPath = path.join(projectDir, '.openclaw', 'patch-9router.js');
|
|
456
|
-
await fs.ensureDir(path.dirname(patchScriptPath));
|
|
457
|
-
await fs.writeFile(patchScriptPath, build9RouterPatchScript());
|
|
458
|
-
return patchScriptPath;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
async function patchProject9RouterOpenClawConfig(projectDir) {
|
|
462
|
-
const configPath = path.join(projectDir, '.openclaw', 'openclaw.json');
|
|
463
|
-
if (!await fs.pathExists(configPath)) return false;
|
|
464
|
-
const config = await fs.readJson(configPath);
|
|
465
|
-
const provider = config?.models?.providers?.['9router'];
|
|
466
|
-
if (!provider) return false;
|
|
467
|
-
provider.baseUrl = get9RouterBaseUrl(detectProjectDeployMode(projectDir));
|
|
468
|
-
provider.apiKey = NINE_ROUTER_PROXY_API_KEY;
|
|
469
|
-
provider.api = 'openai-completions';
|
|
470
|
-
provider.models = build9RouterProviderConfig(provider.baseUrl).models;
|
|
471
|
-
await fs.writeJson(configPath, config, { spaces: 2 });
|
|
472
|
-
return true;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
async function patchProjectDocker9Router(projectDir) {
|
|
476
|
-
const dockerDir = path.join(projectDir, 'docker', 'openclaw');
|
|
477
|
-
const composePath = path.join(dockerDir, 'docker-compose.yml');
|
|
478
|
-
if (!await fs.pathExists(composePath)) return false;
|
|
479
|
-
|
|
480
|
-
await fs.ensureDir(dockerDir);
|
|
481
|
-
await fs.writeFile(path.join(dockerDir, 'sync.js'), build9RouterSmartRouteSyncScript('/root/.9router/db.json'));
|
|
482
|
-
await fs.writeFile(path.join(dockerDir, 'patch-9router.js'), build9RouterPatchScript());
|
|
483
|
-
let compose = await fs.readFile(composePath, 'utf8');
|
|
484
|
-
compose = compose.replace(
|
|
485
|
-
/node -e "require\('fs'\)\.writeFileSync\('\/tmp\/sync\.js',Buffer\.from\('[^']*','base64'\)\.toString\(\)\)"/,
|
|
486
|
-
"cp /opt/sync.js /tmp/sync.js"
|
|
487
|
-
);
|
|
488
|
-
compose = compose.replace(
|
|
489
|
-
/(npm install -g [^\n]+\n)/,
|
|
490
|
-
`$1 cp /opt/patch-9router.js /tmp/patch-9router.js\n`
|
|
491
|
-
);
|
|
492
|
-
if (!compose.includes('node /tmp/patch-9router.js || true')) {
|
|
493
|
-
compose = compose.replace(
|
|
494
|
-
/(\s*node \/tmp\/sync\.js > \/tmp\/sync\.log 2>&1 &\n)/,
|
|
495
|
-
` node /tmp/patch-9router.js || true\n$1`
|
|
496
|
-
);
|
|
497
|
-
}
|
|
498
|
-
if (!compose.includes('./sync.js:/opt/sync.js:ro')) {
|
|
499
|
-
compose = compose.replace(
|
|
500
|
-
/(\s*-\s*9router-data:\/root\/\.9router\s*\n)/,
|
|
501
|
-
`$1 - ./sync.js:/opt/sync.js:ro\n - ./patch-9router.js:/opt/patch-9router.js:ro\n`
|
|
502
|
-
);
|
|
503
|
-
}
|
|
504
|
-
await fs.writeFile(composePath, compose, 'utf8');
|
|
505
|
-
return true;
|
|
506
|
-
}
|
|
444
|
+
async function writeNative9RouterSyncScript(projectDir) {
|
|
445
|
+
const syncScriptPath = path.join(projectDir, '.openclaw', '9router-smart-route-sync.js');
|
|
446
|
+
await fs.ensureDir(path.dirname(syncScriptPath));
|
|
447
|
+
// Use native home data dir so sync script writes to same place 9router binary reads from
|
|
448
|
+
const nativeDataDir = getNative9RouterDataDir();
|
|
449
|
+
await fs.ensureDir(nativeDataDir);
|
|
450
|
+
await fs.writeFile(syncScriptPath, build9RouterSmartRouteSyncScript(path.join(nativeDataDir, 'db.json')));
|
|
451
|
+
return syncScriptPath;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async function writeNative9RouterPatchScript(projectDir) {
|
|
455
|
+
const patchScriptPath = path.join(projectDir, '.openclaw', 'patch-9router.js');
|
|
456
|
+
await fs.ensureDir(path.dirname(patchScriptPath));
|
|
457
|
+
await fs.writeFile(patchScriptPath, build9RouterPatchScript());
|
|
458
|
+
return patchScriptPath;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
async function patchProject9RouterOpenClawConfig(projectDir) {
|
|
462
|
+
const configPath = path.join(projectDir, '.openclaw', 'openclaw.json');
|
|
463
|
+
if (!await fs.pathExists(configPath)) return false;
|
|
464
|
+
const config = await fs.readJson(configPath);
|
|
465
|
+
const provider = config?.models?.providers?.['9router'];
|
|
466
|
+
if (!provider) return false;
|
|
467
|
+
provider.baseUrl = get9RouterBaseUrl(detectProjectDeployMode(projectDir));
|
|
468
|
+
provider.apiKey = NINE_ROUTER_PROXY_API_KEY;
|
|
469
|
+
provider.api = 'openai-completions';
|
|
470
|
+
provider.models = build9RouterProviderConfig(provider.baseUrl).models;
|
|
471
|
+
await fs.writeJson(configPath, config, { spaces: 2 });
|
|
472
|
+
return true;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
async function patchProjectDocker9Router(projectDir) {
|
|
476
|
+
const dockerDir = path.join(projectDir, 'docker', 'openclaw');
|
|
477
|
+
const composePath = path.join(dockerDir, 'docker-compose.yml');
|
|
478
|
+
if (!await fs.pathExists(composePath)) return false;
|
|
479
|
+
|
|
480
|
+
await fs.ensureDir(dockerDir);
|
|
481
|
+
await fs.writeFile(path.join(dockerDir, 'sync.js'), build9RouterSmartRouteSyncScript('/root/.9router/db.json'));
|
|
482
|
+
await fs.writeFile(path.join(dockerDir, 'patch-9router.js'), build9RouterPatchScript());
|
|
483
|
+
let compose = await fs.readFile(composePath, 'utf8');
|
|
484
|
+
compose = compose.replace(
|
|
485
|
+
/node -e "require\('fs'\)\.writeFileSync\('\/tmp\/sync\.js',Buffer\.from\('[^']*','base64'\)\.toString\(\)\)"/,
|
|
486
|
+
"cp /opt/sync.js /tmp/sync.js"
|
|
487
|
+
);
|
|
488
|
+
compose = compose.replace(
|
|
489
|
+
/(npm install -g [^\n]+\n)/,
|
|
490
|
+
`$1 cp /opt/patch-9router.js /tmp/patch-9router.js\n`
|
|
491
|
+
);
|
|
492
|
+
if (!compose.includes('node /tmp/patch-9router.js || true')) {
|
|
493
|
+
compose = compose.replace(
|
|
494
|
+
/(\s*node \/tmp\/sync\.js > \/tmp\/sync\.log 2>&1 &\n)/,
|
|
495
|
+
` node /tmp/patch-9router.js || true\n$1`
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
if (!compose.includes('./sync.js:/opt/sync.js:ro')) {
|
|
499
|
+
compose = compose.replace(
|
|
500
|
+
/(\s*-\s*9router-data:\/root\/\.9router\s*\n)/,
|
|
501
|
+
`$1 - ./sync.js:/opt/sync.js:ro\n - ./patch-9router.js:/opt/patch-9router.js:ro\n`
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
await fs.writeFile(composePath, compose, 'utf8');
|
|
505
|
+
return true;
|
|
506
|
+
}
|
|
507
507
|
|
|
508
508
|
function getGatewayAllowedOrigins(port) {
|
|
509
509
|
const normalizedPort = Number(port) || 18791;
|
|
@@ -854,7 +854,7 @@ function detectProjectBotName(projectDir) {
|
|
|
854
854
|
return path.basename(projectDir);
|
|
855
855
|
}
|
|
856
856
|
|
|
857
|
-
function detectProjectUses9Router(projectDir) {
|
|
857
|
+
function detectProjectUses9Router(projectDir) {
|
|
858
858
|
try {
|
|
859
859
|
const configPath = path.join(projectDir, '.openclaw', 'openclaw.json');
|
|
860
860
|
if (fs.existsSync(configPath)) {
|
|
@@ -866,25 +866,25 @@ function detectProjectUses9Router(projectDir) {
|
|
|
866
866
|
} catch {
|
|
867
867
|
// fallback below
|
|
868
868
|
}
|
|
869
|
-
return fs.existsSync(path.join(projectDir, '.9router'));
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
function detectProjectIsMultiBot(projectDir) {
|
|
873
|
-
try {
|
|
874
|
-
const configPath = path.join(projectDir, '.openclaw', 'openclaw.json');
|
|
875
|
-
if (fs.existsSync(configPath)) {
|
|
876
|
-
const config = fs.readJsonSync(configPath);
|
|
877
|
-
return (config?.agents?.list?.length || 0) > 1;
|
|
878
|
-
}
|
|
879
|
-
} catch {
|
|
880
|
-
// fallback below
|
|
881
|
-
}
|
|
882
|
-
return false;
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
function getNativePm2AppName(isMultiBot = false) {
|
|
886
|
-
return isMultiBot ? 'openclaw-multibot' : 'openclaw';
|
|
887
|
-
}
|
|
869
|
+
return fs.existsSync(path.join(projectDir, '.9router'));
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
function detectProjectIsMultiBot(projectDir) {
|
|
873
|
+
try {
|
|
874
|
+
const configPath = path.join(projectDir, '.openclaw', 'openclaw.json');
|
|
875
|
+
if (fs.existsSync(configPath)) {
|
|
876
|
+
const config = fs.readJsonSync(configPath);
|
|
877
|
+
return (config?.agents?.list?.length || 0) > 1;
|
|
878
|
+
}
|
|
879
|
+
} catch {
|
|
880
|
+
// fallback below
|
|
881
|
+
}
|
|
882
|
+
return false;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
function getNativePm2AppName(isMultiBot = false) {
|
|
886
|
+
return isMultiBot ? 'openclaw-multibot' : 'openclaw';
|
|
887
|
+
}
|
|
888
888
|
|
|
889
889
|
async function runUpgradeCommand() {
|
|
890
890
|
const projectDir = findProjectDir();
|
|
@@ -894,133 +894,133 @@ async function runUpgradeCommand() {
|
|
|
894
894
|
process.exit(1);
|
|
895
895
|
}
|
|
896
896
|
|
|
897
|
-
const deployMode = detectProjectDeployMode(projectDir);
|
|
898
|
-
const osChoice = getDetectedOsChoice();
|
|
899
|
-
const botName = detectProjectBotName(projectDir);
|
|
900
|
-
const is9Router = detectProjectUses9Router(projectDir);
|
|
901
|
-
const isMultiBot = detectProjectIsMultiBot(projectDir);
|
|
897
|
+
const deployMode = detectProjectDeployMode(projectDir);
|
|
898
|
+
const osChoice = getDetectedOsChoice();
|
|
899
|
+
const botName = detectProjectBotName(projectDir);
|
|
900
|
+
const is9Router = detectProjectUses9Router(projectDir);
|
|
901
|
+
const isMultiBot = detectProjectIsMultiBot(projectDir);
|
|
902
902
|
|
|
903
903
|
console.log(chalk.cyan('\nRefreshing generated OpenClaw project artifacts...'));
|
|
904
904
|
console.log(chalk.gray(` Project: ${projectDir}`));
|
|
905
905
|
console.log(chalk.gray(` Mode: ${deployMode}`));
|
|
906
906
|
|
|
907
907
|
await writeGeneratedArtifacts(projectDir, buildCliChromeDebugArtifacts());
|
|
908
|
-
await writeGeneratedArtifacts(projectDir, buildCliUninstallArtifacts({
|
|
909
|
-
deployMode,
|
|
910
|
-
osChoice,
|
|
911
|
-
projectDir,
|
|
912
|
-
botName: (deployMode !== 'docker' && osChoice === 'vps')
|
|
913
|
-
? getNativePm2AppName(isMultiBot)
|
|
914
|
-
: botName,
|
|
915
|
-
}));
|
|
908
|
+
await writeGeneratedArtifacts(projectDir, buildCliUninstallArtifacts({
|
|
909
|
+
deployMode,
|
|
910
|
+
osChoice,
|
|
911
|
+
projectDir,
|
|
912
|
+
botName: (deployMode !== 'docker' && osChoice === 'vps')
|
|
913
|
+
? getNativePm2AppName(isMultiBot)
|
|
914
|
+
: botName,
|
|
915
|
+
}));
|
|
916
916
|
await writeGeneratedArtifacts(projectDir, buildCliUpgradeArtifacts());
|
|
917
917
|
|
|
918
|
-
if (deployMode !== 'docker') {
|
|
919
|
-
await writeGeneratedArtifacts(projectDir, buildCliStartBotArtifacts({
|
|
920
|
-
projectDir,
|
|
921
|
-
openclawHome: path.join(projectDir, '.openclaw'),
|
|
922
|
-
is9Router,
|
|
923
|
-
osChoice,
|
|
924
|
-
isMultiBot,
|
|
925
|
-
appName: getNativePm2AppName(isMultiBot),
|
|
926
|
-
isVi: false,
|
|
927
|
-
}));
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
if (is9Router) {
|
|
931
|
-
await writeNative9RouterPatchScript(projectDir);
|
|
932
|
-
await patchProject9RouterOpenClawConfig(projectDir);
|
|
933
|
-
if (deployMode === 'docker') {
|
|
934
|
-
await patchProjectDocker9Router(projectDir);
|
|
935
|
-
} else {
|
|
936
|
-
await writeNative9RouterSyncScript(projectDir);
|
|
937
|
-
try {
|
|
938
|
-
execFileSync(process.execPath, [path.join(projectDir, '.openclaw', 'patch-9router.js')], {
|
|
939
|
-
cwd: projectDir,
|
|
940
|
-
stdio: 'ignore',
|
|
941
|
-
});
|
|
942
|
-
} catch {
|
|
943
|
-
// Best effort: start scripts also retry the patch before launch.
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
console.log(chalk.green('\nUpgrade artifacts refreshed successfully.'));
|
|
918
|
+
if (deployMode !== 'docker') {
|
|
919
|
+
await writeGeneratedArtifacts(projectDir, buildCliStartBotArtifacts({
|
|
920
|
+
projectDir,
|
|
921
|
+
openclawHome: path.join(projectDir, '.openclaw'),
|
|
922
|
+
is9Router,
|
|
923
|
+
osChoice,
|
|
924
|
+
isMultiBot,
|
|
925
|
+
appName: getNativePm2AppName(isMultiBot),
|
|
926
|
+
isVi: false,
|
|
927
|
+
}));
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
if (is9Router) {
|
|
931
|
+
await writeNative9RouterPatchScript(projectDir);
|
|
932
|
+
await patchProject9RouterOpenClawConfig(projectDir);
|
|
933
|
+
if (deployMode === 'docker') {
|
|
934
|
+
await patchProjectDocker9Router(projectDir);
|
|
935
|
+
} else {
|
|
936
|
+
await writeNative9RouterSyncScript(projectDir);
|
|
937
|
+
try {
|
|
938
|
+
execFileSync(process.execPath, [path.join(projectDir, '.openclaw', 'patch-9router.js')], {
|
|
939
|
+
cwd: projectDir,
|
|
940
|
+
stdio: 'ignore',
|
|
941
|
+
});
|
|
942
|
+
} catch {
|
|
943
|
+
// Best effort: start scripts also retry the patch before launch.
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
console.log(chalk.green('\nUpgrade artifacts refreshed successfully.'));
|
|
949
949
|
if (deployMode === 'docker') {
|
|
950
950
|
console.log(chalk.white(` Next: cd ${path.join(projectDir, 'docker', 'openclaw')} && docker compose up -d --build`));
|
|
951
951
|
} else {
|
|
952
|
-
console.log(chalk.white(` Next: run ${process.platform === 'win32' ? '.\\start-bot.bat' : './start-bot.sh'} from ${projectDir}`));
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
function startNative9RouterPm2({ isVi, projectDir, appName, syncScriptPath }) {
|
|
957
|
-
const routerAppName = `${appName}-9router`;
|
|
958
|
-
const syncAppName = `${appName}-9router-sync`;
|
|
959
|
-
const routerLaunch = resolveNative9RouterDesktopLaunch();
|
|
960
|
-
const normalizedProjectDir = projectDir.replace(/\\/g, '/');
|
|
961
|
-
const normalizedSyncScriptPath = syncScriptPath ? syncScriptPath.replace(/\\/g, '/') : '';
|
|
962
|
-
try {
|
|
963
|
-
execSync(`pm2 delete ${routerAppName}`, {
|
|
964
|
-
cwd: projectDir,
|
|
965
|
-
stdio: 'ignore',
|
|
966
|
-
shell: true
|
|
967
|
-
});
|
|
968
|
-
} catch {
|
|
969
|
-
// ignore missing app
|
|
970
|
-
}
|
|
971
|
-
execFileSync('pm2', [
|
|
972
|
-
'start',
|
|
973
|
-
routerLaunch.command,
|
|
974
|
-
'--name',
|
|
975
|
-
routerAppName,
|
|
976
|
-
'--cwd',
|
|
977
|
-
normalizedProjectDir,
|
|
978
|
-
'--interpreter',
|
|
979
|
-
'none',
|
|
980
|
-
'--',
|
|
981
|
-
...routerLaunch.args
|
|
982
|
-
], {
|
|
983
|
-
cwd: projectDir,
|
|
984
|
-
stdio: 'inherit',
|
|
985
|
-
env: { ...process.env, ...routerLaunch.env }
|
|
986
|
-
});
|
|
987
|
-
if (syncScriptPath) {
|
|
988
|
-
try {
|
|
989
|
-
execSync(`pm2 delete ${syncAppName}`, {
|
|
990
|
-
cwd: projectDir,
|
|
991
|
-
stdio: 'ignore',
|
|
992
|
-
shell: true
|
|
993
|
-
});
|
|
994
|
-
} catch {
|
|
995
|
-
// ignore missing app
|
|
996
|
-
}
|
|
997
|
-
try {
|
|
998
|
-
execFileSync('pm2', [
|
|
999
|
-
'start',
|
|
1000
|
-
normalizedSyncScriptPath,
|
|
1001
|
-
'--name',
|
|
1002
|
-
syncAppName,
|
|
1003
|
-
'--cwd',
|
|
1004
|
-
normalizedProjectDir,
|
|
1005
|
-
'--interpreter',
|
|
1006
|
-
process.execPath,
|
|
1007
|
-
'--no-autorestart',
|
|
1008
|
-
], {
|
|
1009
|
-
cwd: projectDir,
|
|
1010
|
-
stdio: 'inherit',
|
|
1011
|
-
env: process.env
|
|
1012
|
-
});
|
|
1013
|
-
} catch (syncErr) {
|
|
1014
|
-
console.log(chalk.yellow(isVi
|
|
1015
|
-
? `\n⚠️ Khong the tu dong khoi dong sync script qua PM2.`
|
|
1016
|
-
: `\n⚠️ Could not auto-start 9router sync script via PM2.`));
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
runPm2Save({ projectDir, isVi });
|
|
1020
|
-
console.log(chalk.green(`\n✅ ${isVi ? '9Router da duoc khoi dong qua PM2.' : '9Router is running via PM2.'}`));
|
|
1021
|
-
console.log(chalk.gray(isVi ? ` Xem log: pm2 logs ${routerAppName}` : ` View logs: pm2 logs ${routerAppName}`));
|
|
1022
|
-
}
|
|
1023
|
-
|
|
952
|
+
console.log(chalk.white(` Next: run ${process.platform === 'win32' ? '.\\start-bot.bat' : './start-bot.sh'} from ${projectDir}`));
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
function startNative9RouterPm2({ isVi, projectDir, appName, syncScriptPath }) {
|
|
957
|
+
const routerAppName = `${appName}-9router`;
|
|
958
|
+
const syncAppName = `${appName}-9router-sync`;
|
|
959
|
+
const routerLaunch = resolveNative9RouterDesktopLaunch();
|
|
960
|
+
const normalizedProjectDir = projectDir.replace(/\\/g, '/');
|
|
961
|
+
const normalizedSyncScriptPath = syncScriptPath ? syncScriptPath.replace(/\\/g, '/') : '';
|
|
962
|
+
try {
|
|
963
|
+
execSync(`pm2 delete ${routerAppName}`, {
|
|
964
|
+
cwd: projectDir,
|
|
965
|
+
stdio: 'ignore',
|
|
966
|
+
shell: true
|
|
967
|
+
});
|
|
968
|
+
} catch {
|
|
969
|
+
// ignore missing app
|
|
970
|
+
}
|
|
971
|
+
execFileSync('pm2', [
|
|
972
|
+
'start',
|
|
973
|
+
routerLaunch.command,
|
|
974
|
+
'--name',
|
|
975
|
+
routerAppName,
|
|
976
|
+
'--cwd',
|
|
977
|
+
normalizedProjectDir,
|
|
978
|
+
'--interpreter',
|
|
979
|
+
'none',
|
|
980
|
+
'--',
|
|
981
|
+
...routerLaunch.args
|
|
982
|
+
], {
|
|
983
|
+
cwd: projectDir,
|
|
984
|
+
stdio: 'inherit',
|
|
985
|
+
env: { ...process.env, ...routerLaunch.env }
|
|
986
|
+
});
|
|
987
|
+
if (syncScriptPath) {
|
|
988
|
+
try {
|
|
989
|
+
execSync(`pm2 delete ${syncAppName}`, {
|
|
990
|
+
cwd: projectDir,
|
|
991
|
+
stdio: 'ignore',
|
|
992
|
+
shell: true
|
|
993
|
+
});
|
|
994
|
+
} catch {
|
|
995
|
+
// ignore missing app
|
|
996
|
+
}
|
|
997
|
+
try {
|
|
998
|
+
execFileSync('pm2', [
|
|
999
|
+
'start',
|
|
1000
|
+
normalizedSyncScriptPath,
|
|
1001
|
+
'--name',
|
|
1002
|
+
syncAppName,
|
|
1003
|
+
'--cwd',
|
|
1004
|
+
normalizedProjectDir,
|
|
1005
|
+
'--interpreter',
|
|
1006
|
+
process.execPath,
|
|
1007
|
+
'--no-autorestart',
|
|
1008
|
+
], {
|
|
1009
|
+
cwd: projectDir,
|
|
1010
|
+
stdio: 'inherit',
|
|
1011
|
+
env: process.env
|
|
1012
|
+
});
|
|
1013
|
+
} catch (syncErr) {
|
|
1014
|
+
console.log(chalk.yellow(isVi
|
|
1015
|
+
? `\n⚠️ Khong the tu dong khoi dong sync script qua PM2.`
|
|
1016
|
+
: `\n⚠️ Could not auto-start 9router sync script via PM2.`));
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
runPm2Save({ projectDir, isVi });
|
|
1020
|
+
console.log(chalk.green(`\n✅ ${isVi ? '9Router da duoc khoi dong qua PM2.' : '9Router is running via PM2.'}`));
|
|
1021
|
+
console.log(chalk.gray(isVi ? ` Xem log: pm2 logs ${routerAppName}` : ` View logs: pm2 logs ${routerAppName}`));
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
1024
|
async function ensureProjectRuntimeDirs(projectDir, isVi) {
|
|
1025
1025
|
await fs.ensureDir(path.join(projectDir, '.openclaw'));
|
|
1026
1026
|
await fs.ensureDir(getProject9RouterDataDir(projectDir));
|
|
@@ -1070,9 +1070,9 @@ function providerSupportsMemoryEmbeddings(providerKey) {
|
|
|
1070
1070
|
return !!PROVIDERS[providerKey]?.supportsEmbeddings;
|
|
1071
1071
|
}
|
|
1072
1072
|
|
|
1073
|
-
function getCliSkillChoices({ providerKey, isVi }) {
|
|
1074
|
-
const memoryRecommended = providerSupportsMemoryEmbeddings(providerKey);
|
|
1075
|
-
return SKILLS
|
|
1073
|
+
function getCliSkillChoices({ providerKey, isVi }) {
|
|
1074
|
+
const memoryRecommended = providerSupportsMemoryEmbeddings(providerKey);
|
|
1075
|
+
return SKILLS
|
|
1076
1076
|
.filter((skill) => skill.value !== 'memory' || providerSupportsMemoryEmbeddings(providerKey) || skill.id === 'memory')
|
|
1077
1077
|
.map((skill) => {
|
|
1078
1078
|
const value = skill.value || skill.id;
|
|
@@ -1087,677 +1087,677 @@ function getCliSkillChoices({ providerKey, isVi }) {
|
|
|
1087
1087
|
value,
|
|
1088
1088
|
checked: value === 'browser' || value === 'scheduler' || (value === 'memory' && memoryRecommended),
|
|
1089
1089
|
};
|
|
1090
|
-
});
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
const CLI_BACK = '__openclaw_cli_back__';
|
|
1094
|
-
|
|
1095
|
-
function getBackChoice(isVi) {
|
|
1096
|
-
return {
|
|
1097
|
-
name: isVi ? '← Quay lại' : '← Back',
|
|
1098
|
-
value: CLI_BACK,
|
|
1099
|
-
};
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
function withBackHint(message, isVi) {
|
|
1103
|
-
return `${message} ${isVi ? '(gõ "back" để quay lại)' : '(type "back" to go back)'}`;
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
async function selectWithBack({ message, choices, defaultValue, allowBack = false, isVi = true }) {
|
|
1107
|
-
const finalChoices = allowBack ? [...choices, getBackChoice(isVi)] : choices;
|
|
1108
|
-
return select({
|
|
1109
|
-
message,
|
|
1110
|
-
choices: finalChoices,
|
|
1111
|
-
...(defaultValue ? { default: defaultValue } : {}),
|
|
1112
|
-
});
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
async function inputWithBack({ message, defaultValue = '', required = false, allowBack = false, isVi = true }) {
|
|
1116
|
-
const value = await input({
|
|
1117
|
-
message: allowBack ? withBackHint(message, isVi) : message,
|
|
1118
|
-
default: defaultValue,
|
|
1119
|
-
required,
|
|
1120
|
-
});
|
|
1121
|
-
if (allowBack && String(value || '').trim().toLowerCase() === 'back') {
|
|
1122
|
-
return CLI_BACK;
|
|
1123
|
-
}
|
|
1124
|
-
return value;
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
async function checkboxWithBack({ message, choices, isVi = true, allowBack = false }) {
|
|
1128
|
-
const finalChoices = allowBack ? [...choices, getBackChoice(isVi)] : choices;
|
|
1129
|
-
const value = await checkbox({
|
|
1130
|
-
message,
|
|
1131
|
-
choices: finalChoices,
|
|
1132
|
-
});
|
|
1133
|
-
return allowBack && value.includes(CLI_BACK) ? CLI_BACK : value;
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
async function collectBotSetupStep({
|
|
1137
|
-
isVi,
|
|
1138
|
-
channelKey,
|
|
1139
|
-
channel,
|
|
1140
|
-
existingBots = [],
|
|
1141
|
-
existingBotCount = 1,
|
|
1142
|
-
existingGroupId = '',
|
|
1143
|
-
}) {
|
|
1144
|
-
let botCount = channelKey === 'telegram' ? existingBotCount : 1;
|
|
1145
|
-
let groupId = existingGroupId;
|
|
1146
|
-
const bots = [];
|
|
1147
|
-
|
|
1148
|
-
if (channelKey === 'telegram') {
|
|
1149
|
-
const botCountValue = await selectWithBack({
|
|
1150
|
-
message: isVi ? 'Bạn muốn cài bao nhiêu Telegram bot?' : 'How many Telegram bots do you want to deploy?',
|
|
1151
|
-
choices: [
|
|
1152
|
-
{ name: '1 bot (single)', value: '1' },
|
|
1153
|
-
{ name: '2 bots (Department Room)', value: '2' },
|
|
1154
|
-
{ name: '3 bots', value: '3' },
|
|
1155
|
-
{ name: '4 bots', value: '4' },
|
|
1156
|
-
{ name: '5 bots', value: '5' },
|
|
1157
|
-
],
|
|
1158
|
-
defaultValue: String(existingBotCount || 1),
|
|
1159
|
-
allowBack: true,
|
|
1160
|
-
isVi,
|
|
1161
|
-
});
|
|
1162
|
-
if (botCountValue === CLI_BACK) {
|
|
1163
|
-
return { back: true };
|
|
1164
|
-
}
|
|
1165
|
-
botCount = parseInt(botCountValue, 10);
|
|
1166
|
-
|
|
1167
|
-
if (botCount > 1) {
|
|
1168
|
-
const groupOption = await selectWithBack({
|
|
1169
|
-
message: isVi ? 'Bạn có sẵn Telegram Group chưa?' : 'Do you already have a Telegram Group?',
|
|
1170
|
-
choices: [
|
|
1171
|
-
{ 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' },
|
|
1172
|
-
{ name: isVi ? '🔗 Đã có group — nhập Group ID ngay' : '🔗 Already have a group — enter Group ID now', value: 'existing' }
|
|
1173
|
-
],
|
|
1174
|
-
defaultValue: groupId ? 'existing' : 'create',
|
|
1175
|
-
allowBack: true,
|
|
1176
|
-
isVi,
|
|
1177
|
-
});
|
|
1178
|
-
if (groupOption === CLI_BACK) {
|
|
1179
|
-
return { back: true };
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
if (groupOption === 'existing') {
|
|
1183
|
-
console.log(chalk.dim(isVi
|
|
1184
|
-
? '\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'
|
|
1185
|
-
: '\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'));
|
|
1186
|
-
const nextGroupId = await inputWithBack({
|
|
1187
|
-
message: isVi ? 'Telegram Group ID (VD: -1001234567890):' : 'Telegram Group ID (e.g. -1001234567890):',
|
|
1188
|
-
defaultValue: groupId,
|
|
1189
|
-
allowBack: true,
|
|
1190
|
-
isVi,
|
|
1191
|
-
});
|
|
1192
|
-
if (nextGroupId === CLI_BACK) {
|
|
1193
|
-
return { back: true };
|
|
1194
|
-
}
|
|
1195
|
-
groupId = nextGroupId;
|
|
1196
|
-
} else {
|
|
1197
|
-
groupId = '';
|
|
1198
|
-
}
|
|
1199
|
-
} else {
|
|
1200
|
-
groupId = '';
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
for (let i = 0; i < botCount; i++) {
|
|
1204
|
-
console.log(chalk.bold(`\n${isVi ? `─── Bot ${i + 1} / ${botCount} ───` : `─── Bot ${i + 1} / ${botCount} ───`}`));
|
|
1205
|
-
const defaults = existingBots[i] || {};
|
|
1206
|
-
const fields = [
|
|
1207
|
-
{
|
|
1208
|
-
key: 'name',
|
|
1209
|
-
message: isVi ? `Tên Bot ${i + 1}:` : `Bot ${i + 1} name:`,
|
|
1210
|
-
defaultValue: defaults.name || `Bot ${i + 1}`,
|
|
1211
|
-
required: true,
|
|
1212
|
-
},
|
|
1213
|
-
{
|
|
1214
|
-
key: 'slashCmd',
|
|
1215
|
-
message: isVi ? `Slash command (VD: /bot${i + 1}):` : `Slash command (e.g. /bot${i + 1}):`,
|
|
1216
|
-
defaultValue: defaults.slashCmd || `/bot${i + 1}`,
|
|
1217
|
-
required: true,
|
|
1218
|
-
},
|
|
1219
|
-
{
|
|
1220
|
-
key: 'desc',
|
|
1221
|
-
message: isVi ? `Mô tả Bot ${i + 1} (VD: Trợ lý AI cá nhân):` : `Bot ${i + 1} description (e.g. Personal AI assistant):`,
|
|
1222
|
-
defaultValue: defaults.desc || (isVi ? 'Trợ lý AI cá nhân' : 'Personal AI assistant'),
|
|
1223
|
-
required: true,
|
|
1224
|
-
},
|
|
1225
|
-
{
|
|
1226
|
-
key: 'persona',
|
|
1227
|
-
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):`,
|
|
1228
|
-
defaultValue: defaults.persona || '',
|
|
1229
|
-
required: false,
|
|
1230
|
-
},
|
|
1231
|
-
{
|
|
1232
|
-
key: 'token',
|
|
1233
|
-
message: isVi ? 'Bot Token (từ @BotFather):' : 'Bot Token (from @BotFather):',
|
|
1234
|
-
defaultValue: defaults.token || '',
|
|
1235
|
-
required: true,
|
|
1236
|
-
},
|
|
1237
|
-
];
|
|
1238
|
-
|
|
1239
|
-
const draft = { ...defaults };
|
|
1240
|
-
let fieldIndex = 0;
|
|
1241
|
-
while (fieldIndex < fields.length) {
|
|
1242
|
-
const field = fields[fieldIndex];
|
|
1243
|
-
const value = await inputWithBack({
|
|
1244
|
-
message: field.message,
|
|
1245
|
-
defaultValue: draft[field.key] || field.defaultValue,
|
|
1246
|
-
required: field.required,
|
|
1247
|
-
allowBack: true,
|
|
1248
|
-
isVi,
|
|
1249
|
-
});
|
|
1250
|
-
if (value === CLI_BACK) {
|
|
1251
|
-
if (fieldIndex > 0) {
|
|
1252
|
-
fieldIndex--;
|
|
1253
|
-
continue;
|
|
1254
|
-
}
|
|
1255
|
-
return { back: true };
|
|
1256
|
-
}
|
|
1257
|
-
draft[field.key] = value;
|
|
1258
|
-
fieldIndex++;
|
|
1259
|
-
}
|
|
1260
|
-
bots.push(draft);
|
|
1261
|
-
}
|
|
1262
|
-
} else if (channelKey !== 'zalo-personal') {
|
|
1263
|
-
const defaults = existingBots[0] || {};
|
|
1264
|
-
const fields = [
|
|
1265
|
-
{
|
|
1266
|
-
key: 'name',
|
|
1267
|
-
message: isVi ? 'Tên Bot:' : 'Bot Name:',
|
|
1268
|
-
defaultValue: defaults.name || 'Chat Bot',
|
|
1269
|
-
required: true,
|
|
1270
|
-
},
|
|
1271
|
-
{
|
|
1272
|
-
key: 'desc',
|
|
1273
|
-
message: isVi ? 'Mô tả Bot:' : 'Bot Description:',
|
|
1274
|
-
defaultValue: defaults.desc || (isVi ? 'Trợ lý AI cá nhân' : 'Personal AI assistant'),
|
|
1275
|
-
required: true,
|
|
1276
|
-
},
|
|
1277
|
-
{
|
|
1278
|
-
key: 'persona',
|
|
1279
|
-
message: isVi ? 'Tính cách & quy tắc (VD: gọn gàng, thân thiện):' : 'Persona & rules (e.g. concise, friendly):',
|
|
1280
|
-
defaultValue: defaults.persona || '',
|
|
1281
|
-
required: false,
|
|
1282
|
-
},
|
|
1283
|
-
{
|
|
1284
|
-
key: 'token',
|
|
1285
|
-
message: isVi ? `Nhập ${channel.name} Token:` : `Enter ${channel.name} Token:`,
|
|
1286
|
-
defaultValue: defaults.token || '',
|
|
1287
|
-
required: true,
|
|
1288
|
-
},
|
|
1289
|
-
];
|
|
1290
|
-
const draft = { ...defaults, slashCmd: '' };
|
|
1291
|
-
let fieldIndex = 0;
|
|
1292
|
-
while (fieldIndex < fields.length) {
|
|
1293
|
-
const field = fields[fieldIndex];
|
|
1294
|
-
const value = await inputWithBack({
|
|
1295
|
-
message: field.message,
|
|
1296
|
-
defaultValue: draft[field.key] || field.defaultValue,
|
|
1297
|
-
required: field.required,
|
|
1298
|
-
allowBack: true,
|
|
1299
|
-
isVi,
|
|
1300
|
-
});
|
|
1301
|
-
if (value === CLI_BACK) {
|
|
1302
|
-
if (fieldIndex > 0) {
|
|
1303
|
-
fieldIndex--;
|
|
1304
|
-
continue;
|
|
1305
|
-
}
|
|
1306
|
-
return { back: true };
|
|
1307
|
-
}
|
|
1308
|
-
draft[field.key] = value;
|
|
1309
|
-
fieldIndex++;
|
|
1310
|
-
}
|
|
1311
|
-
bots.push(draft);
|
|
1312
|
-
} else {
|
|
1313
|
-
bots.push({ name: 'Bot', slashCmd: '', desc: '', persona: '', token: '' });
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
return {
|
|
1317
|
-
back: false,
|
|
1318
|
-
botCount,
|
|
1319
|
-
groupId,
|
|
1320
|
-
bots,
|
|
1321
|
-
botToken: bots[0]?.token || '',
|
|
1322
|
-
};
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
|
-
async function collectBotSetupStepWithGroupBack(options) {
|
|
1326
|
-
const {
|
|
1327
|
-
isVi,
|
|
1328
|
-
channelKey,
|
|
1329
|
-
channel,
|
|
1330
|
-
existingBots = [],
|
|
1331
|
-
existingBotCount = 1,
|
|
1332
|
-
existingGroupId = '',
|
|
1333
|
-
} = options;
|
|
1334
|
-
|
|
1335
|
-
let botCount = channelKey === 'telegram' ? existingBotCount : 1;
|
|
1336
|
-
let groupId = existingGroupId;
|
|
1337
|
-
const bots = [];
|
|
1338
|
-
|
|
1339
|
-
if (channelKey === 'telegram') {
|
|
1340
|
-
const botCountValue = await selectWithBack({
|
|
1341
|
-
message: isVi ? 'Bạn muốn cài bao nhiêu Telegram bot?' : 'How many Telegram bots do you want to deploy?',
|
|
1342
|
-
choices: [
|
|
1343
|
-
{ name: '1 bot (single)', value: '1' },
|
|
1344
|
-
{ name: '2 bots (Department Room)', value: '2' },
|
|
1345
|
-
{ name: '3 bots', value: '3' },
|
|
1346
|
-
{ name: '4 bots', value: '4' },
|
|
1347
|
-
{ name: '5 bots', value: '5' },
|
|
1348
|
-
],
|
|
1349
|
-
defaultValue: String(existingBotCount || 1),
|
|
1350
|
-
allowBack: true,
|
|
1351
|
-
isVi,
|
|
1352
|
-
});
|
|
1353
|
-
if (botCountValue === CLI_BACK) {
|
|
1354
|
-
return { back: true };
|
|
1355
|
-
}
|
|
1356
|
-
botCount = parseInt(botCountValue, 10);
|
|
1357
|
-
|
|
1358
|
-
if (botCount > 1) {
|
|
1359
|
-
while (true) {
|
|
1360
|
-
const groupOption = await selectWithBack({
|
|
1361
|
-
message: isVi ? 'Bạn có sẵn Telegram Group chưa?' : 'Do you already have a Telegram Group?',
|
|
1362
|
-
choices: [
|
|
1363
|
-
{ 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' },
|
|
1364
|
-
{ name: isVi ? '🔗 Đã có group — nhập Group ID ngay' : '🔗 Already have a group — enter Group ID now', value: 'existing' }
|
|
1365
|
-
],
|
|
1366
|
-
defaultValue: groupId ? 'existing' : 'create',
|
|
1367
|
-
allowBack: true,
|
|
1368
|
-
isVi,
|
|
1369
|
-
});
|
|
1370
|
-
if (groupOption === CLI_BACK) {
|
|
1371
|
-
return { back: true };
|
|
1372
|
-
}
|
|
1373
|
-
|
|
1374
|
-
if (groupOption === 'existing') {
|
|
1375
|
-
console.log(chalk.dim(isVi
|
|
1376
|
-
? '\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'
|
|
1377
|
-
: '\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'));
|
|
1378
|
-
const nextGroupId = await inputWithBack({
|
|
1379
|
-
message: isVi ? 'Telegram Group ID (VD: -1001234567890):' : 'Telegram Group ID (e.g. -1001234567890):',
|
|
1380
|
-
defaultValue: groupId,
|
|
1381
|
-
allowBack: true,
|
|
1382
|
-
isVi,
|
|
1383
|
-
});
|
|
1384
|
-
if (nextGroupId === CLI_BACK) {
|
|
1385
|
-
continue;
|
|
1386
|
-
}
|
|
1387
|
-
groupId = nextGroupId;
|
|
1388
|
-
break;
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
|
-
groupId = '';
|
|
1392
|
-
break;
|
|
1393
|
-
}
|
|
1394
|
-
} else {
|
|
1395
|
-
groupId = '';
|
|
1396
|
-
}
|
|
1397
|
-
|
|
1398
|
-
for (let i = 0; i < botCount; i++) {
|
|
1399
|
-
console.log(chalk.bold(`\n${isVi ? `─── Bot ${i + 1} / ${botCount} ───` : `─── Bot ${i + 1} / ${botCount} ───`}`));
|
|
1400
|
-
const defaults = existingBots[i] || {};
|
|
1401
|
-
const fields = [
|
|
1402
|
-
{
|
|
1403
|
-
key: 'name',
|
|
1404
|
-
message: isVi ? `Tên Bot ${i + 1}:` : `Bot ${i + 1} name:`,
|
|
1405
|
-
defaultValue: defaults.name || `Bot ${i + 1}`,
|
|
1406
|
-
required: true,
|
|
1407
|
-
},
|
|
1408
|
-
{
|
|
1409
|
-
key: 'slashCmd',
|
|
1410
|
-
message: isVi ? `Slash command (VD: /bot${i + 1}):` : `Slash command (e.g. /bot${i + 1}):`,
|
|
1411
|
-
defaultValue: defaults.slashCmd || `/bot${i + 1}`,
|
|
1412
|
-
required: true,
|
|
1413
|
-
},
|
|
1414
|
-
{
|
|
1415
|
-
key: 'desc',
|
|
1416
|
-
message: isVi ? `Mô tả Bot ${i + 1} (VD: Trợ lý AI cá nhân):` : `Bot ${i + 1} description (e.g. Personal AI assistant):`,
|
|
1417
|
-
defaultValue: defaults.desc || (isVi ? 'Trợ lý AI cá nhân' : 'Personal AI assistant'),
|
|
1418
|
-
required: true,
|
|
1419
|
-
},
|
|
1420
|
-
{
|
|
1421
|
-
key: 'persona',
|
|
1422
|
-
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):`,
|
|
1423
|
-
defaultValue: defaults.persona || '',
|
|
1424
|
-
required: false,
|
|
1425
|
-
},
|
|
1426
|
-
{
|
|
1427
|
-
key: 'token',
|
|
1428
|
-
message: isVi ? 'Bot Token (từ @BotFather):' : 'Bot Token (from @BotFather):',
|
|
1429
|
-
defaultValue: defaults.token || '',
|
|
1430
|
-
required: true,
|
|
1431
|
-
},
|
|
1432
|
-
];
|
|
1433
|
-
|
|
1434
|
-
const draft = { ...defaults };
|
|
1435
|
-
let fieldIndex = 0;
|
|
1436
|
-
while (fieldIndex < fields.length) {
|
|
1437
|
-
const field = fields[fieldIndex];
|
|
1438
|
-
const value = await inputWithBack({
|
|
1439
|
-
message: field.message,
|
|
1440
|
-
defaultValue: draft[field.key] || field.defaultValue,
|
|
1441
|
-
required: field.required,
|
|
1442
|
-
allowBack: true,
|
|
1443
|
-
isVi,
|
|
1444
|
-
});
|
|
1445
|
-
if (value === CLI_BACK) {
|
|
1446
|
-
if (fieldIndex > 0) {
|
|
1447
|
-
fieldIndex--;
|
|
1448
|
-
continue;
|
|
1449
|
-
}
|
|
1450
|
-
return { back: true };
|
|
1451
|
-
}
|
|
1452
|
-
draft[field.key] = value;
|
|
1453
|
-
fieldIndex++;
|
|
1454
|
-
}
|
|
1455
|
-
bots.push(draft);
|
|
1456
|
-
}
|
|
1457
|
-
} else if (channelKey !== 'zalo-personal') {
|
|
1458
|
-
const defaults = existingBots[0] || {};
|
|
1459
|
-
const fields = [
|
|
1460
|
-
{
|
|
1461
|
-
key: 'name',
|
|
1462
|
-
message: isVi ? 'Tên Bot:' : 'Bot Name:',
|
|
1463
|
-
defaultValue: defaults.name || 'Chat Bot',
|
|
1464
|
-
required: true,
|
|
1465
|
-
},
|
|
1466
|
-
{
|
|
1467
|
-
key: 'desc',
|
|
1468
|
-
message: isVi ? 'Mô tả Bot:' : 'Bot Description:',
|
|
1469
|
-
defaultValue: defaults.desc || (isVi ? 'Trợ lý AI cá nhân' : 'Personal AI assistant'),
|
|
1470
|
-
required: true,
|
|
1471
|
-
},
|
|
1472
|
-
{
|
|
1473
|
-
key: 'persona',
|
|
1474
|
-
message: isVi ? 'Tính cách & quy tắc (VD: gọn gàng, thân thiện):' : 'Persona & rules (e.g. concise, friendly):',
|
|
1475
|
-
defaultValue: defaults.persona || '',
|
|
1476
|
-
required: false,
|
|
1477
|
-
},
|
|
1478
|
-
{
|
|
1479
|
-
key: 'token',
|
|
1480
|
-
message: isVi ? `Nhập ${channel.name} Token:` : `Enter ${channel.name} Token:`,
|
|
1481
|
-
defaultValue: defaults.token || '',
|
|
1482
|
-
required: true,
|
|
1483
|
-
},
|
|
1484
|
-
];
|
|
1485
|
-
const draft = { ...defaults, slashCmd: '' };
|
|
1486
|
-
let fieldIndex = 0;
|
|
1487
|
-
while (fieldIndex < fields.length) {
|
|
1488
|
-
const field = fields[fieldIndex];
|
|
1489
|
-
const value = await inputWithBack({
|
|
1490
|
-
message: field.message,
|
|
1491
|
-
defaultValue: draft[field.key] || field.defaultValue,
|
|
1492
|
-
required: field.required,
|
|
1493
|
-
allowBack: true,
|
|
1494
|
-
isVi,
|
|
1495
|
-
});
|
|
1496
|
-
if (value === CLI_BACK) {
|
|
1497
|
-
if (fieldIndex > 0) {
|
|
1498
|
-
fieldIndex--;
|
|
1499
|
-
continue;
|
|
1500
|
-
}
|
|
1501
|
-
return { back: true };
|
|
1502
|
-
}
|
|
1503
|
-
draft[field.key] = value;
|
|
1504
|
-
fieldIndex++;
|
|
1505
|
-
}
|
|
1506
|
-
bots.push(draft);
|
|
1507
|
-
} else {
|
|
1508
|
-
bots.push({ name: 'Bot', slashCmd: '', desc: '', persona: '', token: '' });
|
|
1509
|
-
}
|
|
1510
|
-
|
|
1511
|
-
return {
|
|
1512
|
-
back: false,
|
|
1513
|
-
botCount,
|
|
1514
|
-
groupId,
|
|
1515
|
-
bots,
|
|
1516
|
-
botToken: bots[0]?.token || '',
|
|
1517
|
-
};
|
|
1518
|
-
}
|
|
1519
|
-
|
|
1520
|
-
async function collectProviderStep({
|
|
1521
|
-
isVi,
|
|
1522
|
-
existingProviderKey = '',
|
|
1523
|
-
existingProviderKeyVal = '',
|
|
1524
|
-
existingOllamaModel = 'gemma4:e2b',
|
|
1525
|
-
}) {
|
|
1526
|
-
const providerKey = await selectWithBack({
|
|
1527
|
-
message: isVi ? 'Chọn AI Provider:' : 'Select AI Provider:',
|
|
1528
|
-
choices: Object.entries(PROVIDERS).map(([k, v]) => ({ name: `${v.icon} ${v.name}`, value: k })),
|
|
1529
|
-
defaultValue: existingProviderKey || undefined,
|
|
1530
|
-
allowBack: true,
|
|
1531
|
-
isVi,
|
|
1532
|
-
});
|
|
1533
|
-
if (providerKey === CLI_BACK) {
|
|
1534
|
-
return { back: true };
|
|
1535
|
-
}
|
|
1536
|
-
const provider = PROVIDERS[providerKey];
|
|
1537
|
-
|
|
1538
|
-
let providerKeyVal = existingProviderKey === providerKey ? existingProviderKeyVal : '';
|
|
1539
|
-
if (!provider.isProxy && !provider.isLocal) {
|
|
1540
|
-
const keyValue = await inputWithBack({
|
|
1541
|
-
message: isVi ? `Nhập ${provider.envKey}:` : `Enter ${provider.envKey}:`,
|
|
1542
|
-
defaultValue: providerKeyVal,
|
|
1543
|
-
required: true,
|
|
1544
|
-
allowBack: true,
|
|
1545
|
-
isVi,
|
|
1546
|
-
});
|
|
1547
|
-
if (keyValue === CLI_BACK) {
|
|
1548
|
-
return { back: true };
|
|
1549
|
-
}
|
|
1550
|
-
providerKeyVal = keyValue;
|
|
1551
|
-
}
|
|
1552
|
-
|
|
1553
|
-
let selectedOllamaModel = existingProviderKey === 'ollama' ? existingOllamaModel : 'gemma4:e2b';
|
|
1554
|
-
if (providerKey === 'ollama') {
|
|
1555
|
-
console.log(chalk.yellow(isVi
|
|
1556
|
-
? '\n💡 Gemma 4 (02/04/2026) — chọn kích thước phù hợp với RAM máy bạn:'
|
|
1557
|
-
: '\n💡 Gemma 4 (April 2, 2026) — pick a size that fits your RAM:'));
|
|
1558
|
-
const modelValue = await selectWithBack({
|
|
1559
|
-
message: isVi ? 'Chọn model Ollama:' : 'Select Ollama model:',
|
|
1560
|
-
choices: [
|
|
1561
|
-
{
|
|
1562
|
-
name: isVi
|
|
1563
|
-
? '🟢 gemma4:e2b — Nhẹ nhất (~4-6 GB RAM) — Laptop / test nhanh ★ Khuyên dùng'
|
|
1564
|
-
: '🟢 gemma4:e2b — Lightest (~4-6 GB RAM) — Laptop / fastest test ★ Recommended',
|
|
1565
|
-
value: 'gemma4:e2b'
|
|
1566
|
-
},
|
|
1567
|
-
{
|
|
1568
|
-
name: isVi
|
|
1569
|
-
? '🟡 gemma4:e4b — Cân bằng (~8-10 GB RAM) — Dùng hằng ngày'
|
|
1570
|
-
: '🟡 gemma4:e4b — Balanced (~8-10 GB RAM) — Daily use',
|
|
1571
|
-
value: 'gemma4:e4b'
|
|
1572
|
-
},
|
|
1573
|
-
{
|
|
1574
|
-
name: isVi
|
|
1575
|
-
? '🟠 gemma4:26b — Mạnh (~18-24 GB RAM/VRAM) — Máy mạnh'
|
|
1576
|
-
: '🟠 gemma4:26b — Powerful (~18-24 GB RAM/VRAM) — High-end machine',
|
|
1577
|
-
value: 'gemma4:26b'
|
|
1578
|
-
},
|
|
1579
|
-
{
|
|
1580
|
-
name: isVi
|
|
1581
|
-
? '🔴 gemma4:31b — Mạnh nhất (~24+ GB RAM/VRAM) — GPU workstation'
|
|
1582
|
-
: '🔴 gemma4:31b — Most powerful (~24+ GB RAM/VRAM) — GPU workstation',
|
|
1583
|
-
value: 'gemma4:31b'
|
|
1584
|
-
},
|
|
1585
|
-
],
|
|
1586
|
-
defaultValue: selectedOllamaModel,
|
|
1587
|
-
allowBack: true,
|
|
1588
|
-
isVi,
|
|
1589
|
-
});
|
|
1590
|
-
if (modelValue === CLI_BACK) {
|
|
1591
|
-
return { back: true };
|
|
1592
|
-
}
|
|
1593
|
-
selectedOllamaModel = modelValue;
|
|
1594
|
-
}
|
|
1595
|
-
|
|
1596
|
-
return {
|
|
1597
|
-
back: false,
|
|
1598
|
-
providerKey,
|
|
1599
|
-
provider,
|
|
1600
|
-
providerKeyVal,
|
|
1601
|
-
selectedOllamaModel,
|
|
1602
|
-
};
|
|
1603
|
-
}
|
|
1604
|
-
|
|
1605
|
-
async function collectSkillsStep({
|
|
1606
|
-
isVi,
|
|
1607
|
-
providerKey,
|
|
1608
|
-
existingSelectedSkills = [],
|
|
1609
|
-
existingBrowserMode = 'server',
|
|
1610
|
-
existingTtsOpenaiKey = '',
|
|
1611
|
-
existingTtsElevenKey = '',
|
|
1612
|
-
existingSmtpHost = 'smtp.gmail.com',
|
|
1613
|
-
existingSmtpPort = '587',
|
|
1614
|
-
existingSmtpUser = '',
|
|
1615
|
-
existingSmtpPass = '',
|
|
1616
|
-
}) {
|
|
1617
|
-
const selectedSkills = await checkboxWithBack({
|
|
1618
|
-
message: isVi ? 'Bật tính năng bổ sung (Space để chọn):' : 'Enable extra skills (Space to select):',
|
|
1619
|
-
choices: getCliSkillChoices({ providerKey, isVi }).map((choice) => ({
|
|
1620
|
-
...choice,
|
|
1621
|
-
checked: existingSelectedSkills.includes(choice.value),
|
|
1622
|
-
})),
|
|
1623
|
-
allowBack: true,
|
|
1624
|
-
isVi,
|
|
1625
|
-
});
|
|
1626
|
-
if (selectedSkills === CLI_BACK) {
|
|
1627
|
-
return { back: true };
|
|
1628
|
-
}
|
|
1629
|
-
|
|
1630
|
-
let browserMode = existingBrowserMode;
|
|
1631
|
-
if (selectedSkills.includes('browser')) {
|
|
1632
|
-
const isLinux = process.platform === 'linux';
|
|
1633
|
-
const browserValue = await selectWithBack({
|
|
1634
|
-
message: isVi ? 'Chế độ Browser Automation:' : 'Browser Automation mode:',
|
|
1635
|
-
choices: [
|
|
1636
|
-
{
|
|
1637
|
-
name: isVi
|
|
1638
|
-
? '🖥️ Dùng Chrome trên máy tính (Windows/Mac — Bypass Cloudflare tốt hơn)'
|
|
1639
|
-
: '🖥️ Use Host Chrome (Windows/Mac — Better Cloudflare bypass)',
|
|
1640
|
-
value: 'desktop'
|
|
1641
|
-
},
|
|
1642
|
-
{
|
|
1643
|
-
name: isVi
|
|
1644
|
-
? '🐳 Headless Chromium trong Docker (Ubuntu Server / VPS — không cần GUI)'
|
|
1645
|
-
: '🐳 Headless Chromium inside Docker (Ubuntu Server / VPS — No GUI)',
|
|
1646
|
-
value: 'server'
|
|
1647
|
-
}
|
|
1648
|
-
],
|
|
1649
|
-
defaultValue: browserMode || (isLinux ? 'server' : 'desktop'),
|
|
1650
|
-
allowBack: true,
|
|
1651
|
-
isVi,
|
|
1652
|
-
});
|
|
1653
|
-
if (browserValue === CLI_BACK) {
|
|
1654
|
-
return { back: true };
|
|
1655
|
-
}
|
|
1656
|
-
browserMode = browserValue;
|
|
1657
|
-
} else {
|
|
1658
|
-
browserMode = 'server';
|
|
1659
|
-
}
|
|
1660
|
-
|
|
1661
|
-
let ttsOpenaiKey = existingTtsOpenaiKey;
|
|
1662
|
-
let ttsElevenKey = existingTtsElevenKey;
|
|
1663
|
-
if (selectedSkills.includes('tts')) {
|
|
1664
|
-
const openaiKey = await inputWithBack({
|
|
1665
|
-
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):',
|
|
1666
|
-
defaultValue: ttsOpenaiKey,
|
|
1667
|
-
allowBack: true,
|
|
1668
|
-
isVi,
|
|
1669
|
-
});
|
|
1670
|
-
if (openaiKey === CLI_BACK) {
|
|
1671
|
-
return { back: true };
|
|
1672
|
-
}
|
|
1673
|
-
ttsOpenaiKey = openaiKey;
|
|
1674
|
-
|
|
1675
|
-
const elevenKey = await inputWithBack({
|
|
1676
|
-
message: isVi ? 'Nhập ELEVENLABS_API_KEY (hoặc bỏ trống):' : 'Enter ELEVENLABS_API_KEY (or leave empty):',
|
|
1677
|
-
defaultValue: ttsElevenKey,
|
|
1678
|
-
allowBack: true,
|
|
1679
|
-
isVi,
|
|
1680
|
-
});
|
|
1681
|
-
if (elevenKey === CLI_BACK) {
|
|
1682
|
-
return { back: true };
|
|
1683
|
-
}
|
|
1684
|
-
ttsElevenKey = elevenKey;
|
|
1685
|
-
} else {
|
|
1686
|
-
ttsOpenaiKey = '';
|
|
1687
|
-
ttsElevenKey = '';
|
|
1688
|
-
}
|
|
1689
|
-
|
|
1690
|
-
let smtpHost = existingSmtpHost;
|
|
1691
|
-
let smtpPort = existingSmtpPort;
|
|
1692
|
-
let smtpUser = existingSmtpUser;
|
|
1693
|
-
let smtpPass = existingSmtpPass;
|
|
1694
|
-
if (selectedSkills.includes('email')) {
|
|
1695
|
-
const smtpHostValue = await inputWithBack({
|
|
1696
|
-
message: isVi ? 'SMTP Host (VD: smtp.gmail.com):' : 'SMTP Host (e.g. smtp.gmail.com):',
|
|
1697
|
-
defaultValue: smtpHost,
|
|
1698
|
-
required: true,
|
|
1699
|
-
allowBack: true,
|
|
1700
|
-
isVi,
|
|
1701
|
-
});
|
|
1702
|
-
if (smtpHostValue === CLI_BACK) {
|
|
1703
|
-
return { back: true };
|
|
1704
|
-
}
|
|
1705
|
-
smtpHost = smtpHostValue;
|
|
1706
|
-
|
|
1707
|
-
const smtpPortValue = await inputWithBack({
|
|
1708
|
-
message: 'SMTP Port:',
|
|
1709
|
-
defaultValue: smtpPort,
|
|
1710
|
-
required: true,
|
|
1711
|
-
allowBack: true,
|
|
1712
|
-
isVi,
|
|
1713
|
-
});
|
|
1714
|
-
if (smtpPortValue === CLI_BACK) {
|
|
1715
|
-
return { back: true };
|
|
1716
|
-
}
|
|
1717
|
-
smtpPort = smtpPortValue;
|
|
1718
|
-
|
|
1719
|
-
const smtpUserValue = await inputWithBack({
|
|
1720
|
-
message: isVi ? 'SMTP Email:' : 'SMTP Email:',
|
|
1721
|
-
defaultValue: smtpUser,
|
|
1722
|
-
required: true,
|
|
1723
|
-
allowBack: true,
|
|
1724
|
-
isVi,
|
|
1725
|
-
});
|
|
1726
|
-
if (smtpUserValue === CLI_BACK) {
|
|
1727
|
-
return { back: true };
|
|
1728
|
-
}
|
|
1729
|
-
smtpUser = smtpUserValue;
|
|
1730
|
-
|
|
1731
|
-
const smtpPassValue = await inputWithBack({
|
|
1732
|
-
message: isVi ? 'SMTP App Password:' : 'SMTP App Password:',
|
|
1733
|
-
defaultValue: smtpPass,
|
|
1734
|
-
required: true,
|
|
1735
|
-
allowBack: true,
|
|
1736
|
-
isVi,
|
|
1737
|
-
});
|
|
1738
|
-
if (smtpPassValue === CLI_BACK) {
|
|
1739
|
-
return { back: true };
|
|
1740
|
-
}
|
|
1741
|
-
smtpPass = smtpPassValue;
|
|
1742
|
-
} else {
|
|
1743
|
-
smtpHost = 'smtp.gmail.com';
|
|
1744
|
-
smtpPort = '587';
|
|
1745
|
-
smtpUser = '';
|
|
1746
|
-
smtpPass = '';
|
|
1747
|
-
}
|
|
1748
|
-
|
|
1749
|
-
return {
|
|
1750
|
-
back: false,
|
|
1751
|
-
selectedSkills,
|
|
1752
|
-
browserMode,
|
|
1753
|
-
ttsOpenaiKey,
|
|
1754
|
-
ttsElevenKey,
|
|
1755
|
-
smtpHost,
|
|
1756
|
-
smtpPort,
|
|
1757
|
-
smtpUser,
|
|
1758
|
-
smtpPass,
|
|
1759
|
-
};
|
|
1760
|
-
}
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
const CLI_BACK = '__openclaw_cli_back__';
|
|
1094
|
+
|
|
1095
|
+
function getBackChoice(isVi) {
|
|
1096
|
+
return {
|
|
1097
|
+
name: isVi ? '← Quay lại' : '← Back',
|
|
1098
|
+
value: CLI_BACK,
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
function withBackHint(message, isVi) {
|
|
1103
|
+
return `${message} ${isVi ? '(gõ "back" để quay lại)' : '(type "back" to go back)'}`;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
async function selectWithBack({ message, choices, defaultValue, allowBack = false, isVi = true }) {
|
|
1107
|
+
const finalChoices = allowBack ? [...choices, getBackChoice(isVi)] : choices;
|
|
1108
|
+
return select({
|
|
1109
|
+
message,
|
|
1110
|
+
choices: finalChoices,
|
|
1111
|
+
...(defaultValue ? { default: defaultValue } : {}),
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
async function inputWithBack({ message, defaultValue = '', required = false, allowBack = false, isVi = true }) {
|
|
1116
|
+
const value = await input({
|
|
1117
|
+
message: allowBack ? withBackHint(message, isVi) : message,
|
|
1118
|
+
default: defaultValue,
|
|
1119
|
+
required,
|
|
1120
|
+
});
|
|
1121
|
+
if (allowBack && String(value || '').trim().toLowerCase() === 'back') {
|
|
1122
|
+
return CLI_BACK;
|
|
1123
|
+
}
|
|
1124
|
+
return value;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
async function checkboxWithBack({ message, choices, isVi = true, allowBack = false }) {
|
|
1128
|
+
const finalChoices = allowBack ? [...choices, getBackChoice(isVi)] : choices;
|
|
1129
|
+
const value = await checkbox({
|
|
1130
|
+
message,
|
|
1131
|
+
choices: finalChoices,
|
|
1132
|
+
});
|
|
1133
|
+
return allowBack && value.includes(CLI_BACK) ? CLI_BACK : value;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
async function collectBotSetupStep({
|
|
1137
|
+
isVi,
|
|
1138
|
+
channelKey,
|
|
1139
|
+
channel,
|
|
1140
|
+
existingBots = [],
|
|
1141
|
+
existingBotCount = 1,
|
|
1142
|
+
existingGroupId = '',
|
|
1143
|
+
}) {
|
|
1144
|
+
let botCount = channelKey === 'telegram' ? existingBotCount : 1;
|
|
1145
|
+
let groupId = existingGroupId;
|
|
1146
|
+
const bots = [];
|
|
1147
|
+
|
|
1148
|
+
if (channelKey === 'telegram') {
|
|
1149
|
+
const botCountValue = await selectWithBack({
|
|
1150
|
+
message: isVi ? 'Bạn muốn cài bao nhiêu Telegram bot?' : 'How many Telegram bots do you want to deploy?',
|
|
1151
|
+
choices: [
|
|
1152
|
+
{ name: '1 bot (single)', value: '1' },
|
|
1153
|
+
{ name: '2 bots (Department Room)', value: '2' },
|
|
1154
|
+
{ name: '3 bots', value: '3' },
|
|
1155
|
+
{ name: '4 bots', value: '4' },
|
|
1156
|
+
{ name: '5 bots', value: '5' },
|
|
1157
|
+
],
|
|
1158
|
+
defaultValue: String(existingBotCount || 1),
|
|
1159
|
+
allowBack: true,
|
|
1160
|
+
isVi,
|
|
1161
|
+
});
|
|
1162
|
+
if (botCountValue === CLI_BACK) {
|
|
1163
|
+
return { back: true };
|
|
1164
|
+
}
|
|
1165
|
+
botCount = parseInt(botCountValue, 10);
|
|
1166
|
+
|
|
1167
|
+
if (botCount > 1) {
|
|
1168
|
+
const groupOption = await selectWithBack({
|
|
1169
|
+
message: isVi ? 'Bạn có sẵn Telegram Group chưa?' : 'Do you already have a Telegram Group?',
|
|
1170
|
+
choices: [
|
|
1171
|
+
{ 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' },
|
|
1172
|
+
{ name: isVi ? '🔗 Đã có group — nhập Group ID ngay' : '🔗 Already have a group — enter Group ID now', value: 'existing' }
|
|
1173
|
+
],
|
|
1174
|
+
defaultValue: groupId ? 'existing' : 'create',
|
|
1175
|
+
allowBack: true,
|
|
1176
|
+
isVi,
|
|
1177
|
+
});
|
|
1178
|
+
if (groupOption === CLI_BACK) {
|
|
1179
|
+
return { back: true };
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
if (groupOption === 'existing') {
|
|
1183
|
+
console.log(chalk.dim(isVi
|
|
1184
|
+
? '\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'
|
|
1185
|
+
: '\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'));
|
|
1186
|
+
const nextGroupId = await inputWithBack({
|
|
1187
|
+
message: isVi ? 'Telegram Group ID (VD: -1001234567890):' : 'Telegram Group ID (e.g. -1001234567890):',
|
|
1188
|
+
defaultValue: groupId,
|
|
1189
|
+
allowBack: true,
|
|
1190
|
+
isVi,
|
|
1191
|
+
});
|
|
1192
|
+
if (nextGroupId === CLI_BACK) {
|
|
1193
|
+
return { back: true };
|
|
1194
|
+
}
|
|
1195
|
+
groupId = nextGroupId;
|
|
1196
|
+
} else {
|
|
1197
|
+
groupId = '';
|
|
1198
|
+
}
|
|
1199
|
+
} else {
|
|
1200
|
+
groupId = '';
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
for (let i = 0; i < botCount; i++) {
|
|
1204
|
+
console.log(chalk.bold(`\n${isVi ? `─── Bot ${i + 1} / ${botCount} ───` : `─── Bot ${i + 1} / ${botCount} ───`}`));
|
|
1205
|
+
const defaults = existingBots[i] || {};
|
|
1206
|
+
const fields = [
|
|
1207
|
+
{
|
|
1208
|
+
key: 'name',
|
|
1209
|
+
message: isVi ? `Tên Bot ${i + 1}:` : `Bot ${i + 1} name:`,
|
|
1210
|
+
defaultValue: defaults.name || `Bot ${i + 1}`,
|
|
1211
|
+
required: true,
|
|
1212
|
+
},
|
|
1213
|
+
{
|
|
1214
|
+
key: 'slashCmd',
|
|
1215
|
+
message: isVi ? `Slash command (VD: /bot${i + 1}):` : `Slash command (e.g. /bot${i + 1}):`,
|
|
1216
|
+
defaultValue: defaults.slashCmd || `/bot${i + 1}`,
|
|
1217
|
+
required: true,
|
|
1218
|
+
},
|
|
1219
|
+
{
|
|
1220
|
+
key: 'desc',
|
|
1221
|
+
message: isVi ? `Mô tả Bot ${i + 1} (VD: Trợ lý AI cá nhân):` : `Bot ${i + 1} description (e.g. Personal AI assistant):`,
|
|
1222
|
+
defaultValue: defaults.desc || (isVi ? 'Trợ lý AI cá nhân' : 'Personal AI assistant'),
|
|
1223
|
+
required: true,
|
|
1224
|
+
},
|
|
1225
|
+
{
|
|
1226
|
+
key: 'persona',
|
|
1227
|
+
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):`,
|
|
1228
|
+
defaultValue: defaults.persona || '',
|
|
1229
|
+
required: false,
|
|
1230
|
+
},
|
|
1231
|
+
{
|
|
1232
|
+
key: 'token',
|
|
1233
|
+
message: isVi ? 'Bot Token (từ @BotFather):' : 'Bot Token (from @BotFather):',
|
|
1234
|
+
defaultValue: defaults.token || '',
|
|
1235
|
+
required: true,
|
|
1236
|
+
},
|
|
1237
|
+
];
|
|
1238
|
+
|
|
1239
|
+
const draft = { ...defaults };
|
|
1240
|
+
let fieldIndex = 0;
|
|
1241
|
+
while (fieldIndex < fields.length) {
|
|
1242
|
+
const field = fields[fieldIndex];
|
|
1243
|
+
const value = await inputWithBack({
|
|
1244
|
+
message: field.message,
|
|
1245
|
+
defaultValue: draft[field.key] || field.defaultValue,
|
|
1246
|
+
required: field.required,
|
|
1247
|
+
allowBack: true,
|
|
1248
|
+
isVi,
|
|
1249
|
+
});
|
|
1250
|
+
if (value === CLI_BACK) {
|
|
1251
|
+
if (fieldIndex > 0) {
|
|
1252
|
+
fieldIndex--;
|
|
1253
|
+
continue;
|
|
1254
|
+
}
|
|
1255
|
+
return { back: true };
|
|
1256
|
+
}
|
|
1257
|
+
draft[field.key] = value;
|
|
1258
|
+
fieldIndex++;
|
|
1259
|
+
}
|
|
1260
|
+
bots.push(draft);
|
|
1261
|
+
}
|
|
1262
|
+
} else if (channelKey !== 'zalo-personal') {
|
|
1263
|
+
const defaults = existingBots[0] || {};
|
|
1264
|
+
const fields = [
|
|
1265
|
+
{
|
|
1266
|
+
key: 'name',
|
|
1267
|
+
message: isVi ? 'Tên Bot:' : 'Bot Name:',
|
|
1268
|
+
defaultValue: defaults.name || 'Chat Bot',
|
|
1269
|
+
required: true,
|
|
1270
|
+
},
|
|
1271
|
+
{
|
|
1272
|
+
key: 'desc',
|
|
1273
|
+
message: isVi ? 'Mô tả Bot:' : 'Bot Description:',
|
|
1274
|
+
defaultValue: defaults.desc || (isVi ? 'Trợ lý AI cá nhân' : 'Personal AI assistant'),
|
|
1275
|
+
required: true,
|
|
1276
|
+
},
|
|
1277
|
+
{
|
|
1278
|
+
key: 'persona',
|
|
1279
|
+
message: isVi ? 'Tính cách & quy tắc (VD: gọn gàng, thân thiện):' : 'Persona & rules (e.g. concise, friendly):',
|
|
1280
|
+
defaultValue: defaults.persona || '',
|
|
1281
|
+
required: false,
|
|
1282
|
+
},
|
|
1283
|
+
{
|
|
1284
|
+
key: 'token',
|
|
1285
|
+
message: isVi ? `Nhập ${channel.name} Token:` : `Enter ${channel.name} Token:`,
|
|
1286
|
+
defaultValue: defaults.token || '',
|
|
1287
|
+
required: true,
|
|
1288
|
+
},
|
|
1289
|
+
];
|
|
1290
|
+
const draft = { ...defaults, slashCmd: '' };
|
|
1291
|
+
let fieldIndex = 0;
|
|
1292
|
+
while (fieldIndex < fields.length) {
|
|
1293
|
+
const field = fields[fieldIndex];
|
|
1294
|
+
const value = await inputWithBack({
|
|
1295
|
+
message: field.message,
|
|
1296
|
+
defaultValue: draft[field.key] || field.defaultValue,
|
|
1297
|
+
required: field.required,
|
|
1298
|
+
allowBack: true,
|
|
1299
|
+
isVi,
|
|
1300
|
+
});
|
|
1301
|
+
if (value === CLI_BACK) {
|
|
1302
|
+
if (fieldIndex > 0) {
|
|
1303
|
+
fieldIndex--;
|
|
1304
|
+
continue;
|
|
1305
|
+
}
|
|
1306
|
+
return { back: true };
|
|
1307
|
+
}
|
|
1308
|
+
draft[field.key] = value;
|
|
1309
|
+
fieldIndex++;
|
|
1310
|
+
}
|
|
1311
|
+
bots.push(draft);
|
|
1312
|
+
} else {
|
|
1313
|
+
bots.push({ name: 'Bot', slashCmd: '', desc: '', persona: '', token: '' });
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
return {
|
|
1317
|
+
back: false,
|
|
1318
|
+
botCount,
|
|
1319
|
+
groupId,
|
|
1320
|
+
bots,
|
|
1321
|
+
botToken: bots[0]?.token || '',
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
async function collectBotSetupStepWithGroupBack(options) {
|
|
1326
|
+
const {
|
|
1327
|
+
isVi,
|
|
1328
|
+
channelKey,
|
|
1329
|
+
channel,
|
|
1330
|
+
existingBots = [],
|
|
1331
|
+
existingBotCount = 1,
|
|
1332
|
+
existingGroupId = '',
|
|
1333
|
+
} = options;
|
|
1334
|
+
|
|
1335
|
+
let botCount = channelKey === 'telegram' ? existingBotCount : 1;
|
|
1336
|
+
let groupId = existingGroupId;
|
|
1337
|
+
const bots = [];
|
|
1338
|
+
|
|
1339
|
+
if (channelKey === 'telegram') {
|
|
1340
|
+
const botCountValue = await selectWithBack({
|
|
1341
|
+
message: isVi ? 'Bạn muốn cài bao nhiêu Telegram bot?' : 'How many Telegram bots do you want to deploy?',
|
|
1342
|
+
choices: [
|
|
1343
|
+
{ name: '1 bot (single)', value: '1' },
|
|
1344
|
+
{ name: '2 bots (Department Room)', value: '2' },
|
|
1345
|
+
{ name: '3 bots', value: '3' },
|
|
1346
|
+
{ name: '4 bots', value: '4' },
|
|
1347
|
+
{ name: '5 bots', value: '5' },
|
|
1348
|
+
],
|
|
1349
|
+
defaultValue: String(existingBotCount || 1),
|
|
1350
|
+
allowBack: true,
|
|
1351
|
+
isVi,
|
|
1352
|
+
});
|
|
1353
|
+
if (botCountValue === CLI_BACK) {
|
|
1354
|
+
return { back: true };
|
|
1355
|
+
}
|
|
1356
|
+
botCount = parseInt(botCountValue, 10);
|
|
1357
|
+
|
|
1358
|
+
if (botCount > 1) {
|
|
1359
|
+
while (true) {
|
|
1360
|
+
const groupOption = await selectWithBack({
|
|
1361
|
+
message: isVi ? 'Bạn có sẵn Telegram Group chưa?' : 'Do you already have a Telegram Group?',
|
|
1362
|
+
choices: [
|
|
1363
|
+
{ 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' },
|
|
1364
|
+
{ name: isVi ? '🔗 Đã có group — nhập Group ID ngay' : '🔗 Already have a group — enter Group ID now', value: 'existing' }
|
|
1365
|
+
],
|
|
1366
|
+
defaultValue: groupId ? 'existing' : 'create',
|
|
1367
|
+
allowBack: true,
|
|
1368
|
+
isVi,
|
|
1369
|
+
});
|
|
1370
|
+
if (groupOption === CLI_BACK) {
|
|
1371
|
+
return { back: true };
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
if (groupOption === 'existing') {
|
|
1375
|
+
console.log(chalk.dim(isVi
|
|
1376
|
+
? '\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'
|
|
1377
|
+
: '\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'));
|
|
1378
|
+
const nextGroupId = await inputWithBack({
|
|
1379
|
+
message: isVi ? 'Telegram Group ID (VD: -1001234567890):' : 'Telegram Group ID (e.g. -1001234567890):',
|
|
1380
|
+
defaultValue: groupId,
|
|
1381
|
+
allowBack: true,
|
|
1382
|
+
isVi,
|
|
1383
|
+
});
|
|
1384
|
+
if (nextGroupId === CLI_BACK) {
|
|
1385
|
+
continue;
|
|
1386
|
+
}
|
|
1387
|
+
groupId = nextGroupId;
|
|
1388
|
+
break;
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
groupId = '';
|
|
1392
|
+
break;
|
|
1393
|
+
}
|
|
1394
|
+
} else {
|
|
1395
|
+
groupId = '';
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
for (let i = 0; i < botCount; i++) {
|
|
1399
|
+
console.log(chalk.bold(`\n${isVi ? `─── Bot ${i + 1} / ${botCount} ───` : `─── Bot ${i + 1} / ${botCount} ───`}`));
|
|
1400
|
+
const defaults = existingBots[i] || {};
|
|
1401
|
+
const fields = [
|
|
1402
|
+
{
|
|
1403
|
+
key: 'name',
|
|
1404
|
+
message: isVi ? `Tên Bot ${i + 1}:` : `Bot ${i + 1} name:`,
|
|
1405
|
+
defaultValue: defaults.name || `Bot ${i + 1}`,
|
|
1406
|
+
required: true,
|
|
1407
|
+
},
|
|
1408
|
+
{
|
|
1409
|
+
key: 'slashCmd',
|
|
1410
|
+
message: isVi ? `Slash command (VD: /bot${i + 1}):` : `Slash command (e.g. /bot${i + 1}):`,
|
|
1411
|
+
defaultValue: defaults.slashCmd || `/bot${i + 1}`,
|
|
1412
|
+
required: true,
|
|
1413
|
+
},
|
|
1414
|
+
{
|
|
1415
|
+
key: 'desc',
|
|
1416
|
+
message: isVi ? `Mô tả Bot ${i + 1} (VD: Trợ lý AI cá nhân):` : `Bot ${i + 1} description (e.g. Personal AI assistant):`,
|
|
1417
|
+
defaultValue: defaults.desc || (isVi ? 'Trợ lý AI cá nhân' : 'Personal AI assistant'),
|
|
1418
|
+
required: true,
|
|
1419
|
+
},
|
|
1420
|
+
{
|
|
1421
|
+
key: 'persona',
|
|
1422
|
+
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):`,
|
|
1423
|
+
defaultValue: defaults.persona || '',
|
|
1424
|
+
required: false,
|
|
1425
|
+
},
|
|
1426
|
+
{
|
|
1427
|
+
key: 'token',
|
|
1428
|
+
message: isVi ? 'Bot Token (từ @BotFather):' : 'Bot Token (from @BotFather):',
|
|
1429
|
+
defaultValue: defaults.token || '',
|
|
1430
|
+
required: true,
|
|
1431
|
+
},
|
|
1432
|
+
];
|
|
1433
|
+
|
|
1434
|
+
const draft = { ...defaults };
|
|
1435
|
+
let fieldIndex = 0;
|
|
1436
|
+
while (fieldIndex < fields.length) {
|
|
1437
|
+
const field = fields[fieldIndex];
|
|
1438
|
+
const value = await inputWithBack({
|
|
1439
|
+
message: field.message,
|
|
1440
|
+
defaultValue: draft[field.key] || field.defaultValue,
|
|
1441
|
+
required: field.required,
|
|
1442
|
+
allowBack: true,
|
|
1443
|
+
isVi,
|
|
1444
|
+
});
|
|
1445
|
+
if (value === CLI_BACK) {
|
|
1446
|
+
if (fieldIndex > 0) {
|
|
1447
|
+
fieldIndex--;
|
|
1448
|
+
continue;
|
|
1449
|
+
}
|
|
1450
|
+
return { back: true };
|
|
1451
|
+
}
|
|
1452
|
+
draft[field.key] = value;
|
|
1453
|
+
fieldIndex++;
|
|
1454
|
+
}
|
|
1455
|
+
bots.push(draft);
|
|
1456
|
+
}
|
|
1457
|
+
} else if (channelKey !== 'zalo-personal') {
|
|
1458
|
+
const defaults = existingBots[0] || {};
|
|
1459
|
+
const fields = [
|
|
1460
|
+
{
|
|
1461
|
+
key: 'name',
|
|
1462
|
+
message: isVi ? 'Tên Bot:' : 'Bot Name:',
|
|
1463
|
+
defaultValue: defaults.name || 'Chat Bot',
|
|
1464
|
+
required: true,
|
|
1465
|
+
},
|
|
1466
|
+
{
|
|
1467
|
+
key: 'desc',
|
|
1468
|
+
message: isVi ? 'Mô tả Bot:' : 'Bot Description:',
|
|
1469
|
+
defaultValue: defaults.desc || (isVi ? 'Trợ lý AI cá nhân' : 'Personal AI assistant'),
|
|
1470
|
+
required: true,
|
|
1471
|
+
},
|
|
1472
|
+
{
|
|
1473
|
+
key: 'persona',
|
|
1474
|
+
message: isVi ? 'Tính cách & quy tắc (VD: gọn gàng, thân thiện):' : 'Persona & rules (e.g. concise, friendly):',
|
|
1475
|
+
defaultValue: defaults.persona || '',
|
|
1476
|
+
required: false,
|
|
1477
|
+
},
|
|
1478
|
+
{
|
|
1479
|
+
key: 'token',
|
|
1480
|
+
message: isVi ? `Nhập ${channel.name} Token:` : `Enter ${channel.name} Token:`,
|
|
1481
|
+
defaultValue: defaults.token || '',
|
|
1482
|
+
required: true,
|
|
1483
|
+
},
|
|
1484
|
+
];
|
|
1485
|
+
const draft = { ...defaults, slashCmd: '' };
|
|
1486
|
+
let fieldIndex = 0;
|
|
1487
|
+
while (fieldIndex < fields.length) {
|
|
1488
|
+
const field = fields[fieldIndex];
|
|
1489
|
+
const value = await inputWithBack({
|
|
1490
|
+
message: field.message,
|
|
1491
|
+
defaultValue: draft[field.key] || field.defaultValue,
|
|
1492
|
+
required: field.required,
|
|
1493
|
+
allowBack: true,
|
|
1494
|
+
isVi,
|
|
1495
|
+
});
|
|
1496
|
+
if (value === CLI_BACK) {
|
|
1497
|
+
if (fieldIndex > 0) {
|
|
1498
|
+
fieldIndex--;
|
|
1499
|
+
continue;
|
|
1500
|
+
}
|
|
1501
|
+
return { back: true };
|
|
1502
|
+
}
|
|
1503
|
+
draft[field.key] = value;
|
|
1504
|
+
fieldIndex++;
|
|
1505
|
+
}
|
|
1506
|
+
bots.push(draft);
|
|
1507
|
+
} else {
|
|
1508
|
+
bots.push({ name: 'Bot', slashCmd: '', desc: '', persona: '', token: '' });
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
return {
|
|
1512
|
+
back: false,
|
|
1513
|
+
botCount,
|
|
1514
|
+
groupId,
|
|
1515
|
+
bots,
|
|
1516
|
+
botToken: bots[0]?.token || '',
|
|
1517
|
+
};
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
async function collectProviderStep({
|
|
1521
|
+
isVi,
|
|
1522
|
+
existingProviderKey = '',
|
|
1523
|
+
existingProviderKeyVal = '',
|
|
1524
|
+
existingOllamaModel = 'gemma4:e2b',
|
|
1525
|
+
}) {
|
|
1526
|
+
const providerKey = await selectWithBack({
|
|
1527
|
+
message: isVi ? 'Chọn AI Provider:' : 'Select AI Provider:',
|
|
1528
|
+
choices: Object.entries(PROVIDERS).map(([k, v]) => ({ name: `${v.icon} ${v.name}`, value: k })),
|
|
1529
|
+
defaultValue: existingProviderKey || undefined,
|
|
1530
|
+
allowBack: true,
|
|
1531
|
+
isVi,
|
|
1532
|
+
});
|
|
1533
|
+
if (providerKey === CLI_BACK) {
|
|
1534
|
+
return { back: true };
|
|
1535
|
+
}
|
|
1536
|
+
const provider = PROVIDERS[providerKey];
|
|
1537
|
+
|
|
1538
|
+
let providerKeyVal = existingProviderKey === providerKey ? existingProviderKeyVal : '';
|
|
1539
|
+
if (!provider.isProxy && !provider.isLocal) {
|
|
1540
|
+
const keyValue = await inputWithBack({
|
|
1541
|
+
message: isVi ? `Nhập ${provider.envKey}:` : `Enter ${provider.envKey}:`,
|
|
1542
|
+
defaultValue: providerKeyVal,
|
|
1543
|
+
required: true,
|
|
1544
|
+
allowBack: true,
|
|
1545
|
+
isVi,
|
|
1546
|
+
});
|
|
1547
|
+
if (keyValue === CLI_BACK) {
|
|
1548
|
+
return { back: true };
|
|
1549
|
+
}
|
|
1550
|
+
providerKeyVal = keyValue;
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
let selectedOllamaModel = existingProviderKey === 'ollama' ? existingOllamaModel : 'gemma4:e2b';
|
|
1554
|
+
if (providerKey === 'ollama') {
|
|
1555
|
+
console.log(chalk.yellow(isVi
|
|
1556
|
+
? '\n💡 Gemma 4 (02/04/2026) — chọn kích thước phù hợp với RAM máy bạn:'
|
|
1557
|
+
: '\n💡 Gemma 4 (April 2, 2026) — pick a size that fits your RAM:'));
|
|
1558
|
+
const modelValue = await selectWithBack({
|
|
1559
|
+
message: isVi ? 'Chọn model Ollama:' : 'Select Ollama model:',
|
|
1560
|
+
choices: [
|
|
1561
|
+
{
|
|
1562
|
+
name: isVi
|
|
1563
|
+
? '🟢 gemma4:e2b — Nhẹ nhất (~4-6 GB RAM) — Laptop / test nhanh ★ Khuyên dùng'
|
|
1564
|
+
: '🟢 gemma4:e2b — Lightest (~4-6 GB RAM) — Laptop / fastest test ★ Recommended',
|
|
1565
|
+
value: 'gemma4:e2b'
|
|
1566
|
+
},
|
|
1567
|
+
{
|
|
1568
|
+
name: isVi
|
|
1569
|
+
? '🟡 gemma4:e4b — Cân bằng (~8-10 GB RAM) — Dùng hằng ngày'
|
|
1570
|
+
: '🟡 gemma4:e4b — Balanced (~8-10 GB RAM) — Daily use',
|
|
1571
|
+
value: 'gemma4:e4b'
|
|
1572
|
+
},
|
|
1573
|
+
{
|
|
1574
|
+
name: isVi
|
|
1575
|
+
? '🟠 gemma4:26b — Mạnh (~18-24 GB RAM/VRAM) — Máy mạnh'
|
|
1576
|
+
: '🟠 gemma4:26b — Powerful (~18-24 GB RAM/VRAM) — High-end machine',
|
|
1577
|
+
value: 'gemma4:26b'
|
|
1578
|
+
},
|
|
1579
|
+
{
|
|
1580
|
+
name: isVi
|
|
1581
|
+
? '🔴 gemma4:31b — Mạnh nhất (~24+ GB RAM/VRAM) — GPU workstation'
|
|
1582
|
+
: '🔴 gemma4:31b — Most powerful (~24+ GB RAM/VRAM) — GPU workstation',
|
|
1583
|
+
value: 'gemma4:31b'
|
|
1584
|
+
},
|
|
1585
|
+
],
|
|
1586
|
+
defaultValue: selectedOllamaModel,
|
|
1587
|
+
allowBack: true,
|
|
1588
|
+
isVi,
|
|
1589
|
+
});
|
|
1590
|
+
if (modelValue === CLI_BACK) {
|
|
1591
|
+
return { back: true };
|
|
1592
|
+
}
|
|
1593
|
+
selectedOllamaModel = modelValue;
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
return {
|
|
1597
|
+
back: false,
|
|
1598
|
+
providerKey,
|
|
1599
|
+
provider,
|
|
1600
|
+
providerKeyVal,
|
|
1601
|
+
selectedOllamaModel,
|
|
1602
|
+
};
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
async function collectSkillsStep({
|
|
1606
|
+
isVi,
|
|
1607
|
+
providerKey,
|
|
1608
|
+
existingSelectedSkills = [],
|
|
1609
|
+
existingBrowserMode = 'server',
|
|
1610
|
+
existingTtsOpenaiKey = '',
|
|
1611
|
+
existingTtsElevenKey = '',
|
|
1612
|
+
existingSmtpHost = 'smtp.gmail.com',
|
|
1613
|
+
existingSmtpPort = '587',
|
|
1614
|
+
existingSmtpUser = '',
|
|
1615
|
+
existingSmtpPass = '',
|
|
1616
|
+
}) {
|
|
1617
|
+
const selectedSkills = await checkboxWithBack({
|
|
1618
|
+
message: isVi ? 'Bật tính năng bổ sung (Space để chọn):' : 'Enable extra skills (Space to select):',
|
|
1619
|
+
choices: getCliSkillChoices({ providerKey, isVi }).map((choice) => ({
|
|
1620
|
+
...choice,
|
|
1621
|
+
checked: existingSelectedSkills.includes(choice.value),
|
|
1622
|
+
})),
|
|
1623
|
+
allowBack: true,
|
|
1624
|
+
isVi,
|
|
1625
|
+
});
|
|
1626
|
+
if (selectedSkills === CLI_BACK) {
|
|
1627
|
+
return { back: true };
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
let browserMode = existingBrowserMode;
|
|
1631
|
+
if (selectedSkills.includes('browser')) {
|
|
1632
|
+
const isLinux = process.platform === 'linux';
|
|
1633
|
+
const browserValue = await selectWithBack({
|
|
1634
|
+
message: isVi ? 'Chế độ Browser Automation:' : 'Browser Automation mode:',
|
|
1635
|
+
choices: [
|
|
1636
|
+
{
|
|
1637
|
+
name: isVi
|
|
1638
|
+
? '🖥️ Dùng Chrome trên máy tính (Windows/Mac — Bypass Cloudflare tốt hơn)'
|
|
1639
|
+
: '🖥️ Use Host Chrome (Windows/Mac — Better Cloudflare bypass)',
|
|
1640
|
+
value: 'desktop'
|
|
1641
|
+
},
|
|
1642
|
+
{
|
|
1643
|
+
name: isVi
|
|
1644
|
+
? '🐳 Headless Chromium trong Docker (Ubuntu Server / VPS — không cần GUI)'
|
|
1645
|
+
: '🐳 Headless Chromium inside Docker (Ubuntu Server / VPS — No GUI)',
|
|
1646
|
+
value: 'server'
|
|
1647
|
+
}
|
|
1648
|
+
],
|
|
1649
|
+
defaultValue: browserMode || (isLinux ? 'server' : 'desktop'),
|
|
1650
|
+
allowBack: true,
|
|
1651
|
+
isVi,
|
|
1652
|
+
});
|
|
1653
|
+
if (browserValue === CLI_BACK) {
|
|
1654
|
+
return { back: true };
|
|
1655
|
+
}
|
|
1656
|
+
browserMode = browserValue;
|
|
1657
|
+
} else {
|
|
1658
|
+
browserMode = 'server';
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
let ttsOpenaiKey = existingTtsOpenaiKey;
|
|
1662
|
+
let ttsElevenKey = existingTtsElevenKey;
|
|
1663
|
+
if (selectedSkills.includes('tts')) {
|
|
1664
|
+
const openaiKey = await inputWithBack({
|
|
1665
|
+
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):',
|
|
1666
|
+
defaultValue: ttsOpenaiKey,
|
|
1667
|
+
allowBack: true,
|
|
1668
|
+
isVi,
|
|
1669
|
+
});
|
|
1670
|
+
if (openaiKey === CLI_BACK) {
|
|
1671
|
+
return { back: true };
|
|
1672
|
+
}
|
|
1673
|
+
ttsOpenaiKey = openaiKey;
|
|
1674
|
+
|
|
1675
|
+
const elevenKey = await inputWithBack({
|
|
1676
|
+
message: isVi ? 'Nhập ELEVENLABS_API_KEY (hoặc bỏ trống):' : 'Enter ELEVENLABS_API_KEY (or leave empty):',
|
|
1677
|
+
defaultValue: ttsElevenKey,
|
|
1678
|
+
allowBack: true,
|
|
1679
|
+
isVi,
|
|
1680
|
+
});
|
|
1681
|
+
if (elevenKey === CLI_BACK) {
|
|
1682
|
+
return { back: true };
|
|
1683
|
+
}
|
|
1684
|
+
ttsElevenKey = elevenKey;
|
|
1685
|
+
} else {
|
|
1686
|
+
ttsOpenaiKey = '';
|
|
1687
|
+
ttsElevenKey = '';
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
let smtpHost = existingSmtpHost;
|
|
1691
|
+
let smtpPort = existingSmtpPort;
|
|
1692
|
+
let smtpUser = existingSmtpUser;
|
|
1693
|
+
let smtpPass = existingSmtpPass;
|
|
1694
|
+
if (selectedSkills.includes('email')) {
|
|
1695
|
+
const smtpHostValue = await inputWithBack({
|
|
1696
|
+
message: isVi ? 'SMTP Host (VD: smtp.gmail.com):' : 'SMTP Host (e.g. smtp.gmail.com):',
|
|
1697
|
+
defaultValue: smtpHost,
|
|
1698
|
+
required: true,
|
|
1699
|
+
allowBack: true,
|
|
1700
|
+
isVi,
|
|
1701
|
+
});
|
|
1702
|
+
if (smtpHostValue === CLI_BACK) {
|
|
1703
|
+
return { back: true };
|
|
1704
|
+
}
|
|
1705
|
+
smtpHost = smtpHostValue;
|
|
1706
|
+
|
|
1707
|
+
const smtpPortValue = await inputWithBack({
|
|
1708
|
+
message: 'SMTP Port:',
|
|
1709
|
+
defaultValue: smtpPort,
|
|
1710
|
+
required: true,
|
|
1711
|
+
allowBack: true,
|
|
1712
|
+
isVi,
|
|
1713
|
+
});
|
|
1714
|
+
if (smtpPortValue === CLI_BACK) {
|
|
1715
|
+
return { back: true };
|
|
1716
|
+
}
|
|
1717
|
+
smtpPort = smtpPortValue;
|
|
1718
|
+
|
|
1719
|
+
const smtpUserValue = await inputWithBack({
|
|
1720
|
+
message: isVi ? 'SMTP Email:' : 'SMTP Email:',
|
|
1721
|
+
defaultValue: smtpUser,
|
|
1722
|
+
required: true,
|
|
1723
|
+
allowBack: true,
|
|
1724
|
+
isVi,
|
|
1725
|
+
});
|
|
1726
|
+
if (smtpUserValue === CLI_BACK) {
|
|
1727
|
+
return { back: true };
|
|
1728
|
+
}
|
|
1729
|
+
smtpUser = smtpUserValue;
|
|
1730
|
+
|
|
1731
|
+
const smtpPassValue = await inputWithBack({
|
|
1732
|
+
message: isVi ? 'SMTP App Password:' : 'SMTP App Password:',
|
|
1733
|
+
defaultValue: smtpPass,
|
|
1734
|
+
required: true,
|
|
1735
|
+
allowBack: true,
|
|
1736
|
+
isVi,
|
|
1737
|
+
});
|
|
1738
|
+
if (smtpPassValue === CLI_BACK) {
|
|
1739
|
+
return { back: true };
|
|
1740
|
+
}
|
|
1741
|
+
smtpPass = smtpPassValue;
|
|
1742
|
+
} else {
|
|
1743
|
+
smtpHost = 'smtp.gmail.com';
|
|
1744
|
+
smtpPort = '587';
|
|
1745
|
+
smtpUser = '';
|
|
1746
|
+
smtpPass = '';
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
return {
|
|
1750
|
+
back: false,
|
|
1751
|
+
selectedSkills,
|
|
1752
|
+
browserMode,
|
|
1753
|
+
ttsOpenaiKey,
|
|
1754
|
+
ttsElevenKey,
|
|
1755
|
+
smtpHost,
|
|
1756
|
+
smtpPort,
|
|
1757
|
+
smtpUser,
|
|
1758
|
+
smtpPass,
|
|
1759
|
+
};
|
|
1760
|
+
}
|
|
1761
1761
|
|
|
1762
1762
|
|
|
1763
1763
|
// ─── Shared workspace file writer ─────────────────────────────────────────────
|
|
@@ -1833,228 +1833,228 @@ async function writeWorkspaceFiles({
|
|
|
1833
1833
|
}
|
|
1834
1834
|
|
|
1835
1835
|
|
|
1836
|
-
async function main() {
|
|
1837
|
-
const cliSubcommand = getCliSubcommand();
|
|
1838
|
-
if (cliSubcommand === 'upgrade') {
|
|
1839
|
-
await runUpgradeCommand();
|
|
1840
|
-
return;
|
|
1841
|
-
}
|
|
1842
|
-
|
|
1843
|
-
console.log(chalk.red('\n=================================='));
|
|
1844
|
-
console.log(chalk.redBright(LOGO));
|
|
1845
|
-
console.log(chalk.greenBright(' OpenClaw Auto Setup CLI '));
|
|
1846
|
-
console.log(chalk.red('==================================\n'));
|
|
1847
|
-
|
|
1848
|
-
let lang = 'vi';
|
|
1849
|
-
let isVi = true;
|
|
1850
|
-
const detectedPlatform = process.platform;
|
|
1851
|
-
const detectedOS = detectedPlatform === 'win32' ? 'windows'
|
|
1852
|
-
: detectedPlatform === 'darwin' ? 'macos'
|
|
1853
|
-
: 'linux';
|
|
1854
|
-
let osChoice = detectedOS === 'linux' ? 'vps' : detectedOS;
|
|
1855
|
-
let deployMode = 'docker';
|
|
1856
|
-
let channelKey = 'telegram';
|
|
1857
|
-
let channel = CHANNELS[channelKey];
|
|
1858
|
-
let botToken = '';
|
|
1859
|
-
let botCount = 1;
|
|
1860
|
-
let bots = [];
|
|
1861
|
-
let groupId = '';
|
|
1862
|
-
let userInfo = '';
|
|
1863
|
-
let providerKey = '9router';
|
|
1864
|
-
let provider = PROVIDERS[providerKey];
|
|
1865
|
-
let providerKeyVal = '';
|
|
1866
|
-
let selectedOllamaModel = 'gemma4:e2b';
|
|
1867
|
-
let selectedSkills = [];
|
|
1868
|
-
let tavilyKey = '';
|
|
1869
|
-
let browserMode = 'server';
|
|
1870
|
-
let ttsOpenaiKey = '';
|
|
1871
|
-
let ttsElevenKey = '';
|
|
1872
|
-
let smtpHost = 'smtp.gmail.com', smtpPort = '587', smtpUser = '', smtpPass = '';
|
|
1873
|
-
let defaultDir = process.cwd();
|
|
1874
|
-
if (!defaultDir.endsWith('openclaw-setup') && !defaultDir.endsWith('openclaw')) {
|
|
1875
|
-
defaultDir = path.join(defaultDir, 'openclaw-setup');
|
|
1876
|
-
}
|
|
1877
|
-
let projectDir = defaultDir;
|
|
1878
|
-
|
|
1879
|
-
let setupStep = 'language';
|
|
1880
|
-
while (true) {
|
|
1881
|
-
if (setupStep === 'language') {
|
|
1882
|
-
lang = await select({
|
|
1883
|
-
message: 'Select language / Chọn ngôn ngữ:',
|
|
1884
|
-
choices: [
|
|
1885
|
-
{ name: 'Tiếng Việt', value: 'vi' },
|
|
1886
|
-
{ name: 'English', value: 'en' }
|
|
1887
|
-
],
|
|
1888
|
-
default: lang
|
|
1889
|
-
});
|
|
1890
|
-
isVi = lang === 'vi';
|
|
1891
|
-
setupStep = 'os';
|
|
1892
|
-
continue;
|
|
1893
|
-
}
|
|
1894
|
-
|
|
1895
|
-
if (setupStep === 'os') {
|
|
1896
|
-
const nextOsChoice = await selectWithBack({
|
|
1897
|
-
message: isVi ? 'Bạn đang chạy trên hệ điều hành nào?' : 'What OS are you running on?',
|
|
1898
|
-
choices: [
|
|
1899
|
-
{ name: isVi ? '🪟 Windows' : '🪟 Windows', value: 'windows' },
|
|
1900
|
-
{ name: isVi ? '🍎 macOS' : '🍎 macOS', value: 'macos' },
|
|
1901
|
-
{ name: isVi ? '🐧 Ubuntu Desktop' : '🐧 Ubuntu Desktop', value: 'ubuntu' },
|
|
1902
|
-
{ name: isVi ? '🖥️ VPS / Ubuntu Server' : '🖥️ VPS / Ubuntu Server', value: 'vps' },
|
|
1903
|
-
],
|
|
1904
|
-
defaultValue: osChoice,
|
|
1905
|
-
allowBack: true,
|
|
1906
|
-
isVi,
|
|
1907
|
-
});
|
|
1908
|
-
if (nextOsChoice === CLI_BACK) {
|
|
1909
|
-
setupStep = 'language';
|
|
1910
|
-
continue;
|
|
1911
|
-
}
|
|
1912
|
-
osChoice = nextOsChoice;
|
|
1913
|
-
setupStep = 'deploy';
|
|
1914
|
-
continue;
|
|
1915
|
-
}
|
|
1916
|
-
|
|
1917
|
-
if (setupStep === 'deploy') {
|
|
1918
|
-
const deployModeDefault = (osChoice === 'ubuntu' || osChoice === 'vps') ? 'native' : 'docker';
|
|
1919
|
-
const nextDeployMode = await selectWithBack({
|
|
1920
|
-
message: isVi ? 'Chọn cách chạy bot:' : 'How do you want to run the bot?',
|
|
1921
|
-
choices: [
|
|
1922
|
-
{ 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' },
|
|
1923
|
-
{ 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' }
|
|
1924
|
-
],
|
|
1925
|
-
defaultValue: deployMode || deployModeDefault,
|
|
1926
|
-
allowBack: true,
|
|
1927
|
-
isVi,
|
|
1928
|
-
});
|
|
1929
|
-
if (nextDeployMode === CLI_BACK) {
|
|
1930
|
-
setupStep = 'os';
|
|
1931
|
-
continue;
|
|
1932
|
-
}
|
|
1933
|
-
deployMode = nextDeployMode;
|
|
1934
|
-
if (deployMode === 'docker' && !isDockerInstalled()) {
|
|
1935
|
-
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...'));
|
|
1936
|
-
try {
|
|
1937
|
-
const platform = process.platform;
|
|
1938
|
-
if (platform === 'win32') {
|
|
1939
|
-
execSync('winget install -e --id Docker.DockerDesktop --accept-source-agreements --accept-package-agreements', { stdio: 'inherit' });
|
|
1940
|
-
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.'));
|
|
1941
|
-
process.exit(0);
|
|
1942
|
-
} else if (platform === 'darwin') {
|
|
1943
|
-
execSync('brew install --cask docker', { stdio: 'inherit' });
|
|
1944
|
-
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.'));
|
|
1945
|
-
process.exit(0);
|
|
1946
|
-
} else {
|
|
1947
|
-
execSync('curl -fsSL https://get.docker.com | sh', { stdio: 'inherit', shell: true });
|
|
1948
|
-
try { execSync('apt-get install -y docker-compose-plugin', { stdio: 'ignore', shell: true }); } catch {}
|
|
1949
|
-
console.log(chalk.green(isVi ? '✅ Docker Engine + Compose plugin đã cài xong.' : '✅ Docker Engine + Compose plugin installed.'));
|
|
1950
|
-
}
|
|
1951
|
-
} catch {
|
|
1952
|
-
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/'));
|
|
1953
|
-
process.exit(1);
|
|
1954
|
-
}
|
|
1955
|
-
}
|
|
1956
|
-
setupStep = 'channel';
|
|
1957
|
-
continue;
|
|
1958
|
-
}
|
|
1959
|
-
|
|
1960
|
-
if (setupStep === 'channel') {
|
|
1961
|
-
const nextChannelKey = await selectWithBack({
|
|
1962
|
-
message: isVi ? 'Chọn nền tảng bot:' : 'Select bot platform:',
|
|
1963
|
-
choices: Object.entries(CHANNELS).map(([k, v]) => ({ name: v.icon + ' ' + v.name, value: k })),
|
|
1964
|
-
defaultValue: channelKey,
|
|
1965
|
-
allowBack: true,
|
|
1966
|
-
isVi,
|
|
1967
|
-
});
|
|
1968
|
-
if (nextChannelKey === CLI_BACK) {
|
|
1969
|
-
setupStep = 'deploy';
|
|
1970
|
-
continue;
|
|
1971
|
-
}
|
|
1972
|
-
channelKey = nextChannelKey;
|
|
1973
|
-
channel = CHANNELS[channelKey];
|
|
1974
|
-
if (channelKey === 'zalo-bot') {
|
|
1975
|
-
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.')));
|
|
1976
|
-
}
|
|
1977
|
-
setupStep = 'botSetup';
|
|
1978
|
-
continue;
|
|
1979
|
-
}
|
|
1980
|
-
|
|
1981
|
-
if (setupStep === 'botSetup') {
|
|
1982
|
-
const botSetup = await collectBotSetupStepWithGroupBack({ isVi, channelKey, channel, existingBots: bots, existingBotCount: botCount, existingGroupId: groupId });
|
|
1983
|
-
if (botSetup.back) {
|
|
1984
|
-
setupStep = 'channel';
|
|
1985
|
-
continue;
|
|
1986
|
-
}
|
|
1987
|
-
botCount = botSetup.botCount;
|
|
1988
|
-
groupId = botSetup.groupId;
|
|
1989
|
-
bots = botSetup.bots;
|
|
1990
|
-
botToken = botSetup.botToken;
|
|
1991
|
-
setupStep = 'userInfo';
|
|
1992
|
-
continue;
|
|
1993
|
-
}
|
|
1994
|
-
|
|
1995
|
-
if (setupStep === 'userInfo') {
|
|
1996
|
-
console.log(chalk.bold('\n' + (isVi ? '👤 Thông tin của bạn 👤' : '👤 About You 👤')));
|
|
1997
|
-
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 });
|
|
1998
|
-
if (nextUserInfo === CLI_BACK) {
|
|
1999
|
-
setupStep = 'botSetup';
|
|
2000
|
-
continue;
|
|
2001
|
-
}
|
|
2002
|
-
userInfo = nextUserInfo;
|
|
2003
|
-
setupStep = 'provider';
|
|
2004
|
-
continue;
|
|
2005
|
-
}
|
|
2006
|
-
|
|
2007
|
-
if (setupStep === 'provider') {
|
|
2008
|
-
const providerSetup = await collectProviderStep({ isVi, existingProviderKey: providerKey, existingProviderKeyVal: providerKeyVal, existingOllamaModel: selectedOllamaModel });
|
|
2009
|
-
if (providerSetup.back) {
|
|
2010
|
-
setupStep = 'userInfo';
|
|
2011
|
-
continue;
|
|
2012
|
-
}
|
|
2013
|
-
providerKey = providerSetup.providerKey;
|
|
2014
|
-
provider = providerSetup.provider;
|
|
2015
|
-
providerKeyVal = providerSetup.providerKeyVal;
|
|
2016
|
-
selectedOllamaModel = providerSetup.selectedOllamaModel;
|
|
2017
|
-
setupStep = 'skills';
|
|
2018
|
-
continue;
|
|
2019
|
-
}
|
|
2020
|
-
|
|
2021
|
-
if (setupStep === 'skills') {
|
|
2022
|
-
const skillSetup = await collectSkillsStep({ isVi, providerKey, existingSelectedSkills: selectedSkills, existingBrowserMode: browserMode, existingTtsOpenaiKey: ttsOpenaiKey, existingTtsElevenKey: ttsElevenKey, existingSmtpHost: smtpHost, existingSmtpPort: smtpPort, existingSmtpUser: smtpUser, existingSmtpPass: smtpPass });
|
|
2023
|
-
if (skillSetup.back) {
|
|
2024
|
-
setupStep = 'provider';
|
|
2025
|
-
continue;
|
|
2026
|
-
}
|
|
2027
|
-
selectedSkills = skillSetup.selectedSkills;
|
|
2028
|
-
browserMode = skillSetup.browserMode;
|
|
2029
|
-
ttsOpenaiKey = skillSetup.ttsOpenaiKey;
|
|
2030
|
-
ttsElevenKey = skillSetup.ttsElevenKey;
|
|
2031
|
-
smtpHost = skillSetup.smtpHost;
|
|
2032
|
-
smtpPort = skillSetup.smtpPort;
|
|
2033
|
-
smtpUser = skillSetup.smtpUser;
|
|
2034
|
-
smtpPass = skillSetup.smtpPass;
|
|
2035
|
-
setupStep = 'projectDir';
|
|
2036
|
-
continue;
|
|
2037
|
-
}
|
|
2038
|
-
|
|
2039
|
-
if (setupStep === 'projectDir') {
|
|
2040
|
-
const nextProjectDir = await inputWithBack({ message: isVi ? 'Thư mục cài đặt project:' : 'Project install directory:', defaultValue: projectDir, allowBack: true, isVi });
|
|
2041
|
-
if (nextProjectDir === CLI_BACK) {
|
|
2042
|
-
setupStep = 'skills';
|
|
2043
|
-
continue;
|
|
2044
|
-
}
|
|
2045
|
-
projectDir = nextProjectDir;
|
|
2046
|
-
break;
|
|
2047
|
-
}
|
|
2048
|
-
}
|
|
2049
|
-
|
|
2050
|
-
const isMultiBot = botCount > 1 && channelKey === 'telegram';
|
|
2051
|
-
const botName = bots[0].name;
|
|
2052
|
-
const botDesc = bots[0].desc;
|
|
2053
|
-
const botPersona = bots[0].persona;
|
|
2054
|
-
const agentId = String(botName || 'chat').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'chat';
|
|
2055
|
-
const modelsPrimary = providerKey === 'ollama' ? selectedOllamaModel : providerKey === '9router' ? 'smart-route' : provider.models?.[0]?.id || 'gpt-4o-mini';
|
|
2056
|
-
const hasBrowserDesktop = selectedSkills.includes('browser') && browserMode === 'desktop';
|
|
2057
|
-
const hasBrowserServer = selectedSkills.includes('browser') && browserMode === 'server';
|
|
1836
|
+
async function main() {
|
|
1837
|
+
const cliSubcommand = getCliSubcommand();
|
|
1838
|
+
if (cliSubcommand === 'upgrade') {
|
|
1839
|
+
await runUpgradeCommand();
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
console.log(chalk.red('\n=================================='));
|
|
1844
|
+
console.log(chalk.redBright(LOGO));
|
|
1845
|
+
console.log(chalk.greenBright(' OpenClaw Auto Setup CLI '));
|
|
1846
|
+
console.log(chalk.red('==================================\n'));
|
|
1847
|
+
|
|
1848
|
+
let lang = 'vi';
|
|
1849
|
+
let isVi = true;
|
|
1850
|
+
const detectedPlatform = process.platform;
|
|
1851
|
+
const detectedOS = detectedPlatform === 'win32' ? 'windows'
|
|
1852
|
+
: detectedPlatform === 'darwin' ? 'macos'
|
|
1853
|
+
: 'linux';
|
|
1854
|
+
let osChoice = detectedOS === 'linux' ? 'vps' : detectedOS;
|
|
1855
|
+
let deployMode = 'docker';
|
|
1856
|
+
let channelKey = 'telegram';
|
|
1857
|
+
let channel = CHANNELS[channelKey];
|
|
1858
|
+
let botToken = '';
|
|
1859
|
+
let botCount = 1;
|
|
1860
|
+
let bots = [];
|
|
1861
|
+
let groupId = '';
|
|
1862
|
+
let userInfo = '';
|
|
1863
|
+
let providerKey = '9router';
|
|
1864
|
+
let provider = PROVIDERS[providerKey];
|
|
1865
|
+
let providerKeyVal = '';
|
|
1866
|
+
let selectedOllamaModel = 'gemma4:e2b';
|
|
1867
|
+
let selectedSkills = [];
|
|
1868
|
+
let tavilyKey = '';
|
|
1869
|
+
let browserMode = 'server';
|
|
1870
|
+
let ttsOpenaiKey = '';
|
|
1871
|
+
let ttsElevenKey = '';
|
|
1872
|
+
let smtpHost = 'smtp.gmail.com', smtpPort = '587', smtpUser = '', smtpPass = '';
|
|
1873
|
+
let defaultDir = process.cwd();
|
|
1874
|
+
if (!defaultDir.endsWith('openclaw-setup') && !defaultDir.endsWith('openclaw')) {
|
|
1875
|
+
defaultDir = path.join(defaultDir, 'openclaw-setup');
|
|
1876
|
+
}
|
|
1877
|
+
let projectDir = defaultDir;
|
|
1878
|
+
|
|
1879
|
+
let setupStep = 'language';
|
|
1880
|
+
while (true) {
|
|
1881
|
+
if (setupStep === 'language') {
|
|
1882
|
+
lang = await select({
|
|
1883
|
+
message: 'Select language / Chọn ngôn ngữ:',
|
|
1884
|
+
choices: [
|
|
1885
|
+
{ name: 'Tiếng Việt', value: 'vi' },
|
|
1886
|
+
{ name: 'English', value: 'en' }
|
|
1887
|
+
],
|
|
1888
|
+
default: lang
|
|
1889
|
+
});
|
|
1890
|
+
isVi = lang === 'vi';
|
|
1891
|
+
setupStep = 'os';
|
|
1892
|
+
continue;
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
if (setupStep === 'os') {
|
|
1896
|
+
const nextOsChoice = await selectWithBack({
|
|
1897
|
+
message: isVi ? 'Bạn đang chạy trên hệ điều hành nào?' : 'What OS are you running on?',
|
|
1898
|
+
choices: [
|
|
1899
|
+
{ name: isVi ? '🪟 Windows' : '🪟 Windows', value: 'windows' },
|
|
1900
|
+
{ name: isVi ? '🍎 macOS' : '🍎 macOS', value: 'macos' },
|
|
1901
|
+
{ name: isVi ? '🐧 Ubuntu Desktop' : '🐧 Ubuntu Desktop', value: 'ubuntu' },
|
|
1902
|
+
{ name: isVi ? '🖥️ VPS / Ubuntu Server' : '🖥️ VPS / Ubuntu Server', value: 'vps' },
|
|
1903
|
+
],
|
|
1904
|
+
defaultValue: osChoice,
|
|
1905
|
+
allowBack: true,
|
|
1906
|
+
isVi,
|
|
1907
|
+
});
|
|
1908
|
+
if (nextOsChoice === CLI_BACK) {
|
|
1909
|
+
setupStep = 'language';
|
|
1910
|
+
continue;
|
|
1911
|
+
}
|
|
1912
|
+
osChoice = nextOsChoice;
|
|
1913
|
+
setupStep = 'deploy';
|
|
1914
|
+
continue;
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
if (setupStep === 'deploy') {
|
|
1918
|
+
const deployModeDefault = (osChoice === 'ubuntu' || osChoice === 'vps') ? 'native' : 'docker';
|
|
1919
|
+
const nextDeployMode = await selectWithBack({
|
|
1920
|
+
message: isVi ? 'Chọn cách chạy bot:' : 'How do you want to run the bot?',
|
|
1921
|
+
choices: [
|
|
1922
|
+
{ 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' },
|
|
1923
|
+
{ 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' }
|
|
1924
|
+
],
|
|
1925
|
+
defaultValue: deployMode || deployModeDefault,
|
|
1926
|
+
allowBack: true,
|
|
1927
|
+
isVi,
|
|
1928
|
+
});
|
|
1929
|
+
if (nextDeployMode === CLI_BACK) {
|
|
1930
|
+
setupStep = 'os';
|
|
1931
|
+
continue;
|
|
1932
|
+
}
|
|
1933
|
+
deployMode = nextDeployMode;
|
|
1934
|
+
if (deployMode === 'docker' && !isDockerInstalled()) {
|
|
1935
|
+
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...'));
|
|
1936
|
+
try {
|
|
1937
|
+
const platform = process.platform;
|
|
1938
|
+
if (platform === 'win32') {
|
|
1939
|
+
execSync('winget install -e --id Docker.DockerDesktop --accept-source-agreements --accept-package-agreements', { stdio: 'inherit' });
|
|
1940
|
+
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.'));
|
|
1941
|
+
process.exit(0);
|
|
1942
|
+
} else if (platform === 'darwin') {
|
|
1943
|
+
execSync('brew install --cask docker', { stdio: 'inherit' });
|
|
1944
|
+
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.'));
|
|
1945
|
+
process.exit(0);
|
|
1946
|
+
} else {
|
|
1947
|
+
execSync('curl -fsSL https://get.docker.com | sh', { stdio: 'inherit', shell: true });
|
|
1948
|
+
try { execSync('apt-get install -y docker-compose-plugin', { stdio: 'ignore', shell: true }); } catch {}
|
|
1949
|
+
console.log(chalk.green(isVi ? '✅ Docker Engine + Compose plugin đã cài xong.' : '✅ Docker Engine + Compose plugin installed.'));
|
|
1950
|
+
}
|
|
1951
|
+
} catch {
|
|
1952
|
+
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/'));
|
|
1953
|
+
process.exit(1);
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
setupStep = 'channel';
|
|
1957
|
+
continue;
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
if (setupStep === 'channel') {
|
|
1961
|
+
const nextChannelKey = await selectWithBack({
|
|
1962
|
+
message: isVi ? 'Chọn nền tảng bot:' : 'Select bot platform:',
|
|
1963
|
+
choices: Object.entries(CHANNELS).map(([k, v]) => ({ name: v.icon + ' ' + v.name, value: k })),
|
|
1964
|
+
defaultValue: channelKey,
|
|
1965
|
+
allowBack: true,
|
|
1966
|
+
isVi,
|
|
1967
|
+
});
|
|
1968
|
+
if (nextChannelKey === CLI_BACK) {
|
|
1969
|
+
setupStep = 'deploy';
|
|
1970
|
+
continue;
|
|
1971
|
+
}
|
|
1972
|
+
channelKey = nextChannelKey;
|
|
1973
|
+
channel = CHANNELS[channelKey];
|
|
1974
|
+
if (channelKey === 'zalo-bot') {
|
|
1975
|
+
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.')));
|
|
1976
|
+
}
|
|
1977
|
+
setupStep = 'botSetup';
|
|
1978
|
+
continue;
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
if (setupStep === 'botSetup') {
|
|
1982
|
+
const botSetup = await collectBotSetupStepWithGroupBack({ isVi, channelKey, channel, existingBots: bots, existingBotCount: botCount, existingGroupId: groupId });
|
|
1983
|
+
if (botSetup.back) {
|
|
1984
|
+
setupStep = 'channel';
|
|
1985
|
+
continue;
|
|
1986
|
+
}
|
|
1987
|
+
botCount = botSetup.botCount;
|
|
1988
|
+
groupId = botSetup.groupId;
|
|
1989
|
+
bots = botSetup.bots;
|
|
1990
|
+
botToken = botSetup.botToken;
|
|
1991
|
+
setupStep = 'userInfo';
|
|
1992
|
+
continue;
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
if (setupStep === 'userInfo') {
|
|
1996
|
+
console.log(chalk.bold('\n' + (isVi ? '👤 Thông tin của bạn 👤' : '👤 About You 👤')));
|
|
1997
|
+
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 });
|
|
1998
|
+
if (nextUserInfo === CLI_BACK) {
|
|
1999
|
+
setupStep = 'botSetup';
|
|
2000
|
+
continue;
|
|
2001
|
+
}
|
|
2002
|
+
userInfo = nextUserInfo;
|
|
2003
|
+
setupStep = 'provider';
|
|
2004
|
+
continue;
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
if (setupStep === 'provider') {
|
|
2008
|
+
const providerSetup = await collectProviderStep({ isVi, existingProviderKey: providerKey, existingProviderKeyVal: providerKeyVal, existingOllamaModel: selectedOllamaModel });
|
|
2009
|
+
if (providerSetup.back) {
|
|
2010
|
+
setupStep = 'userInfo';
|
|
2011
|
+
continue;
|
|
2012
|
+
}
|
|
2013
|
+
providerKey = providerSetup.providerKey;
|
|
2014
|
+
provider = providerSetup.provider;
|
|
2015
|
+
providerKeyVal = providerSetup.providerKeyVal;
|
|
2016
|
+
selectedOllamaModel = providerSetup.selectedOllamaModel;
|
|
2017
|
+
setupStep = 'skills';
|
|
2018
|
+
continue;
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
if (setupStep === 'skills') {
|
|
2022
|
+
const skillSetup = await collectSkillsStep({ isVi, providerKey, existingSelectedSkills: selectedSkills, existingBrowserMode: browserMode, existingTtsOpenaiKey: ttsOpenaiKey, existingTtsElevenKey: ttsElevenKey, existingSmtpHost: smtpHost, existingSmtpPort: smtpPort, existingSmtpUser: smtpUser, existingSmtpPass: smtpPass });
|
|
2023
|
+
if (skillSetup.back) {
|
|
2024
|
+
setupStep = 'provider';
|
|
2025
|
+
continue;
|
|
2026
|
+
}
|
|
2027
|
+
selectedSkills = skillSetup.selectedSkills;
|
|
2028
|
+
browserMode = skillSetup.browserMode;
|
|
2029
|
+
ttsOpenaiKey = skillSetup.ttsOpenaiKey;
|
|
2030
|
+
ttsElevenKey = skillSetup.ttsElevenKey;
|
|
2031
|
+
smtpHost = skillSetup.smtpHost;
|
|
2032
|
+
smtpPort = skillSetup.smtpPort;
|
|
2033
|
+
smtpUser = skillSetup.smtpUser;
|
|
2034
|
+
smtpPass = skillSetup.smtpPass;
|
|
2035
|
+
setupStep = 'projectDir';
|
|
2036
|
+
continue;
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
if (setupStep === 'projectDir') {
|
|
2040
|
+
const nextProjectDir = await inputWithBack({ message: isVi ? 'Thư mục cài đặt project:' : 'Project install directory:', defaultValue: projectDir, allowBack: true, isVi });
|
|
2041
|
+
if (nextProjectDir === CLI_BACK) {
|
|
2042
|
+
setupStep = 'skills';
|
|
2043
|
+
continue;
|
|
2044
|
+
}
|
|
2045
|
+
projectDir = nextProjectDir;
|
|
2046
|
+
break;
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
const isMultiBot = botCount > 1 && channelKey === 'telegram';
|
|
2051
|
+
const botName = bots[0].name;
|
|
2052
|
+
const botDesc = bots[0].desc;
|
|
2053
|
+
const botPersona = bots[0].persona;
|
|
2054
|
+
const agentId = String(botName || 'chat').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'chat';
|
|
2055
|
+
const modelsPrimary = providerKey === 'ollama' ? selectedOllamaModel : providerKey === '9router' ? 'smart-route' : provider.models?.[0]?.id || 'gpt-4o-mini';
|
|
2056
|
+
const hasBrowserDesktop = selectedSkills.includes('browser') && browserMode === 'desktop';
|
|
2057
|
+
const hasBrowserServer = selectedSkills.includes('browser') && browserMode === 'server';
|
|
2058
2058
|
console.log(chalk.cyan(`\n🚀 ${isVi ? 'Đang tạo thư mục và file cấu hình...' : 'Generating directories and configurations...'}`));
|
|
2059
2059
|
|
|
2060
2060
|
await fs.ensureDir(projectDir);
|
|
@@ -2156,7 +2156,7 @@ async function main() {
|
|
|
2156
2156
|
socatBridge,
|
|
2157
2157
|
deviceApproveLoop,
|
|
2158
2158
|
].filter(Boolean),
|
|
2159
|
-
volumeMount: '../../.openclaw:/root/project/.openclaw',
|
|
2159
|
+
volumeMount: '../../.openclaw:/root/project/.openclaw',
|
|
2160
2160
|
singleComposeName: `oc-${agentId}`,
|
|
2161
2161
|
multiComposeName: 'oc-multibot',
|
|
2162
2162
|
singleAppContainerName: `openclaw-${agentId}`,
|
|
@@ -2215,13 +2215,13 @@ async function main() {
|
|
|
2215
2215
|
} else if (providerKey === '9router') {
|
|
2216
2216
|
authProfilesJson = {
|
|
2217
2217
|
version: 1,
|
|
2218
|
-
profiles: {
|
|
2219
|
-
'9router-proxy': {
|
|
2220
|
-
provider: '9router',
|
|
2221
|
-
type: 'api_key',
|
|
2222
|
-
key: NINE_ROUTER_PROXY_API_KEY,
|
|
2223
|
-
},
|
|
2224
|
-
},
|
|
2218
|
+
profiles: {
|
|
2219
|
+
'9router-proxy': {
|
|
2220
|
+
provider: '9router',
|
|
2221
|
+
type: 'api_key',
|
|
2222
|
+
key: NINE_ROUTER_PROXY_API_KEY,
|
|
2223
|
+
},
|
|
2224
|
+
},
|
|
2225
2225
|
order: { '9router': ['9router-proxy'] },
|
|
2226
2226
|
};
|
|
2227
2227
|
}
|
|
@@ -2248,9 +2248,9 @@ async function main() {
|
|
|
2248
2248
|
workspaceDir: `workspace-${agentSlug}`,
|
|
2249
2249
|
};
|
|
2250
2250
|
});
|
|
2251
|
-
const telegramAccounts = Object.fromEntries(agentMetas.map((meta) => [meta.accountId, {
|
|
2252
|
-
botToken: meta.token,
|
|
2253
|
-
}]));
|
|
2251
|
+
const telegramAccounts = Object.fromEntries(agentMetas.map((meta) => [meta.accountId, {
|
|
2252
|
+
botToken: meta.token,
|
|
2253
|
+
}]));
|
|
2254
2254
|
const telegramChannelConfig = {
|
|
2255
2255
|
enabled: true,
|
|
2256
2256
|
defaultAccount: 'default',
|
|
@@ -2293,14 +2293,14 @@ async function main() {
|
|
|
2293
2293
|
model: { primary: modelsPrimary, fallbacks: [] },
|
|
2294
2294
|
})),
|
|
2295
2295
|
},
|
|
2296
|
-
...(providerKey === '9router' ? {
|
|
2297
|
-
models: {
|
|
2298
|
-
mode: 'merge',
|
|
2299
|
-
providers: {
|
|
2300
|
-
'9router': build9RouterProviderConfig(get9RouterBaseUrl(deployMode)),
|
|
2301
|
-
},
|
|
2302
|
-
},
|
|
2303
|
-
} : provider.isLocal ? {
|
|
2296
|
+
...(providerKey === '9router' ? {
|
|
2297
|
+
models: {
|
|
2298
|
+
mode: 'merge',
|
|
2299
|
+
providers: {
|
|
2300
|
+
'9router': build9RouterProviderConfig(get9RouterBaseUrl(deployMode)),
|
|
2301
|
+
},
|
|
2302
|
+
},
|
|
2303
|
+
} : provider.isLocal ? {
|
|
2304
2304
|
models: {
|
|
2305
2305
|
mode: 'merge',
|
|
2306
2306
|
providers: {
|
|
@@ -2355,9 +2355,9 @@ async function main() {
|
|
|
2355
2355
|
sharedConfig.skills = { entries: skillEntries };
|
|
2356
2356
|
}
|
|
2357
2357
|
|
|
2358
|
-
await fs.writeJson(path.join(rootClawDir, 'openclaw.json'), sharedConfig, { spaces: 2 });
|
|
2359
|
-
await fs.writeFile(
|
|
2360
|
-
path.join(projectDir, TELEGRAM_SETUP_GUIDE_FILENAME),
|
|
2358
|
+
await fs.writeJson(path.join(rootClawDir, 'openclaw.json'), sharedConfig, { spaces: 2 });
|
|
2359
|
+
await fs.writeFile(
|
|
2360
|
+
path.join(projectDir, TELEGRAM_SETUP_GUIDE_FILENAME),
|
|
2361
2361
|
buildTelegramPostInstallChecklist({ isVi, bots, groupId }),
|
|
2362
2362
|
'utf8',
|
|
2363
2363
|
);
|
|
@@ -2379,7 +2379,7 @@ async function main() {
|
|
|
2379
2379
|
const safeRootClawDir = rootClawDir.replace(/\\/g, '/');
|
|
2380
2380
|
const pm2Apps = [
|
|
2381
2381
|
' {',
|
|
2382
|
-
` name: 'openclaw-multibot',`,
|
|
2382
|
+
` name: 'openclaw-multibot',`,
|
|
2383
2383
|
` script: 'openclaw',`,
|
|
2384
2384
|
` args: 'gateway run',`,
|
|
2385
2385
|
` cwd: '${projectDir.replace(/\\/g, '/')}',`,
|
|
@@ -2456,6 +2456,7 @@ async function main() {
|
|
|
2456
2456
|
const loopBotName = isMultiBot ? (bots[bIndex]?.name || `Bot ${bIndex+1}`) : botName;
|
|
2457
2457
|
const loopBotDesc = isMultiBot ? (bots[bIndex]?.desc || '') : botDesc;
|
|
2458
2458
|
const loopBotPersona = isMultiBot ? (bots[bIndex]?.persona || '') : botPersona;
|
|
2459
|
+
const loopBotToken = isMultiBot ? (bots[bIndex]?.token || '') : botToken;
|
|
2459
2460
|
const teamRoster = bots.slice(0, numBotsToConfigure).map((peer, idx) => ({
|
|
2460
2461
|
idx,
|
|
2461
2462
|
name: peer?.name || `Bot ${idx + 1}`,
|
|
@@ -2492,14 +2493,14 @@ async function main() {
|
|
|
2492
2493
|
model: { primary: modelsPrimary, fallbacks: [] }
|
|
2493
2494
|
}]
|
|
2494
2495
|
},
|
|
2495
|
-
...(providerKey === '9router' ? {
|
|
2496
|
-
models: {
|
|
2497
|
-
mode: 'merge',
|
|
2498
|
-
providers: {
|
|
2499
|
-
'9router': build9RouterProviderConfig(get9RouterBaseUrl(deployMode))
|
|
2500
|
-
}
|
|
2501
|
-
}
|
|
2502
|
-
} : provider.isLocal ? {
|
|
2496
|
+
...(providerKey === '9router' ? {
|
|
2497
|
+
models: {
|
|
2498
|
+
mode: 'merge',
|
|
2499
|
+
providers: {
|
|
2500
|
+
'9router': build9RouterProviderConfig(get9RouterBaseUrl(deployMode))
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
} : provider.isLocal ? {
|
|
2503
2504
|
models: {
|
|
2504
2505
|
mode: 'merge',
|
|
2505
2506
|
providers: {
|
|
@@ -2544,56 +2545,56 @@ async function main() {
|
|
|
2544
2545
|
botConfig.skills = { entries: skillEntries };
|
|
2545
2546
|
}
|
|
2546
2547
|
|
|
2547
|
-
if (channelKey === 'telegram') {
|
|
2548
|
-
const telegramConfig = {
|
|
2549
|
-
enabled: true,
|
|
2550
|
-
dmPolicy: 'open',
|
|
2551
|
-
allowFrom: ['*'],
|
|
2552
|
-
defaultAccount: 'default',
|
|
2553
|
-
replyToMode: 'first',
|
|
2554
|
-
reactionLevel: 'minimal',
|
|
2555
|
-
actions: {
|
|
2556
|
-
sendMessage: true,
|
|
2557
|
-
reactions: true,
|
|
2558
|
-
},
|
|
2559
|
-
accounts: {
|
|
2560
|
-
default: {
|
|
2561
|
-
botToken: loopBotToken || '<your_bot_token>',
|
|
2562
|
-
},
|
|
2563
|
-
},
|
|
2564
|
-
};
|
|
2565
|
-
if (isMultiBot) {
|
|
2566
|
-
telegramConfig.groupPolicy = groupId ? 'allowlist' : 'open';
|
|
2567
|
-
telegramConfig.groupAllowFrom = ['*'];
|
|
2568
|
-
telegramConfig.groups = {
|
|
2569
|
-
[groupId || '*']: { enabled: true, requireMention: false }
|
|
2548
|
+
if (channelKey === 'telegram') {
|
|
2549
|
+
const telegramConfig = {
|
|
2550
|
+
enabled: true,
|
|
2551
|
+
dmPolicy: 'open',
|
|
2552
|
+
allowFrom: ['*'],
|
|
2553
|
+
defaultAccount: 'default',
|
|
2554
|
+
replyToMode: 'first',
|
|
2555
|
+
reactionLevel: 'minimal',
|
|
2556
|
+
actions: {
|
|
2557
|
+
sendMessage: true,
|
|
2558
|
+
reactions: true,
|
|
2559
|
+
},
|
|
2560
|
+
accounts: {
|
|
2561
|
+
default: {
|
|
2562
|
+
botToken: loopBotToken || '<your_bot_token>',
|
|
2563
|
+
},
|
|
2564
|
+
},
|
|
2565
|
+
};
|
|
2566
|
+
if (isMultiBot) {
|
|
2567
|
+
telegramConfig.groupPolicy = groupId ? 'allowlist' : 'open';
|
|
2568
|
+
telegramConfig.groupAllowFrom = ['*'];
|
|
2569
|
+
telegramConfig.groups = {
|
|
2570
|
+
[groupId || '*']: { enabled: true, requireMention: false }
|
|
2570
2571
|
};
|
|
2571
2572
|
}
|
|
2572
2573
|
botConfig.channels['telegram'] = telegramConfig;
|
|
2573
|
-
} else if (hasZaloPersonal(channelKey)) {
|
|
2574
|
-
botConfig.channels['zalouser'] = {
|
|
2575
|
-
enabled: true,
|
|
2576
|
-
defaultAccount: 'default',
|
|
2577
|
-
dmPolicy: 'open',
|
|
2578
|
-
allowFrom: ['*'],
|
|
2579
|
-
groupPolicy: 'allowlist',
|
|
2580
|
-
groupAllowFrom: ['*'],
|
|
2581
|
-
historyLimit: 50,
|
|
2582
|
-
groups: {
|
|
2583
|
-
'*': {
|
|
2584
|
-
enabled: true,
|
|
2585
|
-
requireMention: false,
|
|
2586
|
-
},
|
|
2587
|
-
},
|
|
2588
|
-
autoReply: true,
|
|
2589
|
-
};
|
|
2574
|
+
} else if (hasZaloPersonal(channelKey)) {
|
|
2575
|
+
botConfig.channels['zalouser'] = {
|
|
2576
|
+
enabled: true,
|
|
2577
|
+
defaultAccount: 'default',
|
|
2578
|
+
dmPolicy: 'open',
|
|
2579
|
+
allowFrom: ['*'],
|
|
2580
|
+
groupPolicy: 'allowlist',
|
|
2581
|
+
groupAllowFrom: ['*'],
|
|
2582
|
+
historyLimit: 50,
|
|
2583
|
+
groups: {
|
|
2584
|
+
'*': {
|
|
2585
|
+
enabled: true,
|
|
2586
|
+
requireMention: false,
|
|
2587
|
+
},
|
|
2588
|
+
},
|
|
2589
|
+
autoReply: true,
|
|
2590
|
+
};
|
|
2590
2591
|
} else if (channelKey === 'zalo-bot') {
|
|
2591
2592
|
botConfig.channels['zalo'] = { enabled: true, provider: 'official_account' };
|
|
2592
2593
|
}
|
|
2593
2594
|
|
|
2594
|
-
await fs.writeJson(path.join(loopBotDir, '.openclaw', 'openclaw.json'), botConfig, { spaces: 2 });
|
|
2595
|
-
|
|
2596
|
-
// ── Workspace files: use shared writeWorkspaceFiles() ──────────────────────
|
|
2595
|
+
await fs.writeJson(path.join(loopBotDir, '.openclaw', 'openclaw.json'), botConfig, { spaces: 2 });
|
|
2596
|
+
|
|
2597
|
+
// ── Workspace files: use shared writeWorkspaceFiles() ──────────────────────
|
|
2597
2598
|
const dockerWorkspaceDir = path.join(loopBotDir, '.openclaw', loopWorkspaceDir);
|
|
2598
2599
|
const dockerOwnAliases = [loopBotName, bots[bIndex]?.slashCmd || '', `bot ${bIndex + 1}`].filter(Boolean);
|
|
2599
2600
|
const dockerOtherAgents = teamRoster
|
|
@@ -2624,8 +2625,8 @@ async function main() {
|
|
|
2624
2625
|
// Append per-bot reply rules to AGENTS.md
|
|
2625
2626
|
const otherBotNames = teamRoster.filter((p) => p.idx !== bIndex).map((p) => p.name);
|
|
2626
2627
|
const extraAgentsMd = isVi
|
|
2627
|
-
? `\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.`
|
|
2628
|
-
: `\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.`;
|
|
2628
|
+
? `\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.`
|
|
2629
|
+
: `\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.`;
|
|
2629
2630
|
await fs.appendFile(path.join(dockerWorkspaceDir, 'AGENTS.md'), extraAgentsMd);
|
|
2630
2631
|
}
|
|
2631
2632
|
} // END FOR LOOP
|
|
@@ -2635,14 +2636,14 @@ async function main() {
|
|
|
2635
2636
|
await writeGeneratedArtifacts(projectDir, buildCliChromeDebugArtifacts());
|
|
2636
2637
|
|
|
2637
2638
|
// ── Uninstall scripts ───────────────────────────────────────────────────────
|
|
2638
|
-
await writeGeneratedArtifacts(projectDir, buildCliUninstallArtifacts({
|
|
2639
|
-
deployMode,
|
|
2640
|
-
osChoice: detectedOS,
|
|
2641
|
-
projectDir,
|
|
2642
|
-
botName: (deployMode !== 'docker' && detectedOS === 'vps')
|
|
2643
|
-
? getNativePm2AppName(isMultiBot)
|
|
2644
|
-
: botName,
|
|
2645
|
-
}));
|
|
2639
|
+
await writeGeneratedArtifacts(projectDir, buildCliUninstallArtifacts({
|
|
2640
|
+
deployMode,
|
|
2641
|
+
osChoice: detectedOS,
|
|
2642
|
+
projectDir,
|
|
2643
|
+
botName: (deployMode !== 'docker' && detectedOS === 'vps')
|
|
2644
|
+
? getNativePm2AppName(isMultiBot)
|
|
2645
|
+
: botName,
|
|
2646
|
+
}));
|
|
2646
2647
|
|
|
2647
2648
|
// ── Upgrade scripts ─────────────────────────────────────────────────────────
|
|
2648
2649
|
await writeGeneratedArtifacts(projectDir, buildCliUpgradeArtifacts());
|
|
@@ -2650,33 +2651,33 @@ async function main() {
|
|
|
2650
2651
|
// ── start-bot.bat / start-bot.sh — one-click restart scripts ─────────────
|
|
2651
2652
|
// Generated for native deployments only (docker has docker compose up)
|
|
2652
2653
|
if (deployMode !== 'docker') {
|
|
2653
|
-
await writeGeneratedArtifacts(projectDir, buildCliStartBotArtifacts({
|
|
2654
|
-
projectDir,
|
|
2655
|
-
openclawHome: path.join(projectDir, '.openclaw'),
|
|
2656
|
-
is9Router: providerKey === '9router',
|
|
2657
|
-
osChoice,
|
|
2658
|
-
isMultiBot,
|
|
2659
|
-
appName: getNativePm2AppName(isMultiBot),
|
|
2660
|
-
isVi,
|
|
2661
|
-
}));
|
|
2662
|
-
|
|
2663
|
-
console.log(chalk.cyan(
|
|
2664
|
-
process.platform === 'win32'
|
|
2665
|
-
? (isVi
|
|
2666
|
-
? `\n🚀 start-bot.bat / start-bot.sh đã tạo — double-click để restart bot.`
|
|
2667
|
-
: `\n🚀 start-bot.bat / start-bot.sh created — double-click to restart the bot.`)
|
|
2668
|
-
: (isVi
|
|
2669
|
-
? `\n🚀 start-bot.sh đã tạo — chạy ./start-bot.sh để restart bot.`
|
|
2670
|
-
: `\n🚀 start-bot.sh created — run ./start-bot.sh to restart the bot.`)
|
|
2671
|
-
));
|
|
2672
|
-
}
|
|
2673
|
-
|
|
2674
|
-
console.log(chalk.green(`✅ ${isVi ? 'Tạo cấu hình thành công!' : 'Configs created successfully!'}`));
|
|
2675
|
-
|
|
2676
|
-
installLatestOpenClaw({ isVi, osChoice });
|
|
2677
|
-
|
|
2678
|
-
// 7. Auto Run
|
|
2679
|
-
const autoRun = deployMode === 'docker' ? await confirm({
|
|
2654
|
+
await writeGeneratedArtifacts(projectDir, buildCliStartBotArtifacts({
|
|
2655
|
+
projectDir,
|
|
2656
|
+
openclawHome: path.join(projectDir, '.openclaw'),
|
|
2657
|
+
is9Router: providerKey === '9router',
|
|
2658
|
+
osChoice,
|
|
2659
|
+
isMultiBot,
|
|
2660
|
+
appName: getNativePm2AppName(isMultiBot),
|
|
2661
|
+
isVi,
|
|
2662
|
+
}));
|
|
2663
|
+
|
|
2664
|
+
console.log(chalk.cyan(
|
|
2665
|
+
process.platform === 'win32'
|
|
2666
|
+
? (isVi
|
|
2667
|
+
? `\n🚀 start-bot.bat / start-bot.sh đã tạo — double-click để restart bot.`
|
|
2668
|
+
: `\n🚀 start-bot.bat / start-bot.sh created — double-click to restart the bot.`)
|
|
2669
|
+
: (isVi
|
|
2670
|
+
? `\n🚀 start-bot.sh đã tạo — chạy ./start-bot.sh để restart bot.`
|
|
2671
|
+
: `\n🚀 start-bot.sh created — run ./start-bot.sh to restart the bot.`)
|
|
2672
|
+
));
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2675
|
+
console.log(chalk.green(`✅ ${isVi ? 'Tạo cấu hình thành công!' : 'Configs created successfully!'}`));
|
|
2676
|
+
|
|
2677
|
+
installLatestOpenClaw({ isVi, osChoice });
|
|
2678
|
+
|
|
2679
|
+
// 7. Auto Run
|
|
2680
|
+
const autoRun = deployMode === 'docker' ? await confirm({
|
|
2680
2681
|
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?',
|
|
2681
2682
|
default: true
|
|
2682
2683
|
}) : false;
|
|
@@ -2756,7 +2757,7 @@ async function main() {
|
|
|
2756
2757
|
});
|
|
2757
2758
|
|
|
2758
2759
|
}
|
|
2759
|
-
if (deployMode === 'docker') {
|
|
2760
|
+
if (deployMode === 'docker') {
|
|
2760
2761
|
|
|
2761
2762
|
// ── Auto-install openclaw binary if not present ──────────────────────────
|
|
2762
2763
|
const isOpenClawInstalled = () => { try { execSync('openclaw --version', { stdio: 'ignore' }); return true; } catch { return false; } };
|
|
@@ -2804,19 +2805,19 @@ async function main() {
|
|
|
2804
2805
|
}
|
|
2805
2806
|
}
|
|
2806
2807
|
|
|
2807
|
-
let native9RouterSyncScriptPath = null;
|
|
2808
|
-
if (providerKey === '9router') {
|
|
2809
|
-
await writeNative9RouterPatchScript(projectDir);
|
|
2810
|
-
native9RouterSyncScriptPath = await writeNative9RouterSyncScript(projectDir);
|
|
2811
|
-
try {
|
|
2812
|
-
execFileSync(process.execPath, [path.join(projectDir, '.openclaw', 'patch-9router.js')], {
|
|
2813
|
-
cwd: projectDir,
|
|
2814
|
-
stdio: 'ignore',
|
|
2815
|
-
});
|
|
2816
|
-
} catch {
|
|
2817
|
-
// Start scripts retry this patch before launching 9router.
|
|
2818
|
-
}
|
|
2819
|
-
}
|
|
2808
|
+
let native9RouterSyncScriptPath = null;
|
|
2809
|
+
if (providerKey === '9router') {
|
|
2810
|
+
await writeNative9RouterPatchScript(projectDir);
|
|
2811
|
+
native9RouterSyncScriptPath = await writeNative9RouterSyncScript(projectDir);
|
|
2812
|
+
try {
|
|
2813
|
+
execFileSync(process.execPath, [path.join(projectDir, '.openclaw', 'patch-9router.js')], {
|
|
2814
|
+
cwd: projectDir,
|
|
2815
|
+
stdio: 'ignore',
|
|
2816
|
+
});
|
|
2817
|
+
} catch {
|
|
2818
|
+
// Start scripts retry this patch before launching 9router.
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2820
2821
|
|
|
2821
2822
|
await ensureProjectRuntimeDirs(projectDir, isVi);
|
|
2822
2823
|
|
|
@@ -2832,26 +2833,26 @@ async function main() {
|
|
|
2832
2833
|
}
|
|
2833
2834
|
}
|
|
2834
2835
|
|
|
2835
|
-
if (isMultiBot && channelKey === 'telegram') {
|
|
2836
|
-
if (providerKey === '9router') {
|
|
2837
|
-
startNative9RouterPm2({ isVi, projectDir, appName: getNativePm2AppName(true), syncScriptPath: native9RouterSyncScriptPath });
|
|
2838
|
-
}
|
|
2839
|
-
execSync('pm2 start ecosystem.config.js && pm2 save', {
|
|
2840
|
-
cwd: projectDir,
|
|
2841
|
-
stdio: 'inherit',
|
|
2842
|
-
shell: true
|
|
2843
|
-
});
|
|
2844
|
-
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.'}`));
|
|
2845
|
-
console.log(chalk.gray(isVi ? ` Xem log: pm2 logs ${getNativePm2AppName(true)}` : ` View logs: pm2 logs ${getNativePm2AppName(true)}`));
|
|
2846
|
-
printNativeDashboardAccessInfo({ isVi, providerKey, projectDir });
|
|
2847
|
-
if (channelKey === 'zalo-personal') {
|
|
2848
|
-
printZaloPersonalLoginInfo({ isVi, deployMode: 'native', projectDir });
|
|
2849
|
-
}
|
|
2850
|
-
} else {
|
|
2851
|
-
const appName = getNativePm2AppName(false);
|
|
2852
|
-
if (providerKey === '9router') {
|
|
2853
|
-
startNative9RouterPm2({ isVi, projectDir, appName, syncScriptPath: native9RouterSyncScriptPath });
|
|
2854
|
-
}
|
|
2836
|
+
if (isMultiBot && channelKey === 'telegram') {
|
|
2837
|
+
if (providerKey === '9router') {
|
|
2838
|
+
startNative9RouterPm2({ isVi, projectDir, appName: getNativePm2AppName(true), syncScriptPath: native9RouterSyncScriptPath });
|
|
2839
|
+
}
|
|
2840
|
+
execSync('pm2 start ecosystem.config.js && pm2 save', {
|
|
2841
|
+
cwd: projectDir,
|
|
2842
|
+
stdio: 'inherit',
|
|
2843
|
+
shell: true
|
|
2844
|
+
});
|
|
2845
|
+
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.'}`));
|
|
2846
|
+
console.log(chalk.gray(isVi ? ` Xem log: pm2 logs ${getNativePm2AppName(true)}` : ` View logs: pm2 logs ${getNativePm2AppName(true)}`));
|
|
2847
|
+
printNativeDashboardAccessInfo({ isVi, providerKey, projectDir });
|
|
2848
|
+
if (channelKey === 'zalo-personal') {
|
|
2849
|
+
printZaloPersonalLoginInfo({ isVi, deployMode: 'native', projectDir });
|
|
2850
|
+
}
|
|
2851
|
+
} else {
|
|
2852
|
+
const appName = getNativePm2AppName(false);
|
|
2853
|
+
if (providerKey === '9router') {
|
|
2854
|
+
startNative9RouterPm2({ isVi, projectDir, appName, syncScriptPath: native9RouterSyncScriptPath });
|
|
2855
|
+
}
|
|
2855
2856
|
if (channelKey === 'zalo-personal') {
|
|
2856
2857
|
await runNativeZaloPersonalLoginFlow({ isVi, projectDir });
|
|
2857
2858
|
}
|
|
@@ -2886,10 +2887,10 @@ async function main() {
|
|
|
2886
2887
|
cwd: projectDir,
|
|
2887
2888
|
env: getProjectRuntimeEnv(projectDir, native9RouterLaunch.env)
|
|
2888
2889
|
}).unref();
|
|
2889
|
-
const routerHealth = await waitFor9RouterApiReady();
|
|
2890
|
-
if (native9RouterSyncScriptPath) {
|
|
2891
|
-
spawnBackgroundProcess(process.execPath, [native9RouterSyncScriptPath], {
|
|
2892
|
-
cwd: projectDir
|
|
2890
|
+
const routerHealth = await waitFor9RouterApiReady();
|
|
2891
|
+
if (native9RouterSyncScriptPath) {
|
|
2892
|
+
spawnBackgroundProcess(process.execPath, [native9RouterSyncScriptPath], {
|
|
2893
|
+
cwd: projectDir
|
|
2893
2894
|
}).unref();
|
|
2894
2895
|
}
|
|
2895
2896
|
console.log(chalk.gray(isVi
|
|
@@ -2935,9 +2936,9 @@ async function main() {
|
|
|
2935
2936
|
console.log(chalk.yellow(`\n📋 ${isVi ? 'Xem huong dan sau cai:' : 'Read post-install guide:'} ${path.join(projectDir, TELEGRAM_SETUP_GUIDE_FILENAME)}`));
|
|
2936
2937
|
}
|
|
2937
2938
|
}
|
|
2938
|
-
}
|
|
2939
|
-
|
|
2940
|
-
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
|
|
2941
2942
|
|
|
2942
2943
|
main().catch(err => {
|
|
2943
2944
|
console.error(chalk.red('Error:'), err);
|