@clawlabz/clawnetwork 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.ts +408 -104
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -15,6 +15,7 @@ const DEFAULT_P2P_PORT = 9711
|
|
|
15
15
|
const DEFAULT_NETWORK = 'mainnet'
|
|
16
16
|
const DEFAULT_SYNC_MODE = 'light'
|
|
17
17
|
const DEFAULT_HEALTH_CHECK_SECONDS = 30
|
|
18
|
+
const MIN_NODE_VERSION = '0.4.19'
|
|
18
19
|
const DEFAULT_UI_PORT = 19877
|
|
19
20
|
const MAX_RESTART_ATTEMPTS = 3
|
|
20
21
|
|
|
@@ -227,6 +228,16 @@ function getBinaryVersion(binaryPath: string): string | null {
|
|
|
227
228
|
} catch { return null }
|
|
228
229
|
}
|
|
229
230
|
|
|
231
|
+
function isVersionOlder(current: string, required: string): boolean {
|
|
232
|
+
const c = current.split('.').map(Number)
|
|
233
|
+
const r = required.split('.').map(Number)
|
|
234
|
+
for (let i = 0; i < 3; i++) {
|
|
235
|
+
if ((c[i] || 0) < (r[i] || 0)) return true
|
|
236
|
+
if ((c[i] || 0) > (r[i] || 0)) return false
|
|
237
|
+
}
|
|
238
|
+
return false
|
|
239
|
+
}
|
|
240
|
+
|
|
230
241
|
function detectPlatformTarget(): string {
|
|
231
242
|
const platform = process.platform === 'darwin' ? 'macos' : process.platform === 'win32' ? 'windows' : 'linux'
|
|
232
243
|
const arch = process.arch === 'arm64' ? 'aarch64' : 'x86_64'
|
|
@@ -807,24 +818,28 @@ function buildUiHtml(cfg: PluginConfig): string {
|
|
|
807
818
|
<meta charset="UTF-8">
|
|
808
819
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
809
820
|
<title>ClawNetwork Node Dashboard</title>
|
|
810
|
-
<link rel="icon" href="https://
|
|
821
|
+
<link rel="icon" href="https://explorer.clawlabz.xyz/favicon.png">
|
|
822
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
823
|
+
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
811
824
|
<style>
|
|
812
825
|
:root {
|
|
813
|
-
--bg: #
|
|
814
|
-
--bg-panel: #
|
|
815
|
-
--border: #
|
|
816
|
-
--accent: #
|
|
817
|
-
--accent-dim: rgba(
|
|
818
|
-
--
|
|
819
|
-
--
|
|
820
|
-
--purple:
|
|
821
|
-
--
|
|
822
|
-
--
|
|
823
|
-
--
|
|
824
|
-
--
|
|
825
|
-
--
|
|
826
|
+
--bg: #0a0705;
|
|
827
|
+
--bg-panel: #140e0a;
|
|
828
|
+
--border: #2a1c14;
|
|
829
|
+
--accent: #F96706;
|
|
830
|
+
--accent-dim: rgba(249, 103, 6, 0.15);
|
|
831
|
+
--accent-light: #FF8C3A;
|
|
832
|
+
--purple: #a855f7;
|
|
833
|
+
--purple-dim: rgba(168, 85, 247, 0.15);
|
|
834
|
+
--green: #22c55e;
|
|
835
|
+
--green-dim: rgba(34, 197, 94, 0.15);
|
|
836
|
+
--text: #fffaf5;
|
|
837
|
+
--text-dim: #8892a0;
|
|
838
|
+
--danger: #ef4444;
|
|
839
|
+
--font: 'Space Grotesk', system-ui, -apple-system, sans-serif;
|
|
840
|
+
--font-mono: 'JetBrains Mono', 'SF Mono', Consolas, monospace;
|
|
826
841
|
--radius: 10px;
|
|
827
|
-
--shadow: 0 4px 24px rgba(0, 0, 0, 0.
|
|
842
|
+
--shadow: 0 4px 24px rgba(0, 0, 0, 0.5);
|
|
828
843
|
}
|
|
829
844
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
830
845
|
body { background: var(--bg); color: var(--text); font-family: var(--font); line-height: 1.6; min-height: 100vh; }
|
|
@@ -834,16 +849,17 @@ function buildUiHtml(cfg: PluginConfig): string {
|
|
|
834
849
|
.header { background: var(--bg-panel); border-bottom: 1px solid var(--border); padding: 16px 0; position: sticky; top: 0; z-index: 100; }
|
|
835
850
|
.header .container { display: flex; align-items: center; justify-content: space-between; }
|
|
836
851
|
.logo { font-size: 22px; font-weight: 800; letter-spacing: -0.5px; }
|
|
837
|
-
.logo-claw { color:
|
|
838
|
-
.logo-net { color: var(--
|
|
852
|
+
.logo-claw { color: #ffffff; }
|
|
853
|
+
.logo-net { color: var(--accent); }
|
|
839
854
|
.header-badge { font-size: 11px; background: var(--accent-dim); color: var(--accent); padding: 2px 8px; border-radius: 4px; }
|
|
840
855
|
|
|
841
|
-
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(
|
|
856
|
+
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; }
|
|
842
857
|
.stat-card { background: var(--bg-panel); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; }
|
|
843
858
|
.stat-label { font-size: 12px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 1px; }
|
|
844
859
|
.stat-value { font-size: 28px; font-weight: 700; font-family: var(--font-mono); margin-top: 4px; }
|
|
845
860
|
.stat-value.green { color: var(--green); }
|
|
846
861
|
.stat-value.accent { color: var(--accent); }
|
|
862
|
+
.stat-value.purple { color: var(--purple); }
|
|
847
863
|
.stat-value.danger { color: var(--danger); }
|
|
848
864
|
|
|
849
865
|
.panel { background: var(--bg-panel); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; margin: 16px 0; }
|
|
@@ -858,13 +874,23 @@ function buildUiHtml(cfg: PluginConfig): string {
|
|
|
858
874
|
.status-dot.offline { background: var(--danger); }
|
|
859
875
|
.status-dot.syncing { background: #ffaa00; animation: pulse 1.5s infinite; }
|
|
860
876
|
|
|
861
|
-
.btn { display: inline-flex; align-items: center; gap: 6px; padding: 8px 16px; border-radius: 6px; border: 1px solid var(--border); background: var(--bg-panel); color: var(--text); font-size: 13px; cursor: pointer; transition: 0.2s; }
|
|
877
|
+
.btn { display: inline-flex; align-items: center; gap: 6px; padding: 8px 16px; border-radius: 6px; border: 1px solid var(--border); background: var(--bg-panel); color: var(--text); font-size: 13px; cursor: pointer; transition: 0.2s; font-family: var(--font); }
|
|
862
878
|
.btn:hover { border-color: var(--accent); color: var(--accent); }
|
|
863
879
|
.btn.danger:hover { border-color: var(--danger); color: var(--danger); }
|
|
864
880
|
.btn.primary { background: var(--accent-dim); border-color: var(--accent); color: var(--accent); }
|
|
865
|
-
.
|
|
881
|
+
.node-controls { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; padding-top: 16px; margin-top: 16px; border-top: 1px solid var(--border); }
|
|
882
|
+
.node-controls .spacer { flex: 1; }
|
|
866
883
|
|
|
867
|
-
.
|
|
884
|
+
.wallet-hero { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 16px; flex-wrap: wrap; }
|
|
885
|
+
.wallet-balance { font-size: 36px; font-weight: 800; font-family: var(--font-mono); color: var(--accent); letter-spacing: -1px; line-height: 1; }
|
|
886
|
+
.wallet-balance-label { font-size: 11px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 1px; margin-bottom: 6px; }
|
|
887
|
+
.wallet-addr-wrap { flex: 1; min-width: 0; }
|
|
888
|
+
.wallet-addr-label { font-size: 11px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 1px; margin-bottom: 6px; }
|
|
889
|
+
.wallet-addr { font-family: var(--font-mono); font-size: 12px; background: var(--bg); padding: 8px 12px; border-radius: 6px; border: 1px solid var(--border); word-break: break-all; display: flex; align-items: center; gap: 8px; }
|
|
890
|
+
.copy-btn { background: none; border: none; color: var(--accent); cursor: pointer; font-size: 12px; padding: 2px 8px; border-radius: 4px; border: 1px solid var(--accent); white-space: nowrap; font-family: var(--font); transition: 0.2s; }
|
|
891
|
+
.copy-btn:hover { background: var(--accent-dim); }
|
|
892
|
+
|
|
893
|
+
.logs-box { background: #060402; border: 1px solid var(--border); border-radius: var(--radius); padding: 16px; font-family: var(--font-mono); font-size: 12px; max-height: 300px; overflow-y: auto; white-space: pre-wrap; color: var(--text-dim); line-height: 1.8; }
|
|
868
894
|
|
|
869
895
|
.wallet-addr { font-family: var(--font-mono); font-size: 13px; background: var(--bg); padding: 8px 12px; border-radius: 6px; border: 1px solid var(--border); word-break: break-all; display: flex; align-items: center; gap: 8px; }
|
|
870
896
|
.copy-btn { background: none; border: none; color: var(--accent); cursor: pointer; font-size: 14px; padding: 2px 6px; }
|
|
@@ -873,7 +899,7 @@ function buildUiHtml(cfg: PluginConfig): string {
|
|
|
873
899
|
.toast { position: fixed; bottom: 24px; right: 24px; background: var(--bg-panel); border: 1px solid var(--accent); color: var(--accent); padding: 12px 20px; border-radius: 8px; font-size: 13px; opacity: 0; transition: 0.3s; z-index: 1000; }
|
|
874
900
|
.toast.show { opacity: 1; }
|
|
875
901
|
|
|
876
|
-
.quick-actions { display: grid; grid-template-columns: repeat(
|
|
902
|
+
.quick-actions { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin: 16px 0 0; }
|
|
877
903
|
.quick-action { background: var(--bg-panel); border: 1px solid var(--border); border-radius: var(--radius); padding: 14px 16px; cursor: pointer; transition: 0.2s; display: flex; align-items: center; gap: 10px; font-size: 13px; color: var(--text); }
|
|
878
904
|
.quick-action:hover { border-color: var(--accent); color: var(--accent); transform: translateY(-1px); }
|
|
879
905
|
.quick-action .qa-icon { font-size: 18px; width: 28px; text-align: center; }
|
|
@@ -890,77 +916,98 @@ function buildUiHtml(cfg: PluginConfig): string {
|
|
|
890
916
|
.modal-actions { display: flex; gap: 8px; margin-top: 16px; justify-content: flex-end; }
|
|
891
917
|
.modal-close { background: none; border: 1px solid var(--border); color: var(--text-dim); padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 13px; }
|
|
892
918
|
.modal-close:hover { border-color: var(--text); color: var(--text); }
|
|
919
|
+
.modal-input { width: 100%; box-sizing: border-box; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 10px 12px; font-size: 14px; color: var(--text); font-family: var(--font-mono); outline: none; margin-top: 4px; }
|
|
920
|
+
.modal-input:focus { border-color: var(--accent); }
|
|
921
|
+
.modal-hint { font-size: 12px; color: var(--text-dim); margin-top: 6px; line-height: 1.5; }
|
|
922
|
+
|
|
923
|
+
.upgrade-banner { padding: 14px 16px; border-radius: var(--radius); margin-bottom: 16px; font-size: 13px; line-height: 1.6; display: flex; justify-content: space-between; align-items: center; gap: 12px; }
|
|
924
|
+
.upgrade-banner.recommended { background: rgba(255, 170, 0, 0.1); border: 1px solid rgba(255, 170, 0, 0.3); color: #ffaa00; }
|
|
925
|
+
.upgrade-banner.recommended .upgrade-text { flex: 1; }
|
|
926
|
+
.upgrade-banner.recommended .upgrade-actions { display: flex; gap: 8px; }
|
|
927
|
+
.upgrade-banner.required { background: rgba(255, 140, 0, 0.1); border: 1px solid rgba(255, 140, 0, 0.3); color: #ff8c3a; }
|
|
928
|
+
.upgrade-banner.required .upgrade-text { flex: 1; }
|
|
929
|
+
.upgrade-banner.required .upgrade-actions { display: flex; gap: 8px; }
|
|
930
|
+
.upgrade-banner.critical { background: rgba(239, 68, 68, 0.15); border: 1px solid rgba(239, 68, 68, 0.4); color: var(--danger); width: 100%; margin-left: calc(-20px - 1px); margin-right: calc(-20px - 1px); padding: 16px calc(20px + 1px); border-radius: 0; font-weight: 600; }
|
|
931
|
+
.upgrade-banner.critical .upgrade-text { flex: 1; }
|
|
932
|
+
.upgrade-banner.critical .upgrade-actions { display: flex; gap: 8px; }
|
|
933
|
+
.upgrade-btn { background: var(--accent); color: var(--bg); border: none; padding: 6px 12px; border-radius: 4px; font-size: 12px; cursor: pointer; font-weight: 600; transition: 0.2s; }
|
|
934
|
+
.upgrade-btn:hover { opacity: 0.85; }
|
|
935
|
+
.upgrade-dismiss { background: none; border: 1px solid currentColor; color: currentColor; padding: 4px 10px; border-radius: 4px; font-size: 12px; cursor: pointer; transition: 0.2s; }
|
|
936
|
+
.upgrade-dismiss:hover { opacity: 0.7; }
|
|
893
937
|
</style>
|
|
894
938
|
</head>
|
|
895
939
|
<body>
|
|
896
940
|
<header class="header">
|
|
897
941
|
<div class="container">
|
|
898
942
|
<div style="display:flex;align-items:center;gap:14px">
|
|
899
|
-
<div class="logo"><span class="logo-claw">Claw</span><span class="logo-net">Network</span></div>
|
|
943
|
+
<div class="logo"><img src="https://explorer.clawlabz.xyz/favicon.png" style="width:28px;height:28px;border-radius:6px;vertical-align:middle;margin-right:8px"><span class="logo-claw">Claw</span><span class="logo-net">Network</span></div>
|
|
900
944
|
<span class="header-badge">Node Dashboard</span>
|
|
901
945
|
</div>
|
|
902
946
|
<span id="lastUpdate" style="font-size:12px;color:var(--text-dim)"></span>
|
|
903
947
|
</div>
|
|
904
948
|
</header>
|
|
905
949
|
|
|
906
|
-
<main class="container" style="padding-top:
|
|
907
|
-
<div class="stats-grid">
|
|
908
|
-
<div class="stat-card">
|
|
909
|
-
<div class="stat-label">Status</div>
|
|
910
|
-
<div class="stat-value" id="statusValue"><span class="status-dot offline"></span>Offline</div>
|
|
911
|
-
</div>
|
|
912
|
-
<div class="stat-card">
|
|
913
|
-
<div class="stat-label">Block Height</div>
|
|
914
|
-
<div class="stat-value accent" id="heightValue">—</div>
|
|
915
|
-
</div>
|
|
916
|
-
<div class="stat-card">
|
|
917
|
-
<div class="stat-label">Peers</div>
|
|
918
|
-
<div class="stat-value" id="peersValue">—</div>
|
|
919
|
-
</div>
|
|
920
|
-
<div class="stat-card">
|
|
921
|
-
<div class="stat-label">Uptime</div>
|
|
922
|
-
<div class="stat-value" id="uptimeValue">—</div>
|
|
923
|
-
</div>
|
|
924
|
-
</div>
|
|
950
|
+
<main class="container" style="padding-top:16px;padding-bottom:40px">
|
|
925
951
|
|
|
926
|
-
<div class="
|
|
927
|
-
<button class="btn primary" onclick="doAction('start')">Start Node</button>
|
|
928
|
-
<button class="btn danger" onclick="doAction('stop')">Stop Node</button>
|
|
929
|
-
<button class="btn" onclick="doAction('faucet')">Faucet (testnet)</button>
|
|
930
|
-
<button class="btn" onclick="refreshLogs()">Refresh Logs</button>
|
|
931
|
-
</div>
|
|
952
|
+
<div id="upgradeBanner" style="display:none" class="upgrade-banner"></div>
|
|
932
953
|
|
|
933
954
|
<div class="panel">
|
|
934
|
-
<div class="panel-title">
|
|
935
|
-
<div
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
<span class="
|
|
939
|
-
<div><div class="qa-label">Copy Address</div><div class="qa-hint">Share to receive CLAW</div></div>
|
|
940
|
-
</div>
|
|
941
|
-
<div class="quick-action" onclick="importToExtension()" id="qaImportExt" style="display:none">
|
|
942
|
-
<span class="qa-icon">🔗</span>
|
|
943
|
-
<div><div class="qa-label">Import to Extension</div><div class="qa-hint">One-click import to browser wallet</div></div>
|
|
955
|
+
<div class="panel-title">Node</div>
|
|
956
|
+
<div class="stats-grid" style="margin:0 0 4px">
|
|
957
|
+
<div class="stat-card">
|
|
958
|
+
<div class="stat-label">Status</div>
|
|
959
|
+
<div class="stat-value" id="statusValue"><span class="status-dot offline"></span>Offline</div>
|
|
944
960
|
</div>
|
|
945
|
-
<div class="
|
|
946
|
-
<
|
|
947
|
-
<div
|
|
961
|
+
<div class="stat-card">
|
|
962
|
+
<div class="stat-label">Block Height</div>
|
|
963
|
+
<div class="stat-value accent" id="heightValue">—</div>
|
|
948
964
|
</div>
|
|
949
|
-
<div class="
|
|
950
|
-
<
|
|
951
|
-
<div
|
|
965
|
+
<div class="stat-card">
|
|
966
|
+
<div class="stat-label">Peers</div>
|
|
967
|
+
<div class="stat-value" id="peersValue">—</div>
|
|
952
968
|
</div>
|
|
953
|
-
<div class="
|
|
954
|
-
<
|
|
955
|
-
<div
|
|
969
|
+
<div class="stat-card">
|
|
970
|
+
<div class="stat-label">Uptime</div>
|
|
971
|
+
<div class="stat-value" id="uptimeValue">—</div>
|
|
956
972
|
</div>
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
973
|
+
</div>
|
|
974
|
+
<div class="node-controls">
|
|
975
|
+
<button class="btn primary" id="startBtn" onclick="doAction('start')">▶ Start Node</button>
|
|
976
|
+
<button class="btn danger" id="stopBtn" onclick="doAction('stop')">■ Stop Node</button>
|
|
977
|
+
</div>
|
|
978
|
+
</div>
|
|
979
|
+
|
|
980
|
+
<div class="panel" id="walletPanel">
|
|
981
|
+
<div class="panel-title">Wallet</div>
|
|
982
|
+
<div id="walletEmpty" style="color:var(--text-dim);font-size:13px">No wallet yet — start the node to generate one</div>
|
|
983
|
+
<div id="walletLoaded" style="display:none">
|
|
984
|
+
<div class="wallet-hero">
|
|
985
|
+
<div>
|
|
986
|
+
<div class="wallet-balance-label">Balance</div>
|
|
987
|
+
<div class="wallet-balance" id="walletBalance">—</div>
|
|
988
|
+
</div>
|
|
989
|
+
<div class="wallet-addr-wrap">
|
|
990
|
+
<div class="wallet-addr-label">Address</div>
|
|
991
|
+
<div class="wallet-addr"><span id="walletAddrText" style="flex:1;min-width:0;word-break:break-all"></span><button class="copy-btn" onclick="copyText(cachedAddress)">Copy</button></div>
|
|
992
|
+
</div>
|
|
960
993
|
</div>
|
|
961
|
-
<div class="quick-
|
|
962
|
-
<
|
|
963
|
-
|
|
994
|
+
<div class="quick-actions" id="walletActions">
|
|
995
|
+
<div class="quick-action" onclick="importToExtension()" id="qaImportExt">
|
|
996
|
+
<span class="qa-icon"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></span>
|
|
997
|
+
<div><div class="qa-label">Import to Extension</div><div class="qa-hint" id="qaImportHint">One-click import to browser wallet</div></div>
|
|
998
|
+
</div>
|
|
999
|
+
<div class="quick-action warn" onclick="showExportKey()">
|
|
1000
|
+
<span class="qa-icon"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="7.5" cy="15.5" r="5.5"/><path d="m21 2-9.6 9.6"/><path d="m15.5 7.5 3 3L22 7l-3-3"/></svg></span>
|
|
1001
|
+
<div><div class="qa-label">Export Private Key</div><div class="qa-hint">Manual copy for backup</div></div>
|
|
1002
|
+
</div>
|
|
1003
|
+
<div class="quick-action" onclick="openExplorer()">
|
|
1004
|
+
<span class="qa-icon"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg></span>
|
|
1005
|
+
<div><div class="qa-label">View on Explorer</div><div class="qa-hint">Transaction history</div></div>
|
|
1006
|
+
</div>
|
|
1007
|
+
<div class="quick-action" id="qaRegister" onclick="handleRegisterAgent()">
|
|
1008
|
+
<span class="qa-icon"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="4" width="16" height="16" rx="2"/><rect x="9" y="9" width="6" height="6"/><path d="M15 2v2M9 2v2M15 20v2M9 20v2M2 15h2M2 9h2M20 15h2M20 9h2"/></svg></span>
|
|
1009
|
+
<div><div class="qa-label" id="qaRegisterLabel">Register Agent</div><div class="qa-hint" id="qaRegisterHint">On-chain identity</div></div>
|
|
1010
|
+
</div>
|
|
964
1011
|
</div>
|
|
965
1012
|
</div>
|
|
966
1013
|
</div>
|
|
@@ -971,13 +1018,57 @@ function buildUiHtml(cfg: PluginConfig): string {
|
|
|
971
1018
|
</div>
|
|
972
1019
|
|
|
973
1020
|
<div class="panel">
|
|
974
|
-
<div
|
|
1021
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px">
|
|
1022
|
+
<div class="panel-title" style="margin-bottom:0">Recent Logs</div>
|
|
1023
|
+
<button class="btn" style="font-size:12px;padding:5px 12px" onclick="refreshLogs()">↻ Refresh</button>
|
|
1024
|
+
</div>
|
|
975
1025
|
<div class="logs-box" id="logsBox">Loading...</div>
|
|
976
1026
|
</div>
|
|
977
1027
|
</main>
|
|
978
1028
|
|
|
1029
|
+
<footer style="border-top:1px solid var(--border);padding:24px 0;margin-top:16px">
|
|
1030
|
+
<div class="container" style="display:flex;flex-wrap:wrap;gap:20px;align-items:center;justify-content:space-between">
|
|
1031
|
+
<div style="display:flex;align-items:center;gap:8px">
|
|
1032
|
+
<img src="https://explorer.clawlabz.xyz/favicon.png" style="width:18px;height:18px;border-radius:4px;opacity:0.7">
|
|
1033
|
+
<span style="font-size:12px;color:var(--text-dim)">© 2026 ClawLabz</span>
|
|
1034
|
+
</div>
|
|
1035
|
+
<div style="display:flex;gap:20px;flex-wrap:wrap">
|
|
1036
|
+
<a href="https://chain.clawlabz.xyz" target="_blank" style="font-size:12px;color:var(--text-dim);text-decoration:none;transition:0.2s" onmouseover="this.style.color='var(--accent)'" onmouseout="this.style.color='var(--text-dim)'">Chain</a>
|
|
1037
|
+
<a href="https://explorer.clawlabz.xyz" target="_blank" style="font-size:12px;color:var(--text-dim);text-decoration:none;transition:0.2s" onmouseover="this.style.color='var(--accent)'" onmouseout="this.style.color='var(--text-dim)'">Explorer</a>
|
|
1038
|
+
<a href="https://chrome.google.com/webstore/search/ClawNetwork" target="_blank" style="font-size:12px;color:var(--text-dim);text-decoration:none;transition:0.2s" onmouseover="this.style.color='var(--accent)'" onmouseout="this.style.color='var(--text-dim)'">Wallet Extension</a>
|
|
1039
|
+
</div>
|
|
1040
|
+
</div>
|
|
1041
|
+
</footer>
|
|
1042
|
+
|
|
979
1043
|
<div class="toast" id="toast"></div>
|
|
980
1044
|
|
|
1045
|
+
<div class="modal-overlay" id="registerModal" onclick="if(event.target===this)closeRegisterModal()">
|
|
1046
|
+
<div class="modal">
|
|
1047
|
+
<div class="modal-title">Register Agent</div>
|
|
1048
|
+
<p style="font-size:13px;color:var(--text-dim);margin:0 0 12px">Register your wallet as an AI Agent on ClawNetwork. The name is your on-chain identity — it does not need to be unique globally (the wallet address is what's unique). Registration is gas-free on mainnet.</p>
|
|
1049
|
+
<input id="registerNameInput" class="modal-input" type="text" placeholder="my-agent-name" maxlength="32" onkeydown="if(event.key==='Enter')submitRegisterAgent()" />
|
|
1050
|
+
<div class="modal-hint">Allowed: letters, numbers, hyphens, underscores. Max 32 chars.</div>
|
|
1051
|
+
<div class="modal-actions">
|
|
1052
|
+
<button class="modal-close" onclick="closeRegisterModal()">Cancel</button>
|
|
1053
|
+
<button class="btn primary" onclick="submitRegisterAgent()">Register</button>
|
|
1054
|
+
</div>
|
|
1055
|
+
</div>
|
|
1056
|
+
</div>
|
|
1057
|
+
|
|
1058
|
+
<div class="modal-overlay" id="installModal" onclick="if(event.target===this)closeInstallModal()">
|
|
1059
|
+
<div class="modal">
|
|
1060
|
+
<div class="modal-title">Install ClawNetwork Wallet</div>
|
|
1061
|
+
<p style="font-size:13px;color:var(--text-dim);margin:0 0 16px;line-height:1.6">The ClawNetwork browser extension is not detected. Install it first, then click Import to Extension to import your node wallet.</p>
|
|
1062
|
+
<div style="display:flex;gap:10px;flex-direction:column">
|
|
1063
|
+
<a href="https://chrome.google.com/webstore/search/ClawNetwork" target="_blank" class="btn primary" style="text-decoration:none;justify-content:center;padding:10px 16px">Open Chrome Web Store</a>
|
|
1064
|
+
<a href="https://chain.clawlabz.xyz" target="_blank" style="font-size:12px;color:var(--text-dim);text-decoration:none;text-align:center" onmouseover="this.style.color='var(--accent)'" onmouseout="this.style.color='var(--text-dim)'">Learn more at chain.clawlabz.xyz →</a>
|
|
1065
|
+
</div>
|
|
1066
|
+
<div class="modal-actions" style="margin-top:16px">
|
|
1067
|
+
<button class="modal-close" onclick="closeInstallModal()">Close</button>
|
|
1068
|
+
</div>
|
|
1069
|
+
</div>
|
|
1070
|
+
</div>
|
|
1071
|
+
|
|
981
1072
|
<div class="modal-overlay" id="exportModal" onclick="if(event.target===this)closeExportModal()">
|
|
982
1073
|
<div class="modal">
|
|
983
1074
|
<div class="modal-title">Export Private Key</div>
|
|
@@ -1006,6 +1097,7 @@ function buildUiHtml(cfg: PluginConfig): string {
|
|
|
1006
1097
|
let cachedAddress = '';
|
|
1007
1098
|
let cachedNetwork = '';
|
|
1008
1099
|
let cachedKey = '';
|
|
1100
|
+
let cachedAgentName = ''; // '' = not registered, string = registered name
|
|
1009
1101
|
|
|
1010
1102
|
function copyText(text) {
|
|
1011
1103
|
navigator.clipboard.writeText(text).then(() => toast('Copied!')).catch(() => {});
|
|
@@ -1050,16 +1142,13 @@ function buildUiHtml(cfg: PluginConfig): string {
|
|
|
1050
1142
|
window.open('https://chain.clawlabz.xyz/faucet', '_blank');
|
|
1051
1143
|
}
|
|
1052
1144
|
|
|
1053
|
-
// Detect ClawNetwork extension provider
|
|
1145
|
+
// Detect ClawNetwork extension provider (for enhanced flow when available)
|
|
1054
1146
|
let hasExtension = false;
|
|
1055
1147
|
function checkExtension() {
|
|
1056
1148
|
if (window.clawNetwork && window.clawNetwork.isClawNetwork) {
|
|
1057
1149
|
hasExtension = true;
|
|
1058
|
-
const el = document.getElementById('qaImportExt');
|
|
1059
|
-
if (el) el.style.display = '';
|
|
1060
1150
|
}
|
|
1061
1151
|
}
|
|
1062
|
-
// Check immediately and after a short delay (extension injects at document_start)
|
|
1063
1152
|
checkExtension();
|
|
1064
1153
|
setTimeout(checkExtension, 1000);
|
|
1065
1154
|
setTimeout(checkExtension, 3000);
|
|
@@ -1083,7 +1172,10 @@ function buildUiHtml(cfg: PluginConfig): string {
|
|
|
1083
1172
|
} catch (e) { /* fall through to provider method */ }
|
|
1084
1173
|
}
|
|
1085
1174
|
// Fallback: use window.clawNetwork provider
|
|
1086
|
-
if (!window.clawNetwork) {
|
|
1175
|
+
if (!window.clawNetwork) {
|
|
1176
|
+
document.getElementById('installModal').classList.add('open');
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1087
1179
|
toast('Connecting to extension...');
|
|
1088
1180
|
try {
|
|
1089
1181
|
await window.clawNetwork.request({ method: 'claw_requestAccounts' });
|
|
@@ -1145,21 +1237,45 @@ function buildUiHtml(cfg: PluginConfig): string {
|
|
|
1145
1237
|
setTimeout(fetchStatus, 3000);
|
|
1146
1238
|
}
|
|
1147
1239
|
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1240
|
+
function closeInstallModal() {
|
|
1241
|
+
document.getElementById('installModal').classList.remove('open');
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
function handleRegisterAgent() {
|
|
1245
|
+
if (cachedAgentName) {
|
|
1246
|
+
toast('Already registered as "' + cachedAgentName + '"');
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
1249
|
+
openRegisterModal();
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
function openRegisterModal() {
|
|
1253
|
+
document.getElementById('registerNameInput').value = '';
|
|
1254
|
+
document.getElementById('registerModal').classList.add('open');
|
|
1255
|
+
setTimeout(() => document.getElementById('registerNameInput').focus(), 50);
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
function closeRegisterModal() {
|
|
1259
|
+
document.getElementById('registerModal').classList.remove('open');
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
async function submitRegisterAgent() {
|
|
1263
|
+
const raw = document.getElementById('registerNameInput').value.trim();
|
|
1264
|
+
const name = raw.replace(/[^a-zA-Z0-9_-]/g, '').slice(0, 32);
|
|
1265
|
+
if (!name) { toast('Please enter an agent name'); return; }
|
|
1266
|
+
closeRegisterModal();
|
|
1151
1267
|
if (window.clawNetwork) {
|
|
1152
1268
|
try {
|
|
1153
1269
|
toast('Approve registration in extension...');
|
|
1154
1270
|
await window.clawNetwork.request({ method: 'claw_requestAccounts' });
|
|
1155
1271
|
await window.clawNetwork.request({ method: 'claw_registerAgent', params: [name] });
|
|
1156
|
-
toast('Agent registered!');
|
|
1272
|
+
toast('Agent "' + name + '" registered!');
|
|
1157
1273
|
} catch (e) { toast('Registration failed: ' + (e.message || e)); }
|
|
1158
1274
|
} else {
|
|
1159
1275
|
try {
|
|
1160
1276
|
const res = await fetch(API + '/api/agent/register', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({name}) });
|
|
1161
1277
|
const data = await res.json();
|
|
1162
|
-
toast(data.ok ? 'Agent registered!' : 'Error: ' + data.error);
|
|
1278
|
+
toast(data.ok ? 'Agent "' + name + '" registered!' : 'Error: ' + data.error);
|
|
1163
1279
|
} catch (e) { toast('Registration failed: ' + e.message); }
|
|
1164
1280
|
}
|
|
1165
1281
|
}
|
|
@@ -1170,16 +1286,20 @@ function buildUiHtml(cfg: PluginConfig): string {
|
|
|
1170
1286
|
const data = await res.json();
|
|
1171
1287
|
renderStatus(data);
|
|
1172
1288
|
document.getElementById('lastUpdate').textContent = 'Updated: ' + new Date().toLocaleTimeString();
|
|
1173
|
-
} catch (e) {
|
|
1289
|
+
} catch (e) {
|
|
1290
|
+
console.error(e);
|
|
1291
|
+
renderStatus({ running: false, blockHeight: null, peerCount: null, walletAddress: '', network: 'mainnet', syncMode: 'light', rpcUrl: 'http://localhost:19877', pluginVersion: '0.1.1', restartCount: 0, dataDir: '', balance: '', syncing: false, uptimeFormatted: '—', pid: null });
|
|
1292
|
+
}
|
|
1174
1293
|
}
|
|
1175
1294
|
|
|
1176
1295
|
function renderStatus(s) {
|
|
1177
1296
|
const statusEl = document.getElementById('statusValue');
|
|
1178
1297
|
if (s.running) {
|
|
1179
|
-
|
|
1180
|
-
|
|
1298
|
+
let dotClass = 'online', label = 'Online';
|
|
1299
|
+
if (s.syncing && s.peerless) { dotClass = 'syncing'; label = 'No Peers'; }
|
|
1300
|
+
else if (s.syncing) { dotClass = 'syncing'; label = 'Syncing'; }
|
|
1181
1301
|
statusEl.innerHTML = '<span class="status-dot ' + dotClass + '"></span>' + label;
|
|
1182
|
-
statusEl.className = 'stat-value green';
|
|
1302
|
+
statusEl.className = 'stat-value' + (s.syncing ? '' : ' green');
|
|
1183
1303
|
} else {
|
|
1184
1304
|
statusEl.innerHTML = '<span class="status-dot offline"></span>Offline';
|
|
1185
1305
|
statusEl.className = 'stat-value danger';
|
|
@@ -1188,23 +1308,86 @@ function buildUiHtml(cfg: PluginConfig): string {
|
|
|
1188
1308
|
document.getElementById('heightValue').textContent = s.blockHeight !== null ? s.blockHeight.toLocaleString() : '—';
|
|
1189
1309
|
document.getElementById('peersValue').textContent = s.peerCount !== null ? s.peerCount : '—';
|
|
1190
1310
|
document.getElementById('uptimeValue').textContent = s.uptimeFormatted || '—';
|
|
1311
|
+
document.getElementById('startBtn').disabled = s.running;
|
|
1312
|
+
document.getElementById('stopBtn').disabled = !s.running;
|
|
1313
|
+
document.getElementById('startBtn').style.opacity = s.running ? '0.4' : '1';
|
|
1314
|
+
document.getElementById('stopBtn').style.opacity = !s.running ? '0.4' : '1';
|
|
1315
|
+
|
|
1316
|
+
// Handle upgrade banner
|
|
1317
|
+
const bannerEl = document.getElementById('upgradeBanner');
|
|
1318
|
+
if (s.upgradeLevel && s.upgradeLevel !== 'up_to_date' && s.upgradeLevel !== 'unknown') {
|
|
1319
|
+
bannerEl.style.display = '';
|
|
1320
|
+
const recommended = s.upgradeLevel === 'recommended';
|
|
1321
|
+
const required = s.upgradeLevel === 'required';
|
|
1322
|
+
const critical = s.upgradeLevel === 'critical';
|
|
1323
|
+
bannerEl.className = 'upgrade-banner ' + s.upgradeLevel;
|
|
1324
|
+
let bannerHtml = '<div class="upgrade-text">';
|
|
1325
|
+
if (critical) {
|
|
1326
|
+
bannerHtml += '⚠ CRITICAL UPDATE REQUIRED — ' + (s.changelog || 'Security update required') + '. Node stopped for security.';
|
|
1327
|
+
} else if (required) {
|
|
1328
|
+
bannerHtml += 'Update recommended: v' + (s.latestVersion || '') + ' — ' + (s.changelog || 'Update available');
|
|
1329
|
+
} else if (recommended) {
|
|
1330
|
+
bannerHtml += 'Update available: v' + (s.latestVersion || '') + ' — ' + (s.changelog || 'New version available');
|
|
1331
|
+
}
|
|
1332
|
+
bannerHtml += '</div><div class="upgrade-actions">';
|
|
1333
|
+
bannerHtml += '<button class="upgrade-btn" onclick="doAction(\'upgrade\')">Update Now</button>';
|
|
1334
|
+
if (recommended) {
|
|
1335
|
+
bannerHtml += '<button class="upgrade-dismiss" onclick="document.getElementById(\'upgradeBanner\').style.display=\'none\'">Dismiss</button>';
|
|
1336
|
+
}
|
|
1337
|
+
bannerHtml += '</div>';
|
|
1338
|
+
bannerEl.innerHTML = bannerHtml;
|
|
1339
|
+
} else {
|
|
1340
|
+
bannerEl.style.display = 'none';
|
|
1341
|
+
}
|
|
1191
1342
|
|
|
1192
1343
|
// Wallet
|
|
1193
1344
|
cachedAddress = s.walletAddress || '';
|
|
1194
1345
|
cachedNetwork = s.network || '';
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1346
|
+
if (s.walletAddress) {
|
|
1347
|
+
document.getElementById('walletEmpty').style.display = 'none';
|
|
1348
|
+
document.getElementById('walletLoaded').style.display = '';
|
|
1349
|
+
document.getElementById('walletAddrText').textContent = s.walletAddress;
|
|
1350
|
+
document.getElementById('walletBalance').textContent = s.balance || '—';
|
|
1351
|
+
// Agent status
|
|
1352
|
+
cachedAgentName = s.agentName || '';
|
|
1353
|
+
const regCard = document.getElementById('qaRegister');
|
|
1354
|
+
const regLabel = document.getElementById('qaRegisterLabel');
|
|
1355
|
+
const regHint = document.getElementById('qaRegisterHint');
|
|
1356
|
+
if (cachedAgentName) {
|
|
1357
|
+
regLabel.textContent = 'Agent Registered';
|
|
1358
|
+
regHint.innerHTML = '<span style="color:var(--green)">✓ ' + cachedAgentName + '</span>';
|
|
1359
|
+
regCard.style.borderColor = 'var(--green)';
|
|
1360
|
+
regCard.style.opacity = '0.85';
|
|
1361
|
+
} else {
|
|
1362
|
+
regLabel.textContent = 'Register Agent';
|
|
1363
|
+
regHint.textContent = 'On-chain identity';
|
|
1364
|
+
regCard.style.borderColor = '';
|
|
1365
|
+
regCard.style.opacity = '';
|
|
1366
|
+
}
|
|
1367
|
+
// Extension detection hint
|
|
1368
|
+
const hasExt = !!(window.clawNetwork && window.clawNetwork.isClawNetwork);
|
|
1369
|
+
document.getElementById('qaImportHint').textContent = hasExt ? 'Extension detected — click to import' : 'Install wallet extension first';
|
|
1370
|
+
} else {
|
|
1371
|
+
document.getElementById('walletEmpty').style.display = '';
|
|
1372
|
+
document.getElementById('walletLoaded').style.display = 'none';
|
|
1373
|
+
}
|
|
1201
1374
|
|
|
1202
1375
|
// Node info
|
|
1376
|
+
let versionStatusHtml = s.binaryVersion || '—';
|
|
1377
|
+
if (s.upgradeLevel === 'up_to_date') {
|
|
1378
|
+
versionStatusHtml = (s.binaryVersion || '—') + ' <span style="color:var(--green)">✓</span>';
|
|
1379
|
+
} else if (s.upgradeLevel === 'recommended') {
|
|
1380
|
+
versionStatusHtml = (s.binaryVersion || '—') + ' <span style="color:#ffaa00">→ ' + (s.latestVersion || '') + '</span>';
|
|
1381
|
+
} else if (s.upgradeLevel === 'required') {
|
|
1382
|
+
versionStatusHtml = (s.binaryVersion || '—') + ' <span style="color:#ff8c3a">⚠ Update recommended</span>';
|
|
1383
|
+
} else if (s.upgradeLevel === 'critical') {
|
|
1384
|
+
versionStatusHtml = (s.binaryVersion || '—') + ' <span style="color:var(--danger)">🔴 CRITICAL</span>';
|
|
1385
|
+
}
|
|
1203
1386
|
const rows = [
|
|
1204
1387
|
['Network', s.network],
|
|
1205
1388
|
['Sync Mode', s.syncMode],
|
|
1206
1389
|
['RPC URL', s.rpcUrl],
|
|
1207
|
-
['Binary Version',
|
|
1390
|
+
['Binary Version', versionStatusHtml],
|
|
1208
1391
|
['Plugin Version', s.pluginVersion],
|
|
1209
1392
|
['PID', s.pid || '—'],
|
|
1210
1393
|
['Restart Count', s.restartCount],
|
|
@@ -1321,26 +1504,52 @@ async function handle(req, res) {
|
|
|
1321
1504
|
try {
|
|
1322
1505
|
const h = await fetchJson('http://localhost:' + RPC_PORT + '/health');
|
|
1323
1506
|
let balance = '';
|
|
1507
|
+
let walletAddress = '';
|
|
1508
|
+
let agentName = '';
|
|
1509
|
+
let upgradeLevel = 'unknown';
|
|
1510
|
+
let latestVersion = '';
|
|
1511
|
+
let releaseUrl = '';
|
|
1512
|
+
let changelog = '';
|
|
1513
|
+
let announcement = null;
|
|
1514
|
+
// Fetch version info if available (Phase 1 endpoint)
|
|
1515
|
+
try {
|
|
1516
|
+
const v = await fetchJson('http://localhost:' + RPC_PORT + '/version');
|
|
1517
|
+
if (v && v.upgrade_level) {
|
|
1518
|
+
upgradeLevel = v.upgrade_level;
|
|
1519
|
+
latestVersion = v.latest_version || '';
|
|
1520
|
+
releaseUrl = v.release_url || '';
|
|
1521
|
+
changelog = v.changelog || '';
|
|
1522
|
+
announcement = v.announcement || null;
|
|
1523
|
+
}
|
|
1524
|
+
} catch {}
|
|
1324
1525
|
try {
|
|
1325
1526
|
const walletPath = path.join(os.homedir(), '.openclaw/workspace/clawnetwork/wallet.json');
|
|
1326
1527
|
const w = JSON.parse(fs.readFileSync(walletPath, 'utf8'));
|
|
1327
|
-
|
|
1528
|
+
walletAddress = w.address || '';
|
|
1529
|
+
if (w.address) {
|
|
1530
|
+
const b = await rpcCall('claw_getBalance', [w.address]); balance = formatClaw(b);
|
|
1531
|
+
try { const ag = await rpcCall('claw_getAgent', [w.address]); agentName = (ag && ag.name) ? ag.name : ''; } catch {}
|
|
1532
|
+
}
|
|
1328
1533
|
} catch {}
|
|
1329
1534
|
json(200, {
|
|
1330
|
-
running: h.status === 'ok',
|
|
1535
|
+
running: h.status === 'ok' || h.status === 'degraded',
|
|
1331
1536
|
blockHeight: h.height,
|
|
1332
1537
|
peerCount: h.peer_count,
|
|
1333
1538
|
network: h.chain_id,
|
|
1334
1539
|
syncMode: 'light',
|
|
1335
1540
|
rpcUrl: 'http://localhost:' + RPC_PORT,
|
|
1336
|
-
walletAddress
|
|
1541
|
+
walletAddress,
|
|
1337
1542
|
binaryVersion: h.version,
|
|
1338
|
-
pluginVersion: '0.1.
|
|
1543
|
+
pluginVersion: '0.1.1',
|
|
1339
1544
|
uptime: h.uptime_secs,
|
|
1340
1545
|
uptimeFormatted: h.uptime_secs < 60 ? h.uptime_secs + 's' : h.uptime_secs < 3600 ? Math.floor(h.uptime_secs/60) + 'm' : Math.floor(h.uptime_secs/3600) + 'h ' + Math.floor((h.uptime_secs%3600)/60) + 'm',
|
|
1341
|
-
restartCount: 0, dataDir: path.join(os.homedir(), '.clawnetwork'), balance, syncing: h.status === 'degraded',
|
|
1546
|
+
restartCount: 0, dataDir: path.join(os.homedir(), '.clawnetwork'), balance, agentName, syncing: h.status === 'degraded', peerless: h.peer_count === 0, lastBlockAgeSecs: h.last_block_age_secs,
|
|
1547
|
+
upgradeLevel, latestVersion, releaseUrl, changelog, announcement,
|
|
1342
1548
|
});
|
|
1343
|
-
} catch {
|
|
1549
|
+
} catch {
|
|
1550
|
+
const walletAddr = (() => { try { return JSON.parse(fs.readFileSync(path.join(os.homedir(), '.openclaw/workspace/clawnetwork/wallet.json'), 'utf8')).address; } catch { return ''; } })();
|
|
1551
|
+
json(200, { running: false, blockHeight: null, peerCount: null, walletAddress: walletAddr, network: 'mainnet', syncMode: 'light', rpcUrl: 'http://localhost:' + RPC_PORT, pluginVersion: '0.1.1', restartCount: 0, dataDir: path.join(os.homedir(), '.clawnetwork'), balance: '', agentName: '', syncing: false, uptimeFormatted: '—', pid: null, upgradeLevel: 'unknown', latestVersion: '', releaseUrl: '', changelog: '', announcement: null });
|
|
1552
|
+
}
|
|
1344
1553
|
return;
|
|
1345
1554
|
}
|
|
1346
1555
|
if (p === '/api/logs') {
|
|
@@ -1461,8 +1670,21 @@ async function handle(req, res) {
|
|
|
1461
1670
|
}
|
|
1462
1671
|
if (a === 'start') {
|
|
1463
1672
|
try {
|
|
1464
|
-
// Check if already running
|
|
1673
|
+
// Check if already running — try RPC health first (covers stale PID file)
|
|
1465
1674
|
const pidFile = path.join(os.homedir(), '.openclaw/workspace/clawnetwork/node.pid');
|
|
1675
|
+
try {
|
|
1676
|
+
const h = await fetchJson('http://localhost:' + RPC_PORT + '/health');
|
|
1677
|
+
if (h && (h.status === 'ok' || h.status === 'degraded')) {
|
|
1678
|
+
try {
|
|
1679
|
+
const { execSync } = require('child_process');
|
|
1680
|
+
const pgrep = execSync("pgrep -f 'claw-node start'", { encoding: 'utf8', timeout: 3000 }).trim();
|
|
1681
|
+
const livePid = parseInt(pgrep.split('\\n')[0], 10);
|
|
1682
|
+
if (livePid > 0) fs.writeFileSync(pidFile, String(livePid));
|
|
1683
|
+
} catch {}
|
|
1684
|
+
json(200, { message: 'Node already running' }); return;
|
|
1685
|
+
}
|
|
1686
|
+
} catch {}
|
|
1687
|
+
// Fallback: check PID file
|
|
1466
1688
|
try {
|
|
1467
1689
|
const pid = parseInt(fs.readFileSync(pidFile, 'utf8').trim(), 10);
|
|
1468
1690
|
if (pid > 0) { try { process.kill(pid, 0); json(200, { message: 'Node already running', pid }); return; } catch {} }
|
|
@@ -1529,6 +1751,64 @@ async function handle(req, res) {
|
|
|
1529
1751
|
json(200, { message: 'Use Stop then Start to restart the node' });
|
|
1530
1752
|
return;
|
|
1531
1753
|
}
|
|
1754
|
+
if (a === 'upgrade') {
|
|
1755
|
+
try {
|
|
1756
|
+
// 1. Stop running node
|
|
1757
|
+
const pidFile = path.join(os.homedir(), '.openclaw/workspace/clawnetwork/node.pid');
|
|
1758
|
+
try {
|
|
1759
|
+
const pid = parseInt(fs.readFileSync(pidFile, 'utf8').trim(), 10);
|
|
1760
|
+
if (pid > 0) try { process.kill(pid, 'SIGTERM'); } catch {}
|
|
1761
|
+
} catch {}
|
|
1762
|
+
try { require('child_process').execFileSync('pkill', ['-f', 'claw-node start'], { timeout: 5000 }); } catch {}
|
|
1763
|
+
|
|
1764
|
+
// 2. Download latest binary
|
|
1765
|
+
const binDir = path.join(os.homedir(), '.openclaw/bin');
|
|
1766
|
+
const binName = process.platform === 'win32' ? 'claw-node.exe' : 'claw-node';
|
|
1767
|
+
const target = process.platform === 'darwin'
|
|
1768
|
+
? (process.arch === 'arm64' ? 'macos-aarch64' : 'macos-x86_64')
|
|
1769
|
+
: process.platform === 'win32' ? 'windows-x86_64' : 'linux-x86_64';
|
|
1770
|
+
const ext = process.platform === 'win32' ? 'zip' : 'tar.gz';
|
|
1771
|
+
|
|
1772
|
+
// Fetch latest release tag
|
|
1773
|
+
let version = 'latest';
|
|
1774
|
+
try {
|
|
1775
|
+
const res = await fetch('https://api.github.com/repos/clawlabz/claw-network/releases/latest');
|
|
1776
|
+
if (res.ok) { const d = await res.json(); if (d.tag_name) version = d.tag_name; }
|
|
1777
|
+
} catch {}
|
|
1778
|
+
|
|
1779
|
+
const baseUrl = version === 'latest'
|
|
1780
|
+
? 'https://github.com/clawlabz/claw-network/releases/latest/download'
|
|
1781
|
+
: 'https://github.com/clawlabz/claw-network/releases/download/' + version;
|
|
1782
|
+
const downloadUrl = baseUrl + '/claw-node-' + target + '.' + ext;
|
|
1783
|
+
|
|
1784
|
+
const tmpFile = path.join(os.tmpdir(), 'claw-node-upgrade-' + Date.now() + '.' + ext);
|
|
1785
|
+
require('child_process').execFileSync('curl', ['-sSfL', '-o', tmpFile, downloadUrl], { timeout: 120000 });
|
|
1786
|
+
|
|
1787
|
+
// Ensure bin directory exists
|
|
1788
|
+
if (!fs.existsSync(binDir)) { fs.mkdirSync(binDir, { recursive: true }); }
|
|
1789
|
+
|
|
1790
|
+
// Extract binary
|
|
1791
|
+
if (ext === 'tar.gz') {
|
|
1792
|
+
require('child_process').execFileSync('tar', ['xzf', tmpFile, '-C', binDir], { timeout: 30000 });
|
|
1793
|
+
} else {
|
|
1794
|
+
// Windows zip handling
|
|
1795
|
+
const AdmZip = require('adm-zip');
|
|
1796
|
+
const zip = new AdmZip(tmpFile);
|
|
1797
|
+
zip.extractAllTo(binDir, true);
|
|
1798
|
+
}
|
|
1799
|
+
fs.chmodSync(path.join(binDir, binName), 0o755);
|
|
1800
|
+
try { fs.unlinkSync(tmpFile); } catch {}
|
|
1801
|
+
|
|
1802
|
+
// 3. Get new version
|
|
1803
|
+
let newVersion = 'unknown';
|
|
1804
|
+
try {
|
|
1805
|
+
newVersion = require('child_process').execFileSync(path.join(binDir, binName), ['--version'], { encoding: 'utf8', timeout: 5000 }).trim();
|
|
1806
|
+
} catch {}
|
|
1807
|
+
|
|
1808
|
+
json(200, { message: 'Upgraded to ' + newVersion + '. Restart the node from Dashboard.', newVersion });
|
|
1809
|
+
} catch (e) { json(500, { error: e.message }); }
|
|
1810
|
+
return;
|
|
1811
|
+
}
|
|
1532
1812
|
json(400, { error: 'Unknown action: ' + a });
|
|
1533
1813
|
return;
|
|
1534
1814
|
}
|
|
@@ -1796,6 +2076,18 @@ export default function register(api: OpenClawApi) {
|
|
|
1796
2076
|
out({ error: 'claw-node not found. Run: curl -sSf https://raw.githubusercontent.com/clawlabz/claw-network/main/claw-node/scripts/install.sh | bash' })
|
|
1797
2077
|
return
|
|
1798
2078
|
}
|
|
2079
|
+
} else {
|
|
2080
|
+
// Auto-upgrade if binary is older than required minimum
|
|
2081
|
+
const currentVersion = getBinaryVersion(binary)
|
|
2082
|
+
if (currentVersion && isVersionOlder(currentVersion, MIN_NODE_VERSION)) {
|
|
2083
|
+
api.logger?.info?.(`[clawnetwork] claw-node ${currentVersion} is outdated (need >=${MIN_NODE_VERSION}), upgrading...`)
|
|
2084
|
+
process.stdout.write(`Upgrading claw-node ${currentVersion} → ${MIN_NODE_VERSION}+...\n`)
|
|
2085
|
+
try {
|
|
2086
|
+
binary = await downloadBinary(api)
|
|
2087
|
+
} catch (e: unknown) {
|
|
2088
|
+
api.logger?.warn?.(`[clawnetwork] auto-upgrade failed: ${(e as Error).message}, continuing with ${currentVersion}`)
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
1799
2091
|
}
|
|
1800
2092
|
initNode(binary, cfg.network, api)
|
|
1801
2093
|
startNodeProcess(binary, cfg, api)
|
|
@@ -2035,12 +2327,16 @@ export default function register(api: OpenClawApi) {
|
|
|
2035
2327
|
// Check if already running (e.g. from a previous detached start)
|
|
2036
2328
|
const state = isNodeRunning()
|
|
2037
2329
|
if (state.running) {
|
|
2038
|
-
api.logger?.info?.(`[clawnetwork] node already running (pid=${state.pid}), skipping
|
|
2330
|
+
api.logger?.info?.(`[clawnetwork] node already running (pid=${state.pid}), skipping node start`)
|
|
2039
2331
|
startHealthCheck(cfg, api)
|
|
2332
|
+
// Still need to ensure heartbeat loop is running (may have been lost on gateway restart)
|
|
2333
|
+
const wallet = ensureWallet(cfg.network, api)
|
|
2334
|
+
await sleep(5_000)
|
|
2335
|
+
await autoRegisterMiner(cfg, wallet, api)
|
|
2040
2336
|
return
|
|
2041
2337
|
}
|
|
2042
2338
|
|
|
2043
|
-
// Step 1: Ensure binary
|
|
2339
|
+
// Step 1: Ensure binary (auto-upgrade if outdated)
|
|
2044
2340
|
let binary = findBinary()
|
|
2045
2341
|
if (!binary) {
|
|
2046
2342
|
if (cfg.autoDownload) {
|
|
@@ -2050,6 +2346,14 @@ export default function register(api: OpenClawApi) {
|
|
|
2050
2346
|
api.logger?.error?.('[clawnetwork] claw-node not found and autoDownload is disabled')
|
|
2051
2347
|
return
|
|
2052
2348
|
}
|
|
2349
|
+
} else if (cfg.autoDownload) {
|
|
2350
|
+
const cv = getBinaryVersion(binary)
|
|
2351
|
+
if (cv && isVersionOlder(cv, MIN_NODE_VERSION)) {
|
|
2352
|
+
api.logger?.info?.(`[clawnetwork] claw-node ${cv} outdated (need >=${MIN_NODE_VERSION}), upgrading...`)
|
|
2353
|
+
try { binary = await downloadBinary(api) } catch (e: unknown) {
|
|
2354
|
+
api.logger?.warn?.(`[clawnetwork] auto-upgrade failed: ${(e as Error).message}`)
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2053
2357
|
}
|
|
2054
2358
|
|
|
2055
2359
|
// Step 2: Init
|