@chrysb/alphaclaw 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/bin/alphaclaw.js +11 -0
- package/lib/public/js/components/gateway.js +96 -92
- package/lib/public/js/lib/api.js +11 -0
- package/lib/server/alphaclaw-version.js +190 -0
- package/lib/server/constants.js +2 -0
- package/lib/server/routes/system.js +21 -0
- package/lib/server.js +3 -0
- package/package.json +1 -1
package/bin/alphaclaw.js
CHANGED
|
@@ -13,16 +13,27 @@ const { execSync } = require("child_process");
|
|
|
13
13
|
const args = process.argv.slice(2);
|
|
14
14
|
const command = args.find((a) => !a.startsWith("-"));
|
|
15
15
|
|
|
16
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8"));
|
|
17
|
+
|
|
18
|
+
if (args.includes("--version") || args.includes("-v") || command === "version") {
|
|
19
|
+
console.log(pkg.version);
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}
|
|
22
|
+
|
|
16
23
|
if (!command || command === "help" || args.includes("--help")) {
|
|
17
24
|
console.log(`
|
|
25
|
+
alphaclaw v${pkg.version}
|
|
26
|
+
|
|
18
27
|
Usage: alphaclaw <command> [options]
|
|
19
28
|
|
|
20
29
|
Commands:
|
|
21
30
|
start Start the AlphaClaw server (Setup UI + gateway manager)
|
|
31
|
+
version Print version
|
|
22
32
|
|
|
23
33
|
Options:
|
|
24
34
|
--root-dir <path> Persistent data directory (default: ~/.alphaclaw)
|
|
25
35
|
--port <number> Server port (default: 3000)
|
|
36
|
+
--version, -v Print version
|
|
26
37
|
--help Show this help message
|
|
27
38
|
`);
|
|
28
39
|
process.exit(0);
|
|
@@ -3,102 +3,126 @@ import { useEffect, useState } from "https://esm.sh/preact/hooks";
|
|
|
3
3
|
import htm from "https://esm.sh/htm";
|
|
4
4
|
import {
|
|
5
5
|
fetchOpenclawVersion,
|
|
6
|
+
fetchAlphaclawVersion,
|
|
6
7
|
restartGateway,
|
|
7
8
|
updateOpenclaw,
|
|
9
|
+
updateAlphaclaw,
|
|
8
10
|
} from "../lib/api.js";
|
|
9
11
|
import { showToast } from "./toast.js";
|
|
10
12
|
const html = htm.bind(h);
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
const [
|
|
14
|
-
const [
|
|
15
|
-
const [currentVersion, setCurrentVersion] = useState(openclawVersion || null);
|
|
14
|
+
function VersionRow({ label, currentVersion, fetchVersion, applyUpdate, tagsUrl }) {
|
|
15
|
+
const [checking, setChecking] = useState(false);
|
|
16
|
+
const [version, setVersion] = useState(currentVersion || null);
|
|
16
17
|
const [latestVersion, setLatestVersion] = useState(null);
|
|
17
18
|
const [hasUpdate, setHasUpdate] = useState(false);
|
|
18
|
-
const [
|
|
19
|
-
const isRunning = status === "running" && !restarting;
|
|
20
|
-
const dotClass = isRunning
|
|
21
|
-
? "w-2 h-2 rounded-full bg-green-500"
|
|
22
|
-
: "w-2 h-2 rounded-full bg-yellow-500 animate-pulse";
|
|
19
|
+
const [error, setError] = useState("");
|
|
23
20
|
|
|
24
21
|
useEffect(() => {
|
|
25
|
-
|
|
26
|
-
}, [
|
|
22
|
+
setVersion(currentVersion || null);
|
|
23
|
+
}, [currentVersion]);
|
|
27
24
|
|
|
28
25
|
useEffect(() => {
|
|
29
26
|
let active = true;
|
|
30
|
-
const
|
|
27
|
+
const load = async () => {
|
|
31
28
|
try {
|
|
32
|
-
const data = await
|
|
29
|
+
const data = await fetchVersion(false);
|
|
33
30
|
if (!active) return;
|
|
34
|
-
|
|
31
|
+
setVersion(data.currentVersion || currentVersion || null);
|
|
35
32
|
setLatestVersion(data.latestVersion || null);
|
|
36
33
|
setHasUpdate(!!data.hasUpdate);
|
|
37
|
-
|
|
34
|
+
setError(data.ok ? "" : data.error || "");
|
|
38
35
|
} catch (err) {
|
|
39
36
|
if (!active) return;
|
|
40
|
-
|
|
37
|
+
setError(err.message || "Could not check updates");
|
|
41
38
|
}
|
|
42
39
|
};
|
|
43
|
-
|
|
44
|
-
return () => {
|
|
45
|
-
active = false;
|
|
46
|
-
};
|
|
40
|
+
load();
|
|
41
|
+
return () => { active = false; };
|
|
47
42
|
}, []);
|
|
48
43
|
|
|
49
|
-
const
|
|
50
|
-
if (
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
await restartGateway();
|
|
54
|
-
showToast("Gateway restarted", "success");
|
|
55
|
-
} catch (err) {
|
|
56
|
-
showToast("Restart failed: " + err.message, "error");
|
|
57
|
-
}
|
|
58
|
-
setRestarting(false);
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const handleUpdate = async () => {
|
|
62
|
-
if (checkingUpdate) return;
|
|
63
|
-
setCheckingUpdate(true);
|
|
64
|
-
setUpdateError("");
|
|
44
|
+
const handleAction = async () => {
|
|
45
|
+
if (checking) return;
|
|
46
|
+
setChecking(true);
|
|
47
|
+
setError("");
|
|
65
48
|
try {
|
|
66
49
|
const data = hasUpdate
|
|
67
|
-
? await
|
|
68
|
-
: await
|
|
69
|
-
|
|
50
|
+
? await applyUpdate()
|
|
51
|
+
: await fetchVersion(true);
|
|
52
|
+
setVersion(data.currentVersion || version);
|
|
70
53
|
setLatestVersion(data.latestVersion || null);
|
|
71
54
|
setHasUpdate(!!data.hasUpdate);
|
|
72
|
-
|
|
55
|
+
setError(data.ok ? "" : data.error || "");
|
|
73
56
|
if (hasUpdate) {
|
|
74
57
|
if (!data.ok) {
|
|
75
|
-
showToast(data.error ||
|
|
76
|
-
} else if (data.updated) {
|
|
58
|
+
showToast(data.error || `${label} update failed`, "error");
|
|
59
|
+
} else if (data.updated || data.restarting) {
|
|
77
60
|
showToast(
|
|
78
|
-
data.
|
|
79
|
-
?
|
|
80
|
-
: `Updated to ${data.currentVersion}`,
|
|
61
|
+
data.restarting
|
|
62
|
+
? `${label} updated — restarting...`
|
|
63
|
+
: `Updated ${label} to ${data.currentVersion}`,
|
|
81
64
|
"success",
|
|
82
65
|
);
|
|
83
66
|
} else {
|
|
84
|
-
showToast(
|
|
67
|
+
showToast(`Already at latest ${label} version`, "success");
|
|
85
68
|
}
|
|
86
69
|
} else if (data.hasUpdate && data.latestVersion) {
|
|
87
|
-
showToast(
|
|
70
|
+
showToast(`${label} update available: ${data.latestVersion}`, "warning");
|
|
88
71
|
} else {
|
|
89
|
-
showToast(
|
|
72
|
+
showToast(`${label} is up to date`, "success");
|
|
90
73
|
}
|
|
91
74
|
} catch (err) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
75
|
+
setError(err.message || (hasUpdate ? `Could not update ${label}` : "Could not check updates"));
|
|
76
|
+
showToast(hasUpdate ? `Could not update ${label}` : "Could not check updates", "error");
|
|
77
|
+
}
|
|
78
|
+
setChecking(false);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
return html`
|
|
82
|
+
<div class="flex items-center justify-between gap-3">
|
|
83
|
+
<div class="min-w-0">
|
|
84
|
+
<p class="text-sm text-gray-300 truncate">
|
|
85
|
+
<span class="text-gray-500">${label}</span>${" "}v${version || "unknown"}
|
|
86
|
+
</p>
|
|
87
|
+
${error && html`<p class="text-xs text-yellow-500 mt-1">${error}</p>`}
|
|
88
|
+
</div>
|
|
89
|
+
<div class="flex items-center gap-2 shrink-0">
|
|
90
|
+
${hasUpdate && latestVersion && tagsUrl && html`
|
|
91
|
+
<a href=${tagsUrl} target="_blank"
|
|
92
|
+
class="text-xs text-yellow-500 hover:text-yellow-300 transition-colors"
|
|
93
|
+
>${latestVersion} available</a>
|
|
94
|
+
`}
|
|
95
|
+
<button
|
|
96
|
+
onclick=${handleAction}
|
|
97
|
+
disabled=${checking}
|
|
98
|
+
class="text-xs px-2.5 py-1 rounded-lg border border-border text-gray-500 hover:text-gray-300 hover:border-gray-500 transition-colors ${checking ? "opacity-50 cursor-not-allowed" : ""}"
|
|
99
|
+
>
|
|
100
|
+
${checking
|
|
101
|
+
? hasUpdate ? "Updating..." : "Checking..."
|
|
102
|
+
: hasUpdate ? "Update" : "Check updates"}
|
|
103
|
+
</button>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function Gateway({ status, openclawVersion }) {
|
|
110
|
+
const [restarting, setRestarting] = useState(false);
|
|
111
|
+
const isRunning = status === "running" && !restarting;
|
|
112
|
+
const dotClass = isRunning
|
|
113
|
+
? "w-2 h-2 rounded-full bg-green-500"
|
|
114
|
+
: "w-2 h-2 rounded-full bg-yellow-500 animate-pulse";
|
|
115
|
+
|
|
116
|
+
const handleRestart = async () => {
|
|
117
|
+
if (restarting) return;
|
|
118
|
+
setRestarting(true);
|
|
119
|
+
try {
|
|
120
|
+
await restartGateway();
|
|
121
|
+
showToast("Gateway restarted", "success");
|
|
122
|
+
} catch (err) {
|
|
123
|
+
showToast("Restart failed: " + err.message, "error");
|
|
100
124
|
}
|
|
101
|
-
|
|
125
|
+
setRestarting(false);
|
|
102
126
|
};
|
|
103
127
|
|
|
104
128
|
return html` <div class="bg-surface border border-border rounded-xl p-4">
|
|
@@ -123,41 +147,21 @@ export function Gateway({ status, openclawVersion }) {
|
|
|
123
147
|
Restart
|
|
124
148
|
</button>
|
|
125
149
|
</div>
|
|
126
|
-
<div class="mt-3 pt-3 border-t border-border">
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
class="text-xs text-yellow-500 hover:text-yellow-300 transition-colors"
|
|
142
|
-
>${latestVersion} available</a
|
|
143
|
-
>`}
|
|
144
|
-
<button
|
|
145
|
-
onclick=${handleUpdate}
|
|
146
|
-
disabled=${checkingUpdate}
|
|
147
|
-
class="text-xs px-2.5 py-1 rounded-lg border border-border text-gray-500 hover:text-gray-300 hover:border-gray-500 transition-colors ${checkingUpdate
|
|
148
|
-
? "opacity-50 cursor-not-allowed"
|
|
149
|
-
: ""}"
|
|
150
|
-
>
|
|
151
|
-
${checkingUpdate
|
|
152
|
-
? hasUpdate
|
|
153
|
-
? "Updating..."
|
|
154
|
-
: "Checking..."
|
|
155
|
-
: hasUpdate
|
|
156
|
-
? "Update"
|
|
157
|
-
: "Check updates"}
|
|
158
|
-
</button>
|
|
159
|
-
</div>
|
|
160
|
-
</div>
|
|
150
|
+
<div class="mt-3 pt-3 border-t border-border space-y-3">
|
|
151
|
+
<${VersionRow}
|
|
152
|
+
label="OpenClaw"
|
|
153
|
+
currentVersion=${openclawVersion}
|
|
154
|
+
fetchVersion=${fetchOpenclawVersion}
|
|
155
|
+
applyUpdate=${updateOpenclaw}
|
|
156
|
+
tagsUrl="https://github.com/openclaw/openclaw/tags"
|
|
157
|
+
/>
|
|
158
|
+
<${VersionRow}
|
|
159
|
+
label="AlphaClaw"
|
|
160
|
+
currentVersion=${null}
|
|
161
|
+
fetchVersion=${fetchAlphaclawVersion}
|
|
162
|
+
applyUpdate=${updateAlphaclaw}
|
|
163
|
+
tagsUrl="https://www.npmjs.com/package/@chrysb/alphaclaw"
|
|
164
|
+
/>
|
|
161
165
|
</div>
|
|
162
166
|
</div>`;
|
|
163
167
|
}
|
package/lib/public/js/lib/api.js
CHANGED
|
@@ -80,6 +80,17 @@ export async function updateOpenclaw() {
|
|
|
80
80
|
return res.json();
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
export async function fetchAlphaclawVersion(refresh = false) {
|
|
84
|
+
const query = refresh ? '?refresh=1' : '';
|
|
85
|
+
const res = await authFetch(`/api/alphaclaw/version${query}`);
|
|
86
|
+
return res.json();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function updateAlphaclaw() {
|
|
90
|
+
const res = await authFetch('/api/alphaclaw/update', { method: 'POST' });
|
|
91
|
+
return res.json();
|
|
92
|
+
}
|
|
93
|
+
|
|
83
94
|
export async function fetchSyncCron() {
|
|
84
95
|
const res = await authFetch('/api/sync-cron');
|
|
85
96
|
const text = await res.text();
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
const { exec } = require("child_process");
|
|
2
|
+
const { spawn } = require("child_process");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const https = require("https");
|
|
6
|
+
const http = require("http");
|
|
7
|
+
const {
|
|
8
|
+
kLatestVersionCacheTtlMs,
|
|
9
|
+
kAlphaclawRegistryUrl,
|
|
10
|
+
kPackageRoot,
|
|
11
|
+
} = require("./constants");
|
|
12
|
+
|
|
13
|
+
// kPackageRoot is lib/ — the actual npm package root (with package.json) is one level up
|
|
14
|
+
const kNpmPackageRoot = path.resolve(kPackageRoot, "..");
|
|
15
|
+
|
|
16
|
+
const createAlphaclawVersionService = () => {
|
|
17
|
+
let kUpdateStatusCache = { latestVersion: null, hasUpdate: false, fetchedAt: 0 };
|
|
18
|
+
let kUpdateInProgress = false;
|
|
19
|
+
|
|
20
|
+
const readAlphaclawVersion = () => {
|
|
21
|
+
try {
|
|
22
|
+
const pkg = JSON.parse(
|
|
23
|
+
fs.readFileSync(path.join(kNpmPackageRoot, "package.json"), "utf8"),
|
|
24
|
+
);
|
|
25
|
+
return pkg.version || null;
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const fetchLatestVersionFromRegistry = () =>
|
|
32
|
+
new Promise((resolve, reject) => {
|
|
33
|
+
const get = kAlphaclawRegistryUrl.startsWith("https") ? https.get : http.get;
|
|
34
|
+
get(kAlphaclawRegistryUrl, { headers: { Accept: "application/json" } }, (res) => {
|
|
35
|
+
let data = "";
|
|
36
|
+
res.on("data", (chunk) => { data += chunk; });
|
|
37
|
+
res.on("end", () => {
|
|
38
|
+
try {
|
|
39
|
+
const parsed = JSON.parse(data);
|
|
40
|
+
resolve(parsed["dist-tags"]?.latest || null);
|
|
41
|
+
} catch (e) {
|
|
42
|
+
reject(new Error("Failed to parse registry response"));
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}).on("error", reject);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const readAlphaclawUpdateStatus = async ({ refresh = false } = {}) => {
|
|
49
|
+
const now = Date.now();
|
|
50
|
+
if (
|
|
51
|
+
!refresh &&
|
|
52
|
+
kUpdateStatusCache.fetchedAt &&
|
|
53
|
+
now - kUpdateStatusCache.fetchedAt < kLatestVersionCacheTtlMs
|
|
54
|
+
) {
|
|
55
|
+
return {
|
|
56
|
+
latestVersion: kUpdateStatusCache.latestVersion,
|
|
57
|
+
hasUpdate: kUpdateStatusCache.hasUpdate,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const currentVersion = readAlphaclawVersion();
|
|
61
|
+
const latestVersion = await fetchLatestVersionFromRegistry();
|
|
62
|
+
const hasUpdate = !!(currentVersion && latestVersion && latestVersion !== currentVersion);
|
|
63
|
+
kUpdateStatusCache = { latestVersion, hasUpdate, fetchedAt: Date.now() };
|
|
64
|
+
console.log(
|
|
65
|
+
`[alphaclaw] alphaclaw update status: hasUpdate=${hasUpdate} current=${currentVersion} latest=${latestVersion || "unknown"}`,
|
|
66
|
+
);
|
|
67
|
+
return { latestVersion, hasUpdate };
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const findInstallDir = () => {
|
|
71
|
+
// Walk up from kNpmPackageRoot to find the consuming project's directory
|
|
72
|
+
// (the one with node_modules/@chrysb/alphaclaw). In Docker this is /app.
|
|
73
|
+
let dir = kNpmPackageRoot;
|
|
74
|
+
while (dir !== path.dirname(dir)) {
|
|
75
|
+
const parent = path.dirname(dir);
|
|
76
|
+
if (path.basename(parent) === "node_modules" || parent.includes("node_modules")) {
|
|
77
|
+
dir = parent;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const pkgPath = path.join(parent, "package.json");
|
|
81
|
+
if (fs.existsSync(pkgPath)) {
|
|
82
|
+
try {
|
|
83
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
84
|
+
if (pkg.dependencies?.["@chrysb/alphaclaw"]) {
|
|
85
|
+
return parent;
|
|
86
|
+
}
|
|
87
|
+
} catch {}
|
|
88
|
+
}
|
|
89
|
+
dir = parent;
|
|
90
|
+
}
|
|
91
|
+
// Fallback: if running directly (not from node_modules), use kNpmPackageRoot
|
|
92
|
+
return kNpmPackageRoot;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const installLatestAlphaclaw = () =>
|
|
96
|
+
new Promise((resolve, reject) => {
|
|
97
|
+
const installDir = findInstallDir();
|
|
98
|
+
console.log(`[alphaclaw] Running: npm install @chrysb/alphaclaw@latest (cwd: ${installDir})`);
|
|
99
|
+
exec(
|
|
100
|
+
"npm install @chrysb/alphaclaw@latest --omit=dev --no-save --package-lock=false",
|
|
101
|
+
{
|
|
102
|
+
cwd: installDir,
|
|
103
|
+
env: {
|
|
104
|
+
...process.env,
|
|
105
|
+
npm_config_update_notifier: "false",
|
|
106
|
+
npm_config_fund: "false",
|
|
107
|
+
npm_config_audit: "false",
|
|
108
|
+
},
|
|
109
|
+
timeout: 180000,
|
|
110
|
+
},
|
|
111
|
+
(err, stdout, stderr) => {
|
|
112
|
+
if (err) {
|
|
113
|
+
const message = String(stderr || err.message || "").trim();
|
|
114
|
+
console.log(`[alphaclaw] alphaclaw install error: ${message.slice(0, 200)}`);
|
|
115
|
+
return reject(new Error(message || "Failed to install @chrysb/alphaclaw@latest"));
|
|
116
|
+
}
|
|
117
|
+
if (stdout?.trim()) {
|
|
118
|
+
console.log(`[alphaclaw] alphaclaw install stdout: ${stdout.trim().slice(0, 300)}`);
|
|
119
|
+
}
|
|
120
|
+
console.log("[alphaclaw] alphaclaw install completed");
|
|
121
|
+
resolve({ stdout: stdout?.trim(), stderr: stderr?.trim() });
|
|
122
|
+
},
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const restartProcess = () => {
|
|
127
|
+
console.log("[alphaclaw] Restarting process...");
|
|
128
|
+
const child = spawn(process.argv[0], process.argv.slice(1), {
|
|
129
|
+
detached: true,
|
|
130
|
+
stdio: "inherit",
|
|
131
|
+
});
|
|
132
|
+
child.unref();
|
|
133
|
+
process.exit(0);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const getVersionStatus = async (refresh) => {
|
|
137
|
+
const currentVersion = readAlphaclawVersion();
|
|
138
|
+
try {
|
|
139
|
+
const { latestVersion, hasUpdate } = await readAlphaclawUpdateStatus({ refresh });
|
|
140
|
+
return { ok: true, currentVersion, latestVersion, hasUpdate };
|
|
141
|
+
} catch (err) {
|
|
142
|
+
return {
|
|
143
|
+
ok: false,
|
|
144
|
+
currentVersion,
|
|
145
|
+
latestVersion: kUpdateStatusCache.latestVersion,
|
|
146
|
+
hasUpdate: kUpdateStatusCache.hasUpdate,
|
|
147
|
+
error: err.message || "Failed to fetch latest AlphaClaw version",
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const updateAlphaclaw = async () => {
|
|
153
|
+
if (kUpdateInProgress) {
|
|
154
|
+
return {
|
|
155
|
+
status: 409,
|
|
156
|
+
body: { ok: false, error: "AlphaClaw update already in progress" },
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
kUpdateInProgress = true;
|
|
161
|
+
const previousVersion = readAlphaclawVersion();
|
|
162
|
+
try {
|
|
163
|
+
await installLatestAlphaclaw();
|
|
164
|
+
kUpdateStatusCache = { latestVersion: null, hasUpdate: false, fetchedAt: 0 };
|
|
165
|
+
return {
|
|
166
|
+
status: 200,
|
|
167
|
+
body: {
|
|
168
|
+
ok: true,
|
|
169
|
+
previousVersion,
|
|
170
|
+
restarting: true,
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
} catch (err) {
|
|
174
|
+
kUpdateInProgress = false;
|
|
175
|
+
return {
|
|
176
|
+
status: 500,
|
|
177
|
+
body: { ok: false, error: err.message || "Failed to update AlphaClaw" },
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
readAlphaclawVersion,
|
|
184
|
+
getVersionStatus,
|
|
185
|
+
updateAlphaclaw,
|
|
186
|
+
restartProcess,
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
module.exports = { createAlphaclawVersionService };
|
package/lib/server/constants.js
CHANGED
|
@@ -106,6 +106,7 @@ const kFallbackOnboardingModels = [
|
|
|
106
106
|
const kVersionCacheTtlMs = 60 * 1000;
|
|
107
107
|
const kLatestVersionCacheTtlMs = 10 * 60 * 1000;
|
|
108
108
|
const kOpenclawRegistryUrl = "https://registry.npmjs.org/openclaw";
|
|
109
|
+
const kAlphaclawRegistryUrl = "https://registry.npmjs.org/@chrysb%2falphaclaw";
|
|
109
110
|
const kAppDir = kPackageRoot;
|
|
110
111
|
|
|
111
112
|
const kSystemVars = new Set([
|
|
@@ -265,6 +266,7 @@ module.exports = {
|
|
|
265
266
|
kVersionCacheTtlMs,
|
|
266
267
|
kLatestVersionCacheTtlMs,
|
|
267
268
|
kOpenclawRegistryUrl,
|
|
269
|
+
kAlphaclawRegistryUrl,
|
|
268
270
|
kAppDir,
|
|
269
271
|
kSystemVars,
|
|
270
272
|
kKnownVars,
|
|
@@ -12,6 +12,7 @@ const registerSystemRoutes = ({
|
|
|
12
12
|
isOnboarded,
|
|
13
13
|
getChannelStatus,
|
|
14
14
|
openclawVersionService,
|
|
15
|
+
alphaclawVersionService,
|
|
15
16
|
clawCmd,
|
|
16
17
|
restartGateway,
|
|
17
18
|
OPENCLAW_DIR,
|
|
@@ -183,6 +184,26 @@ const registerSystemRoutes = ({
|
|
|
183
184
|
res.status(result.status).json(result.body);
|
|
184
185
|
});
|
|
185
186
|
|
|
187
|
+
app.get("/api/alphaclaw/version", async (req, res) => {
|
|
188
|
+
const refresh = String(req.query.refresh || "") === "1";
|
|
189
|
+
const status = await alphaclawVersionService.getVersionStatus(refresh);
|
|
190
|
+
res.json(status);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
app.post("/api/alphaclaw/update", async (req, res) => {
|
|
194
|
+
console.log("[alphaclaw] /api/alphaclaw/update requested");
|
|
195
|
+
const result = await alphaclawVersionService.updateAlphaclaw();
|
|
196
|
+
console.log(
|
|
197
|
+
`[alphaclaw] /api/alphaclaw/update result: status=${result.status} ok=${result.body?.ok === true}`,
|
|
198
|
+
);
|
|
199
|
+
if (result.status === 200 && result.body?.ok) {
|
|
200
|
+
res.json(result.body);
|
|
201
|
+
setTimeout(() => alphaclawVersionService.restartProcess(), 1000);
|
|
202
|
+
} else {
|
|
203
|
+
res.status(result.status).json(result.body);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
186
207
|
app.get("/api/gateway-status", async (req, res) => {
|
|
187
208
|
const result = await clawCmd("status");
|
|
188
209
|
res.json(result);
|
package/lib/server.js
CHANGED
|
@@ -34,6 +34,7 @@ const { createCommands } = require("./server/commands");
|
|
|
34
34
|
const { createAuthProfiles } = require("./server/auth-profiles");
|
|
35
35
|
const { createLoginThrottle } = require("./server/login-throttle");
|
|
36
36
|
const { createOpenclawVersionService } = require("./server/openclaw-version");
|
|
37
|
+
const { createAlphaclawVersionService } = require("./server/alphaclaw-version");
|
|
37
38
|
const { syncBootstrapPromptFiles } = require("./server/onboarding/workspace");
|
|
38
39
|
|
|
39
40
|
const { registerAuthRoutes } = require("./server/routes/auth");
|
|
@@ -76,6 +77,7 @@ const openclawVersionService = createOpenclawVersionService({
|
|
|
76
77
|
restartGateway,
|
|
77
78
|
isOnboarded,
|
|
78
79
|
});
|
|
80
|
+
const alphaclawVersionService = createAlphaclawVersionService();
|
|
79
81
|
|
|
80
82
|
const { requireAuth } = registerAuthRoutes({ app, loginThrottle });
|
|
81
83
|
app.use(express.static(path.join(__dirname, "public")));
|
|
@@ -118,6 +120,7 @@ registerSystemRoutes({
|
|
|
118
120
|
isOnboarded,
|
|
119
121
|
getChannelStatus,
|
|
120
122
|
openclawVersionService,
|
|
123
|
+
alphaclawVersionService,
|
|
121
124
|
clawCmd,
|
|
122
125
|
restartGateway,
|
|
123
126
|
OPENCLAW_DIR: constants.OPENCLAW_DIR,
|