create-openclaw-bot 5.1.15 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/style.css CHANGED
@@ -788,15 +788,17 @@ body::after {
788
788
  /* ============ Plugin Grid ============ */
789
789
  .plugin-grid {
790
790
  display: grid;
791
- grid-template-columns: repeat(3, 1fr);
792
- gap: 12px;
791
+ grid-template-columns: repeat(3, minmax(0, 1fr));
792
+ gap: 10px;
793
+ align-items: stretch;
794
+ width: 100%;
793
795
  }
794
796
 
795
797
  .plugin-card {
796
798
  display: flex;
797
- align-items: flex-start;
798
- gap: 12px;
799
- padding: 16px;
799
+ align-items: stretch;
800
+ gap: 0;
801
+ padding: 12px;
800
802
  background: var(--bg-glass);
801
803
  backdrop-filter: blur(12px);
802
804
  -webkit-backdrop-filter: blur(12px);
@@ -805,11 +807,16 @@ body::after {
805
807
  cursor: pointer;
806
808
  transition: all var(--duration-normal) var(--ease-out);
807
809
  position: relative;
810
+ min-height: 88px;
811
+ height: 100%;
812
+ min-width: 0;
813
+ overflow: visible;
808
814
  }
809
815
 
810
816
  .plugin-card:hover {
811
817
  background: var(--bg-card-hover);
812
818
  border-color: rgba(255, 255, 255, 0.15);
819
+ z-index: 5;
813
820
  }
814
821
 
815
822
  .plugin-card--selected {
@@ -824,59 +831,183 @@ body::after {
824
831
  height: 0;
825
832
  }
826
833
 
827
- .plugin-card__icon {
828
- font-size: 22px;
829
- flex-shrink: 0;
830
- margin-top: 2px;
834
+ .plugin-card__info {
835
+ width: 100%;
836
+ min-width: 0;
837
+ min-height: 62px;
838
+ display: flex;
839
+ flex-direction: column;
840
+ gap: 8px;
831
841
  }
832
842
 
833
- .plugin-card__info {
834
- flex: 1;
843
+ .plugin-card__topline {
844
+ display: flex;
845
+ align-items: center;
846
+ justify-content: space-between;
847
+ gap: 12px;
848
+ }
849
+
850
+ .plugin-card__titleline {
851
+ display: flex;
852
+ align-items: center;
853
+ gap: 10px;
835
854
  min-width: 0;
855
+ flex: 1;
856
+ }
857
+
858
+ .plugin-card__icon {
859
+ font-size: 20px;
860
+ flex-shrink: 0;
861
+ width: 24px;
862
+ text-align: center;
836
863
  }
837
864
 
838
865
  .plugin-card__name {
839
866
  font-size: 13px;
840
867
  font-weight: 600;
841
868
  color: var(--text-primary);
842
- margin-bottom: 4px;
869
+ line-height: 1.35;
870
+ display: -webkit-box;
871
+ -webkit-line-clamp: 2;
872
+ -webkit-box-orient: vertical;
873
+ overflow: hidden;
874
+ margin-bottom: 0;
875
+ padding-right: 0;
876
+ min-width: 0;
843
877
  }
844
878
 
845
- .plugin-card__desc {
846
- font-size: 11px;
847
- color: var(--text-muted);
848
- line-height: 1.4;
879
+ .plugin-card__subline {
880
+ display: flex;
881
+ align-items: center;
882
+ justify-content: flex-start;
883
+ padding-left: 5px;
884
+ gap: 10px;
885
+ min-height: 22px;
849
886
  }
850
887
 
851
- .plugin-card__note {
852
- font-size: 10px;
853
- color: rgba(255, 193, 7, 0.7);
854
- line-height: 1.3;
855
- margin-top: 3px;
888
+ .plugin-card__badge-slot {
889
+ width: auto;
890
+ min-width: 0;
891
+ display: flex;
892
+ justify-content: flex-end;
893
+ align-items: center;
856
894
  }
857
895
 
858
- .plugin-card__check {
859
- position: absolute;
860
- top: 8px;
861
- right: 10px;
862
- width: 20px;
863
- height: 20px;
864
- border-radius: 50%;
865
- background: rgba(255, 255, 255, 0.06);
866
- border: 1px solid rgba(255, 255, 255, 0.12);
896
+ .plugin-card__badge {
897
+ display: inline-flex;
898
+ align-items: center;
899
+ gap: 4px;
900
+ padding: 3px 8px;
901
+ border-radius: 999px;
902
+ font-size: 9px;
903
+ font-weight: 700;
904
+ white-space: nowrap;
905
+ flex-shrink: 0;
906
+ letter-spacing: 0.01em;
907
+ }
908
+
909
+ .plugin-card__badge--placeholder {
910
+ visibility: hidden;
911
+ }
912
+
913
+ .plugin-card__badge--recommended {
914
+ background: rgba(255, 107, 53, 0.14);
915
+ color: #ffd3bf;
916
+ border: 1px solid rgba(255, 107, 53, 0.34);
917
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
918
+ }
919
+
920
+ .plugin-card__hint-slot {
867
921
  display: flex;
922
+ justify-content: flex-start;
923
+ align-items: center;
924
+ }
925
+
926
+ .plugin-card__hint {
927
+ position: relative;
928
+ display: inline-flex;
868
929
  align-items: center;
869
930
  justify-content: center;
870
- font-size: 11px;
871
- color: transparent;
931
+ width: 22px;
932
+ height: 22px;
933
+ border-radius: 50%;
934
+ background: rgba(255, 255, 255, 0.08);
935
+ border: 1px solid rgba(255, 255, 255, 0.14);
936
+ color: rgba(255, 255, 255, 0.88);
937
+ font-size: 10px;
938
+ font-weight: 700;
872
939
  transition: all var(--duration-fast) var(--ease-out);
940
+ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
873
941
  }
874
942
 
875
- .plugin-card--selected .plugin-card__check {
876
- background: var(--claw-coral);
877
- border-color: var(--claw-coral);
878
- color: white;
879
- box-shadow: 0 0 10px rgba(255, 107, 53, 0.3);
943
+ .plugin-card__hint--placeholder {
944
+ visibility: hidden;
945
+ }
946
+
947
+ .plugin-card__hint:hover,
948
+ .plugin-card__hint:focus-visible {
949
+ background: rgba(255, 107, 53, 0.18);
950
+ border-color: rgba(255, 107, 53, 0.38);
951
+ color: #fff4ee;
952
+ box-shadow: 0 10px 24px rgba(255, 107, 53, 0.18);
953
+ }
954
+
955
+ .plugin-card__tooltip {
956
+ position: absolute;
957
+ left: 0;
958
+ top: calc(100% + 10px);
959
+ width: 240px;
960
+ padding: 11px 13px;
961
+ border-radius: 14px;
962
+ background: linear-gradient(180deg, rgba(30, 22, 22, 0.98), rgba(20, 20, 30, 0.98));
963
+ border: 1px solid rgba(255, 145, 96, 0.22);
964
+ box-shadow: 0 22px 48px rgba(0, 0, 0, 0.42);
965
+ color: #f3dde0;
966
+ font-size: 11px;
967
+ line-height: 1.5;
968
+ opacity: 0;
969
+ visibility: hidden;
970
+ transform: translateY(6px);
971
+ transition: opacity var(--duration-fast) var(--ease-out), transform var(--duration-fast) var(--ease-out), visibility var(--duration-fast) var(--ease-out);
972
+ pointer-events: none;
973
+ z-index: 20;
974
+ white-space: normal;
975
+ }
976
+
977
+ .plugin-card__tooltip::before {
978
+ content: '';
979
+ position: absolute;
980
+ top: -6px;
981
+ left: 8px;
982
+ width: 12px;
983
+ height: 12px;
984
+ background: rgba(27, 21, 28, 0.98);
985
+ border-left: 1px solid rgba(255, 145, 96, 0.22);
986
+ border-top: 1px solid rgba(255, 145, 96, 0.22);
987
+ transform: rotate(45deg);
988
+ }
989
+
990
+ .plugin-card__hint:hover .plugin-card__tooltip,
991
+ .plugin-card__hint:focus-visible .plugin-card__tooltip {
992
+ opacity: 1;
993
+ visibility: visible;
994
+ transform: translateY(0);
995
+ }
996
+
997
+ .plugin-card__hint:hover,
998
+ .plugin-card__hint:focus-visible {
999
+ z-index: 8;
1000
+ }
1001
+
1002
+ .plugin-card__switch {
1003
+ pointer-events: none;
1004
+ flex-shrink: 0;
1005
+ }
1006
+
1007
+ .plugin-card--selected .plugin-card__badge--recommended {
1008
+ background: rgba(255, 107, 53, 0.2);
1009
+ border-color: rgba(255, 107, 53, 0.38);
1010
+ color: #ffd1bc;
880
1011
  }
881
1012
 
882
1013
  /* ============ Prompt Notice ============ */
@@ -86,6 +86,16 @@ checks.push(() => expectMatch(
86
86
  'Native 9Router flow must auto-install 9Router'
87
87
  ));
88
88
 
89
+ checks.push(() => expect(
90
+ cli.includes('function providerSupportsMemoryEmbeddings(providerKey) {')
91
+ && cli.includes("supportsEmbeddings: true")
92
+ && cli.includes("supportsEmbeddings: false")
93
+ && cli.includes("function getCliSkillChoices({ providerKey, isVi }) {")
94
+ && cli.includes("skill.value !== 'memory'")
95
+ && cli.includes("providerSupportsMemoryEmbeddings(providerKey)"),
96
+ 'CLI skill labels must compute memory recommendations from provider embedding capability instead of hardcoding them'
97
+ ));
98
+
89
99
  checks.push(() => expectMatch(
90
100
  cli,
91
101
  /if \(providerKey === '9router'\) \{[\s\S]*shouldReuseInstalledGlobals\(\) && is9RouterInstalled\(\)[\s\S]*Reusing the installed 9Router[\s\S]*else if \(!is9RouterInstalled\(\)\)/s,
@@ -224,15 +234,18 @@ checks.push(() => expectMatch(
224
234
 
225
235
  checks.push(() => expectMatch(
226
236
  cli,
227
- /RUN npm install -g \$\{OPENCLAW_NPM_SPEC\} grammy/,
228
- 'Docker CLI image must install grammy alongside openclaw so Telegram runtime dependencies resolve'
237
+ /RUN npm install -g \$\{OPENCLAW_NPM_SPEC\} \$\{OPENCLAW_RUNTIME_PACKAGES\}/,
238
+ 'Docker CLI image must install the full OpenClaw runtime package set alongside openclaw'
229
239
  ));
230
240
 
231
241
  checks.push(() => expect(
232
242
  cli.includes("a.add('http://' + entry.address + ':18791')")
233
243
  && cli.includes('allowedOrigins:Array.from(a).filter(Boolean)')
244
+ && cli.includes("bind:'loopback'")
245
+ && cli.includes("delete c.gateway.customBindHost;")
246
+ && cli.includes("const gatewayBridge = 'socat TCP-LISTEN:18791,fork,reuseaddr TCP:127.0.0.1:18791 & ';")
234
247
  && !cli.includes("a.add(`http://${entry.address}:18791`)"),
235
- 'Docker CLI patch script must avoid shell-expanding ${entry.address} and must filter null origins'
248
+ 'Docker CLI patch script must keep the gateway loopback-local behind a socat bridge and avoid shell-expanding ${entry.address}'
236
249
  ));
237
250
 
238
251
  checks.push(() => expectMatch(
@@ -341,20 +354,23 @@ checks.push(() => expectMatch(
341
354
 
342
355
  checks.push(() => expectMatch(
343
356
  setup,
344
- /RUN npm install -g openclaw@2026\.4\.5 grammy/,
345
- 'Wizard Dockerfile generation must install grammy alongside openclaw so Telegram runtime dependencies resolve'
357
+ /RUN npm install -g openclaw@2026\.4\.5 \$\{openClawRuntimePackages\}/,
358
+ 'Wizard Dockerfile generation must install the full OpenClaw runtime package set alongside openclaw'
346
359
  ));
347
360
 
348
361
  checks.push(() => expect(
349
362
  setup.includes("a.add('http://' + entry.address + ':18791')")
350
363
  && setup.includes('allowedOrigins:Array.from(a).filter(Boolean)')
364
+ && setup.includes("bind:'loopback'")
365
+ && setup.includes("delete c.gateway.customBindHost;")
366
+ && setup.includes("const gatewayBridgePrefix = 'socat TCP-LISTEN:18791,fork,reuseaddr TCP:127.0.0.1:18791 & ';")
351
367
  && !setup.includes("a.add(\\`http://\\${entry.address}:18791\\`)"),
352
- 'Wizard Docker patch command must avoid shell-expanding ${entry.address} and must filter null origins'
368
+ 'Wizard Docker patch command must keep the gateway loopback-local behind a socat bridge and avoid shell-expanding ${entry.address}'
353
369
  ));
354
370
 
355
371
  checks.push(() => expectMatch(
356
372
  setup,
357
- /else if \(state\.nativeOs === 'vps'\) \{[\s\S]*scriptName = 'setup-openclaw-vps\.sh';[\s\S]*npm config set prefix "\$HOME\/\.local"[\s\S]*PROJECT_DIR="[\s\S]*export OPENCLAW_HOME="\$PROJECT_DIR\/\.openclaw"[\s\S]*export OPENCLAW_STATE_DIR="\$PROJECT_DIR\/\.openclaw"[\s\S]*export DATA_DIR="\$PROJECT_DIR\/\.9router"[\s\S]*npm install -g openclaw@2026\.4\.5 pm2@latest[\s\S]*pm2 save && pm2 startup/s,
373
+ /else if \(state\.nativeOs === 'vps'\) \{[\s\S]*scriptName = 'setup-openclaw-vps\.sh';[\s\S]*npm config set prefix "\$HOME\/\.local"[\s\S]*PROJECT_DIR="[\s\S]*export OPENCLAW_HOME="\$PROJECT_DIR\/\.openclaw"[\s\S]*export OPENCLAW_STATE_DIR="\$PROJECT_DIR\/\.openclaw"[\s\S]*export DATA_DIR="\$PROJECT_DIR\/\.9router"[\s\S]*npm install -g openclaw@2026\.4\.5[\s\S]*pm2@latest[\s\S]*pm2 save && pm2 startup/s,
358
374
  'VPS native script generation must keep runtime files project-local, install openclaw+pm2, and persist PM2 startup'
359
375
  ));
360
376
 
@@ -398,9 +414,9 @@ checks.push(() => expect(
398
414
  ));
399
415
 
400
416
  checks.push(() => expect(
401
- setup.includes("bind: 'custom'")
402
- && setup.includes("customBindHost: '0.0.0.0'")
403
- && !setup.includes("bind: '0.0.0.0'")
417
+ setup.includes("bind: 'loopback'")
418
+ && !setup.includes("bind: 'custom'")
419
+ && !setup.includes("customBindHost: '0.0.0.0'")
404
420
  && setup.includes("state.bots[state.activeBotIndex].provider = key;")
405
421
  && setup.includes("state.bots[state.activeBotIndex].model = p.models[0].id;")
406
422
  && setup.includes("state.bots[state.activeBotIndex].provider = state.config.provider;")
@@ -415,9 +431,13 @@ checks.push(() => expect(
415
431
  && setup.includes("lines.push('call npm install -g agent-browser playwright || goto :fail');")
416
432
  && setup.includes("lines.push('call npx playwright install chromium || goto :fail');")
417
433
  && setup.includes("lines.push('echo Cai skills...');")
434
+ && setup.includes("const openClawRuntimePackages = 'grammy @grammyjs/runner @grammyjs/transformer-throttler @buape/carbon @larksuiteoapi/node-sdk @slack/web-api';")
435
+ && setup.includes("memory: 'none'")
436
+ && setup.includes("workspace: 'workspace'")
437
+ && setup.includes("workspace: meta.workspaceDir")
418
438
  && !setup.includes("const authProviderName = provider.isProxy ? '9router' : provider.id;")
419
439
  && !setup.includes("const authProviderName = botProvider.isProxy ? '9router' : botProvider.id;"),
420
- 'Wizard native config generation must avoid legacy gateway.bind aliases, preserve concrete auth provider ids, and sync single-bot provider/model selections into bot state'
440
+ 'Wizard native config generation must keep gateway loopback-local, preserve concrete auth provider ids, disable memory search by default, and sync single-bot provider/model selections into bot state'
421
441
  ));
422
442
 
423
443
  checks.push(() => expectMatch(
@@ -446,6 +466,16 @@ checks.push(() => expect(
446
466
  '9Router sync logic in setup.js must remove stale smart-route combos when providers are disabled'
447
467
  ));
448
468
 
469
+ checks.push(() => expect(
470
+ setup.includes('function providerSupportsMemoryEmbeddings(providerKey) {')
471
+ && setup.includes('function getSkillDisplayName(skill, providerKey, lang) {')
472
+ && setup.includes('function getSkillExtraNote(skill, providerKey, lang) {')
473
+ && setup.includes('renderPluginGrid();')
474
+ && setup.includes("supportsEmbeddings: true")
475
+ && setup.includes("supportsEmbeddings: false"),
476
+ 'Wizard skill cards must recompute memory recommendation labels from provider embedding capability when the provider changes'
477
+ ));
478
+
449
479
  checks.push(() => expectMatch(
450
480
  setup,
451
481
  /\.openclaw\/9router-smart-route-sync\.js[\s\S]*pm2 start --name openclaw-9router-sync/s,
package/upgrade.ps1 ADDED
@@ -0,0 +1,90 @@
1
+ # OpenClaw Upgrade Script — Windows (PowerShell)
2
+ # Cach dung:
3
+ # Nhan dup upgrade.ps1 hoac: .\upgrade.ps1
4
+ # irm https://raw.githubusercontent.com/tuanminhhole/openclaw-setup/main/upgrade.ps1 | iex
5
+ # Chi danh cho Windows. Linux/macOS/Ubuntu: dung upgrade.sh
6
+
7
+ $ErrorActionPreference = "Stop"
8
+
9
+ # ── Version ──────────────────────────────────────────────────────────────────
10
+ $VER_STR = ""
11
+ try {
12
+ if (Test-Path "package.json") {
13
+ $pkg = Get-Content "package.json" -Raw | ConvertFrom-Json
14
+ if ($pkg.version) { $VER_STR = " v$($pkg.version)" }
15
+ }
16
+ } catch {}
17
+
18
+ # ── Banner: LOGO + BOX ───────────────────────────────────────────────────────
19
+ Write-Host ""
20
+ $logo = @(
21
+ '████████╗██╗ ██╗ █████╗ ███╗ ██╗███╗ ███╗██╗███╗ ██╗██╗ ██╗██╗ ██╗ ██████╗ ██╗ ███████╗',
22
+ '╚══██╔══╝██║ ██║██╔══██╗████╗ ██║████╗ ████║██║████╗ ██║██║ ██║██║ ██║██╔═══██╗██║ ██╔════╝',
23
+ ' ██║ ██║ ██║███████║██╔██╗ ██║██╔████╔██║██║██╔██╗ ██║███████║███████║██║ ██║██║ █████╗ ',
24
+ ' ██║ ██║ ██║██╔══██║██║╚██╗██║██║╚██╔╝██║██║██║╚██╗██║██╔══██║██╔══██║██║ ██║██║ ██╔══╝ ',
25
+ ' ██║ ╚██████╔╝██║ ██║██║ ╚████║██║ ╚═╝ ██║██║██║ ╚████║██║ ██║██║ ██║╚██████╔╝███████╗███████╗',
26
+ ' ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚══════╝'
27
+ )
28
+ foreach ($l in $logo) { Write-Host $l -ForegroundColor Red }
29
+ Write-Host ""
30
+
31
+ # Box — node render (handles emoji visual width correctly on all terminals)
32
+ $env:L1 = " 🦞 OpenClaw Setup${VER_STR} | Upgrade Script"
33
+ $env:L2 = " Windows (PowerShell)"
34
+ node -e @"
35
+ const RED='`u{1b}[0;31m',NC='`u{1b}[0m';
36
+ function vw(s){let w=0;for(const c of[...s]){const cp=c.codePointAt(0);w+=(cp>=0x1F000&&cp<=0x1FFFF?2:1);}return w;}
37
+ const L1=process.env.L1,L2=process.env.L2;
38
+ const INNER=Math.max(vw(L1),vw(L2))+2;
39
+ const D='─'.repeat(INNER);const pad=s=>' '.repeat(Math.max(0,INNER-vw(s)));
40
+ console.log(RED+'╭'+D+'╮'+NC);
41
+ console.log(RED+'│'+NC+L1+pad(L1)+RED+'│'+NC);
42
+ console.log(RED+'│'+NC+L2+pad(L2)+RED+'│'+NC);
43
+ console.log(RED+'╰'+D+'╯'+NC);
44
+ "@
45
+ Write-Host ""
46
+
47
+ # ── 1. Kiem tra Node.js ──────────────────────────────────────────────────────
48
+ if (-not (Get-Command node -ErrorAction SilentlyContinue)) {
49
+ Write-Host " ❌ Khong tim thay Node.js." -ForegroundColor Red
50
+ Write-Host " Tai LTS: https://nodejs.org/" -ForegroundColor Yellow
51
+ Read-Host "Nhan Enter de dong"; exit 1
52
+ }
53
+ $nodeVer = node -e "process.stdout.write(process.version)"
54
+ Write-Host " ✅ Node.js $nodeVer" -ForegroundColor Green
55
+
56
+ # ── 2. Xac dinh thu muc project ──────────────────────────────────────────────
57
+ $ScriptDir = $PSScriptRoot
58
+ if (-not $ScriptDir -or $ScriptDir -eq "") {
59
+ $ProjectDir = (Get-Location).Path
60
+ } elseif ((Test-Path (Join-Path $ScriptDir ".openclaw")) -or (Test-Path (Join-Path $ScriptDir "docker"))) {
61
+ $ProjectDir = $ScriptDir
62
+ } else {
63
+ $ProjectDir = (Get-Location).Path
64
+ }
65
+ Write-Host " 📁 Project: $ProjectDir" -ForegroundColor DarkGray
66
+ Write-Host ""
67
+ Set-Location $ProjectDir
68
+
69
+ # ── 3. Chay upgrade ──────────────────────────────────────────────────────────
70
+ Write-Host " 🔄 Dang lay CLI moi nhat va chay upgrade..." -ForegroundColor Cyan
71
+ Write-Host " npx luon tai create-openclaw-bot@latest — khong can cap nhat tay" -ForegroundColor DarkGray
72
+ Write-Host ""
73
+
74
+ try {
75
+ & npx create-openclaw-bot@latest upgrade
76
+ $exitCode = $LASTEXITCODE
77
+ } catch {
78
+ Write-Host " ❌ Loi: $_" -ForegroundColor Red
79
+ Read-Host "Nhan Enter de dong"; exit 1
80
+ }
81
+
82
+ Write-Host ""
83
+ if ($exitCode -eq 0) {
84
+ Write-Host " 🎉 Upgrade hoan tat!" -ForegroundColor Green
85
+ Write-Host " Dashboard: http://localhost:18791" -ForegroundColor Cyan
86
+ } else {
87
+ Write-Host " ⚠️ Ma loi: $exitCode — xem log o tren." -ForegroundColor Yellow
88
+ }
89
+ Write-Host ""
90
+ Read-Host "Nhan Enter de dong"
package/upgrade.sh ADDED
@@ -0,0 +1,93 @@
1
+ #!/bin/bash
2
+ # OpenClaw Upgrade Script — Linux / macOS / Ubuntu
3
+ # Cach dung:
4
+ # bash upgrade.sh
5
+ # curl -fsSL https://raw.githubusercontent.com/tuanminhhole/openclaw-setup/main/upgrade.sh | bash
6
+ # wget -qO- https://raw.githubusercontent.com/tuanminhhole/openclaw-setup/main/upgrade.sh | bash
7
+
8
+ set -e
9
+
10
+ RED='\033[0;31m'
11
+ GREEN='\033[0;32m'
12
+ CYAN='\033[0;36m'
13
+ YELLOW='\033[1;33m'
14
+ GRAY='\033[0;90m'
15
+ NC='\033[0m'
16
+
17
+ # ── Version ──────────────────────────────────────────────────────────────────
18
+ VER=""
19
+ if [ -f "package.json" ] && command -v node &>/dev/null; then
20
+ VER=$(node -p "try{JSON.parse(require('fs').readFileSync('package.json','utf8')).version}catch(e){''}" 2>/dev/null || true)
21
+ fi
22
+ [ -n "$VER" ] && VER_STR=" v${VER}" || VER_STR=""
23
+
24
+ # ── Banner: LOGO + BOX ───────────────────────────────────────────────────────
25
+ echo -e "${RED}"
26
+ echo '████████╗██╗ ██╗ █████╗ ███╗ ██╗███╗ ███╗██╗███╗ ██╗██╗ ██╗██╗ ██╗ ██████╗ ██╗ ███████╗'
27
+ echo '╚══██╔══╝██║ ██║██╔══██╗████╗ ██║████╗ ████║██║████╗ ██║██║ ██║██║ ██║██╔═══██╗██║ ██╔════╝'
28
+ echo ' ██║ ██║ ██║███████║██╔██╗ ██║██╔████╔██║██║██╔██╗ ██║███████║███████║██║ ██║██║ █████╗ '
29
+ echo ' ██║ ██║ ██║██╔══██║██║╚██╗██║██║╚██╔╝██║██║██║╚██╗██║██╔══██║██╔══██║██║ ██║██║ ██╔══╝ '
30
+ echo ' ██║ ╚██████╔╝██║ ██║██║ ╚████║██║ ╚═╝ ██║██║██║ ╚████║██║ ██║██║ ██║╚██████╔╝███████╗███████╗'
31
+ echo ' ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚══════╝'
32
+ echo -e "${NC}"
33
+
34
+ # Box — node render (handles emoji visual width correctly on all terminals)
35
+ L1=" 🦞 OpenClaw Setup${VER_STR} | Upgrade Script"
36
+ L2=" Linux / macOS / Ubuntu"
37
+ L1="$L1" L2="$L2" node -e "
38
+ const RED='\x1b[0;31m',NC='\x1b[0m';
39
+ function vw(s){let w=0;for(const c of[...s]){const cp=c.codePointAt(0);w+=(cp>=0x1F000&&cp<=0x1FFFF?2:1);}return w;}
40
+ const L1=process.env.L1,L2=process.env.L2;
41
+ const INNER=Math.max(vw(L1),vw(L2))+2;
42
+ const D='─'.repeat(INNER);const pad=s=>' '.repeat(Math.max(0,INNER-vw(s)));
43
+ console.log(RED+'╭'+D+'╮'+NC);
44
+ console.log(RED+'│'+NC+L1+pad(L1)+RED+'│'+NC);
45
+ console.log(RED+'│'+NC+L2+pad(L2)+RED+'│'+NC);
46
+ console.log(RED+'╰'+D+'╯'+NC);
47
+ "
48
+ echo ""
49
+
50
+ # ── 1. Kiem tra Node.js ──────────────────────────────────────────────────────
51
+ if ! command -v node &> /dev/null; then
52
+ echo -e "${RED} ❌ Khong tim thay Node.js.${NC}"
53
+ echo -e "${YELLOW} Cai dat: https://nodejs.org/${NC}"
54
+ echo ""
55
+ echo -e "${GRAY} Ubuntu/Debian:${NC}"
56
+ echo -e "${GRAY} curl -fsSL https://deb.nodesource.com/setup_22.x | sudo bash -${NC}"
57
+ echo -e "${GRAY} sudo apt-get install -y nodejs${NC}"
58
+ exit 1
59
+ fi
60
+ NODE_VER=$(node -e "process.stdout.write(process.version)")
61
+ echo -e "${GREEN} ✅ Node.js ${NODE_VER}${NC}"
62
+
63
+ # ── 2. Xac dinh thu muc project ──────────────────────────────────────────────
64
+ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
65
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
66
+ if [ -d "$SCRIPT_DIR/.openclaw" ] || [ -d "$SCRIPT_DIR/docker" ]; then
67
+ PROJECT_DIR="$SCRIPT_DIR"
68
+ else
69
+ PROJECT_DIR="$PWD"
70
+ fi
71
+ else
72
+ PROJECT_DIR="$PWD"
73
+ fi
74
+ echo -e "${GRAY} 📁 Project: $PROJECT_DIR${NC}"
75
+ echo ""
76
+ cd "$PROJECT_DIR"
77
+
78
+ # ── 3. Chay upgrade ──────────────────────────────────────────────────────────
79
+ echo -e "${CYAN} 🔄 Dang lay CLI moi nhat va chay upgrade...${NC}"
80
+ echo -e "${GRAY} npx luon tai create-openclaw-bot@latest — khong can cap nhat tay${NC}"
81
+ echo ""
82
+
83
+ npx create-openclaw-bot@latest upgrade
84
+ EXIT_CODE=$?
85
+
86
+ echo ""
87
+ if [ $EXIT_CODE -eq 0 ]; then
88
+ echo -e "${GREEN} 🎉 Upgrade hoan tat!${NC}"
89
+ echo -e "${CYAN} Dashboard: http://localhost:18791${NC}"
90
+ else
91
+ echo -e "${YELLOW} ⚠️ Ma loi: $EXIT_CODE — xem log o tren.${NC}"
92
+ fi
93
+ echo ""