@vellumai/cli 0.1.1
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/bun.lock +291 -0
- package/eslint.config.mjs +17 -0
- package/package.json +26 -0
- package/src/adapters/install.sh +99 -0
- package/src/adapters/openclaw-http-server.ts +189 -0
- package/src/adapters/openclaw.ts +118 -0
- package/src/commands/hatch.ts +806 -0
- package/src/components/DefaultMainScreen.tsx +217 -0
- package/src/index.ts +39 -0
- package/src/lib/constants.ts +75 -0
- package/src/lib/gcp.ts +261 -0
- package/src/lib/health-check.ts +38 -0
- package/src/lib/interfaces-seed.ts +25 -0
- package/src/lib/openclaw-runtime-server.ts +18 -0
- package/src/lib/random-name.ts +133 -0
- package/src/lib/status-emoji.ts +14 -0
- package/src/lib/step-runner.ts +103 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { execSync, spawn } from "node:child_process";
|
|
3
|
+
import http from "node:http";
|
|
4
|
+
|
|
5
|
+
const PORT = parseInt(process.env.PORT || "7830", 10);
|
|
6
|
+
|
|
7
|
+
interface StoredMessage {
|
|
8
|
+
id: string;
|
|
9
|
+
role: "user" | "assistant";
|
|
10
|
+
content: string;
|
|
11
|
+
timestamp: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const messages: Record<string, StoredMessage[]> = {};
|
|
15
|
+
|
|
16
|
+
function parseBody(req: http.IncomingMessage): Promise<Record<string, unknown>> {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
let body = "";
|
|
19
|
+
req.on("data", (chunk: Buffer) => (body += chunk.toString()));
|
|
20
|
+
req.on("end", () => {
|
|
21
|
+
try {
|
|
22
|
+
resolve(JSON.parse(body));
|
|
23
|
+
} catch (e) {
|
|
24
|
+
reject(e);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
req.on("error", reject);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function log(method: string, path: string, status: number): void {
|
|
32
|
+
const ts = new Date().toISOString();
|
|
33
|
+
console.log(`[${ts}] ${method} ${path} -> ${status}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const server = http.createServer(async (req, res) => {
|
|
37
|
+
const url = new URL(req.url ?? "/", `http://localhost:${PORT}`);
|
|
38
|
+
const method = req.method ?? "UNKNOWN";
|
|
39
|
+
|
|
40
|
+
res.setHeader("Content-Type", "application/json");
|
|
41
|
+
|
|
42
|
+
if (req.method === "POST" && url.pathname === "/upgrade") {
|
|
43
|
+
try {
|
|
44
|
+
const npmEnv = {
|
|
45
|
+
...process.env,
|
|
46
|
+
PATH: `${process.env.HOME || "/root"}/.npm-global/bin:${process.env.HOME || "/root"}/.local/bin:/usr/local/bin:${process.env.PATH}`,
|
|
47
|
+
};
|
|
48
|
+
execSync("npm install -g @vellum/openclaw-adapter@latest", {
|
|
49
|
+
encoding: "utf-8",
|
|
50
|
+
timeout: 120000,
|
|
51
|
+
env: npmEnv,
|
|
52
|
+
});
|
|
53
|
+
const child = spawn("vellum-openclaw-adapter", [], {
|
|
54
|
+
detached: true,
|
|
55
|
+
stdio: "ignore",
|
|
56
|
+
env: npmEnv,
|
|
57
|
+
});
|
|
58
|
+
child.unref();
|
|
59
|
+
const responseBody = JSON.stringify({
|
|
60
|
+
status: "success",
|
|
61
|
+
message: "HTTPS adapter installed and started. HTTP adapter shutting down.",
|
|
62
|
+
});
|
|
63
|
+
res.writeHead(200);
|
|
64
|
+
res.end(responseBody, () => {
|
|
65
|
+
log(method, url.pathname, 200);
|
|
66
|
+
server.close(() => process.exit(0));
|
|
67
|
+
});
|
|
68
|
+
} catch (e) {
|
|
69
|
+
const responseBody = JSON.stringify({
|
|
70
|
+
status: "error",
|
|
71
|
+
message: e instanceof Error ? e.message : String(e),
|
|
72
|
+
});
|
|
73
|
+
res.writeHead(500);
|
|
74
|
+
res.end(responseBody);
|
|
75
|
+
log(method, url.pathname, 500);
|
|
76
|
+
}
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (req.method === "GET" && url.pathname === "/healthz") {
|
|
81
|
+
const execEnv = {
|
|
82
|
+
...process.env,
|
|
83
|
+
PATH: `${process.env.HOME || "/root"}/.npm-global/bin:${process.env.HOME || "/root"}/.local/bin:/usr/local/bin:${process.env.PATH}`,
|
|
84
|
+
};
|
|
85
|
+
let responseBody: string;
|
|
86
|
+
try {
|
|
87
|
+
const output = execSync("openclaw health --json", {
|
|
88
|
+
encoding: "utf-8",
|
|
89
|
+
timeout: 10000,
|
|
90
|
+
env: execEnv,
|
|
91
|
+
});
|
|
92
|
+
const health = JSON.parse(output.trim()) as Record<string, unknown>;
|
|
93
|
+
const result: Record<string, unknown> = {
|
|
94
|
+
status: health.status ?? "healthy",
|
|
95
|
+
message: health.message,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const healthStr = JSON.stringify(health);
|
|
99
|
+
if (healthStr.includes("1006") || healthStr.includes("abnormal closure")) {
|
|
100
|
+
try {
|
|
101
|
+
const gatewayOutput = execSync("openclaw gateway status", {
|
|
102
|
+
encoding: "utf-8",
|
|
103
|
+
timeout: 10000,
|
|
104
|
+
env: execEnv,
|
|
105
|
+
});
|
|
106
|
+
result.message = `${result.message}\n\nGateway Status:\n${gatewayOutput.trim()}`;
|
|
107
|
+
} catch (gatewayErr) {
|
|
108
|
+
const gatewayErrMsg =
|
|
109
|
+
gatewayErr instanceof Error ? gatewayErr.message : String(gatewayErr);
|
|
110
|
+
result.message = `${result.message}\n\nGateway Status Error:\n${gatewayErrMsg}`;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
responseBody = JSON.stringify(result);
|
|
115
|
+
res.writeHead(200);
|
|
116
|
+
res.end(responseBody);
|
|
117
|
+
} catch (e) {
|
|
118
|
+
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
119
|
+
const result: Record<string, unknown> = {
|
|
120
|
+
status: "unhealthy",
|
|
121
|
+
message: errorMessage,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
if (errorMessage.includes("1006") || errorMessage.includes("abnormal closure")) {
|
|
125
|
+
try {
|
|
126
|
+
const gatewayOutput = execSync("openclaw gateway status", {
|
|
127
|
+
encoding: "utf-8",
|
|
128
|
+
timeout: 10000,
|
|
129
|
+
env: execEnv,
|
|
130
|
+
});
|
|
131
|
+
result.message = `${result.message}\n\nGateway Status:\n${gatewayOutput.trim()}`;
|
|
132
|
+
} catch (gatewayErr) {
|
|
133
|
+
const gatewayErrMsg =
|
|
134
|
+
gatewayErr instanceof Error ? gatewayErr.message : String(gatewayErr);
|
|
135
|
+
result.message = `${result.message}\n\nGateway Status Error:\n${gatewayErrMsg}`;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
responseBody = JSON.stringify(result);
|
|
140
|
+
res.writeHead(200);
|
|
141
|
+
res.end(responseBody);
|
|
142
|
+
}
|
|
143
|
+
log(method, url.pathname, 200);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const messagesMatch = url.pathname.match(/^\/v1\/assistants\/([^/]+)\/messages$/);
|
|
148
|
+
if (messagesMatch) {
|
|
149
|
+
const assistantId = messagesMatch[1];
|
|
150
|
+
|
|
151
|
+
if (req.method === "GET") {
|
|
152
|
+
const key = url.searchParams.get("conversationKey") ?? assistantId;
|
|
153
|
+
const msgs = messages[key] ?? [];
|
|
154
|
+
res.writeHead(200);
|
|
155
|
+
res.end(JSON.stringify({ messages: msgs }));
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (req.method === "POST") {
|
|
160
|
+
try {
|
|
161
|
+
const parsed = await parseBody(req);
|
|
162
|
+
const key = (parsed.conversationKey as string) || assistantId;
|
|
163
|
+
if (!messages[key]) messages[key] = [];
|
|
164
|
+
const messageId = crypto.randomUUID();
|
|
165
|
+
messages[key].push({
|
|
166
|
+
id: messageId,
|
|
167
|
+
role: "user",
|
|
168
|
+
content: parsed.content as string,
|
|
169
|
+
timestamp: new Date().toISOString(),
|
|
170
|
+
});
|
|
171
|
+
res.writeHead(200);
|
|
172
|
+
res.end(JSON.stringify({ accepted: true, messageId }));
|
|
173
|
+
} catch {
|
|
174
|
+
res.writeHead(400);
|
|
175
|
+
res.end(JSON.stringify({ error: "Invalid request body" }));
|
|
176
|
+
}
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const notFoundBody = JSON.stringify({ error: "Not found" });
|
|
182
|
+
res.writeHead(404);
|
|
183
|
+
res.end(notFoundBody);
|
|
184
|
+
log(method, url.pathname, 404);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
server.listen(PORT, "0.0.0.0", () => {
|
|
188
|
+
console.log(`OpenClaw runtime server listening on port ${PORT}`);
|
|
189
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { GATEWAY_PORT } from "../lib/constants";
|
|
2
|
+
import { buildOpenclawRuntimeServer } from "../lib/openclaw-runtime-server";
|
|
3
|
+
|
|
4
|
+
export function buildOpenclawStartupScript(
|
|
5
|
+
bearerToken: string,
|
|
6
|
+
sshUser: string,
|
|
7
|
+
anthropicApiKey: string,
|
|
8
|
+
timestampRedirect: string,
|
|
9
|
+
userSetup: string,
|
|
10
|
+
ownershipFixup: string,
|
|
11
|
+
): string {
|
|
12
|
+
const runtimeServer = buildOpenclawRuntimeServer();
|
|
13
|
+
|
|
14
|
+
return `#!/bin/bash
|
|
15
|
+
set -e
|
|
16
|
+
|
|
17
|
+
${timestampRedirect}
|
|
18
|
+
|
|
19
|
+
trap 'EXIT_CODE=\$?; if [ \$EXIT_CODE -ne 0 ]; then echo "Startup script failed with exit code \$EXIT_CODE" > /var/log/startup-error; fi' EXIT
|
|
20
|
+
${userSetup}
|
|
21
|
+
|
|
22
|
+
export OPENCLAW_NPM_LOGLEVEL=verbose
|
|
23
|
+
export OPENCLAW_NO_ONBOARD=1
|
|
24
|
+
export OPENCLAW_NO_PROMPT=1
|
|
25
|
+
|
|
26
|
+
echo "=== Pre-install diagnostics ==="
|
|
27
|
+
echo "Date: $(date -u)"
|
|
28
|
+
echo "Disk:" && df -h / 2>&1 || true
|
|
29
|
+
echo "Memory:" && free -m 2>&1 || true
|
|
30
|
+
echo "DNS:" && nslookup registry.npmjs.org 2>&1 || true
|
|
31
|
+
echo "Registry ping:" && curl -sSf --max-time 10 https://registry.npmjs.org/-/ping 2>&1 || echo "WARN: npm registry unreachable"
|
|
32
|
+
echo "=== End pre-install diagnostics ==="
|
|
33
|
+
|
|
34
|
+
echo "=== Installing build dependencies ==="
|
|
35
|
+
apt-get update -y
|
|
36
|
+
apt-get install -y build-essential python3 python3-pip git
|
|
37
|
+
pip3 install cmake
|
|
38
|
+
echo "cmake version: $(cmake --version | head -1)"
|
|
39
|
+
echo "=== Build dependencies installed ==="
|
|
40
|
+
|
|
41
|
+
curl -fsSL https://openclaw.ai/install.sh -o /tmp/openclaw-install.sh
|
|
42
|
+
chmod +x /tmp/openclaw-install.sh
|
|
43
|
+
|
|
44
|
+
set +e
|
|
45
|
+
bash /tmp/openclaw-install.sh
|
|
46
|
+
INSTALL_EXIT_CODE=\$?
|
|
47
|
+
set -e
|
|
48
|
+
|
|
49
|
+
if [ \$INSTALL_EXIT_CODE -ne 0 ]; then
|
|
50
|
+
echo "=== OpenClaw install failed (exit code: \$INSTALL_EXIT_CODE) ==="
|
|
51
|
+
echo "=== npm debug logs ==="
|
|
52
|
+
find \$HOME/.npm/_logs -name '*.log' -type f 2>/dev/null | sort | while read -r logfile; do
|
|
53
|
+
echo "--- \$logfile ---"
|
|
54
|
+
tail -n 200 "\$logfile" 2>/dev/null || true
|
|
55
|
+
done
|
|
56
|
+
echo "=== Post-failure diagnostics ==="
|
|
57
|
+
echo "Disk:" && df -h / 2>&1 || true
|
|
58
|
+
echo "Memory:" && free -m 2>&1 || true
|
|
59
|
+
echo "node version:" && node --version 2>&1 || echo "node not found"
|
|
60
|
+
echo "npm version:" && npm --version 2>&1 || echo "npm not found"
|
|
61
|
+
echo "npm config:" && npm config list 2>&1 || true
|
|
62
|
+
echo "cmake version:" && cmake --version 2>&1 || echo "cmake not found"
|
|
63
|
+
echo "PATH: \$PATH"
|
|
64
|
+
echo "=== End diagnostics ==="
|
|
65
|
+
exit \$INSTALL_EXIT_CODE
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
export PATH="\$HOME/.npm-global/bin:\$HOME/.local/bin:/usr/local/bin:\$PATH"
|
|
69
|
+
|
|
70
|
+
if ! command -v openclaw >/dev/null 2>&1; then
|
|
71
|
+
echo "ERROR: openclaw CLI installation failed. The 'openclaw' command is not available."
|
|
72
|
+
echo "PATH: \$PATH"
|
|
73
|
+
echo "which openclaw:" && which openclaw 2>&1 || true
|
|
74
|
+
echo "npm global bin:" && npm bin -g 2>&1 || true
|
|
75
|
+
echo "npm global list:" && npm list -g --depth=0 2>&1 || true
|
|
76
|
+
exit 1
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
export XDG_RUNTIME_DIR="/run/user/\$(id -u)"
|
|
80
|
+
export DBUS_SESSION_BUS_ADDRESS="unix:path=\$XDG_RUNTIME_DIR/bus"
|
|
81
|
+
mkdir -p "\$XDG_RUNTIME_DIR"
|
|
82
|
+
loginctl enable-linger root 2>/dev/null || true
|
|
83
|
+
systemctl --user daemon-reexec 2>/dev/null || true
|
|
84
|
+
|
|
85
|
+
if ! command -v bun >/dev/null 2>&1; then
|
|
86
|
+
echo "=== Installing bun ==="
|
|
87
|
+
if ! command -v unzip >/dev/null 2>&1; then
|
|
88
|
+
echo "Installing unzip (required by bun)..."
|
|
89
|
+
apt-get install -y unzip
|
|
90
|
+
fi
|
|
91
|
+
curl -fsSL https://bun.sh/install | bash
|
|
92
|
+
export BUN_INSTALL="\$HOME/.bun"
|
|
93
|
+
export PATH="\$BUN_INSTALL/bin:\$PATH"
|
|
94
|
+
echo "bun version: $(bun --version)"
|
|
95
|
+
echo "=== Bun installed ==="
|
|
96
|
+
else
|
|
97
|
+
echo "bun already installed: $(bun --version)"
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
openclaw gateway install --token ${bearerToken}
|
|
101
|
+
|
|
102
|
+
mkdir -p /root/.openclaw
|
|
103
|
+
openclaw config set env.ANTHROPIC_API_KEY "${anthropicApiKey}"
|
|
104
|
+
openclaw config set agents.defaults.model.primary "anthropic/claude-opus-4-6"
|
|
105
|
+
openclaw config set gateway.auth.token "${bearerToken}"
|
|
106
|
+
|
|
107
|
+
echo "=== Starting openclaw gateway at user level ==="
|
|
108
|
+
systemctl --user daemon-reload
|
|
109
|
+
systemctl --user enable --now openclaw-gateway.service
|
|
110
|
+
|
|
111
|
+
export PORT=${GATEWAY_PORT}
|
|
112
|
+
|
|
113
|
+
echo "=== Starting OpenClaw runtime server ==="
|
|
114
|
+
${runtimeServer}
|
|
115
|
+
echo "=== OpenClaw runtime server started ==="
|
|
116
|
+
${ownershipFixup}
|
|
117
|
+
`;
|
|
118
|
+
}
|