@vellumai/cli 0.1.4 → 0.1.6
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/package.json +1 -1
- package/src/adapters/openclaw.ts +3 -3
- package/src/commands/hatch.ts +85 -37
- package/src/lib/aws.ts +11 -6
- package/src/lib/gcp.ts +47 -8
- package/src/lib/openclaw-runtime-server.ts +2 -3
package/package.json
CHANGED
package/src/adapters/openclaw.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { GATEWAY_PORT } from "../lib/constants";
|
|
2
2
|
import { buildOpenclawRuntimeServer } from "../lib/openclaw-runtime-server";
|
|
3
3
|
|
|
4
|
-
export function buildOpenclawStartupScript(
|
|
4
|
+
export async function buildOpenclawStartupScript(
|
|
5
5
|
bearerToken: string,
|
|
6
6
|
sshUser: string,
|
|
7
7
|
anthropicApiKey: string,
|
|
8
8
|
timestampRedirect: string,
|
|
9
9
|
userSetup: string,
|
|
10
10
|
ownershipFixup: string,
|
|
11
|
-
): string {
|
|
12
|
-
const runtimeServer = buildOpenclawRuntimeServer();
|
|
11
|
+
): Promise<string> {
|
|
12
|
+
const runtimeServer = await buildOpenclawRuntimeServer();
|
|
13
13
|
|
|
14
14
|
return `#!/bin/bash
|
|
15
15
|
set -e
|
package/src/commands/hatch.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawn } from "child_process";
|
|
2
2
|
import { randomBytes } from "crypto";
|
|
3
|
-
import { existsSync, unlinkSync, writeFileSync } from "fs";
|
|
3
|
+
import { existsSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
4
|
+
import { createRequire } from "module";
|
|
4
5
|
import { tmpdir, userInfo } from "os";
|
|
5
6
|
import { dirname, join } from "path";
|
|
6
7
|
|
|
@@ -17,11 +18,13 @@ import {
|
|
|
17
18
|
} from "../lib/constants";
|
|
18
19
|
import type { RemoteHost, Species } from "../lib/constants";
|
|
19
20
|
import type { FirewallRuleSpec } from "../lib/gcp";
|
|
20
|
-
import { getActiveProject, instanceExists, syncFirewallRules } from "../lib/gcp";
|
|
21
|
+
import { fetchAndDisplayStartupLogs, getActiveProject, instanceExists, syncFirewallRules } from "../lib/gcp";
|
|
21
22
|
import { buildInterfacesSeed } from "../lib/interfaces-seed";
|
|
22
23
|
import { generateRandomSuffix } from "../lib/random-name";
|
|
23
24
|
import { exec, execOutput } from "../lib/step-runner";
|
|
24
25
|
|
|
26
|
+
const _require = createRequire(import.meta.url);
|
|
27
|
+
|
|
25
28
|
const INSTALL_SCRIPT_REMOTE_PATH = "/tmp/vellum-install.sh";
|
|
26
29
|
const INSTALL_SCRIPT_PATH = join(import.meta.dir, "..", "adapters", "install.sh");
|
|
27
30
|
const MACHINE_TYPE = "e2-standard-4"; // 4 vCPUs, 16 GB memory
|
|
@@ -76,19 +79,19 @@ chown -R "$SSH_USER:$SSH_USER" "$SSH_USER_HOME" 2>/dev/null || true
|
|
|
76
79
|
`;
|
|
77
80
|
}
|
|
78
81
|
|
|
79
|
-
export function buildStartupScript(
|
|
82
|
+
export async function buildStartupScript(
|
|
80
83
|
species: Species,
|
|
81
84
|
bearerToken: string,
|
|
82
85
|
sshUser: string,
|
|
83
86
|
anthropicApiKey: string,
|
|
84
|
-
): string {
|
|
87
|
+
): Promise<string> {
|
|
85
88
|
const platformUrl = process.env.VELLUM_ASSISTANT_PLATFORM_URL ?? "https://assistant.vellum.ai";
|
|
86
89
|
const timestampRedirect = buildTimestampRedirect();
|
|
87
90
|
const userSetup = buildUserSetup(sshUser);
|
|
88
91
|
const ownershipFixup = buildOwnershipFixup();
|
|
89
92
|
|
|
90
93
|
if (species === "openclaw") {
|
|
91
|
-
return buildOpenclawStartupScript(
|
|
94
|
+
return await buildOpenclawStartupScript(
|
|
92
95
|
bearerToken,
|
|
93
96
|
sshUser,
|
|
94
97
|
anthropicApiKey,
|
|
@@ -105,7 +108,7 @@ set -e
|
|
|
105
108
|
|
|
106
109
|
${timestampRedirect}
|
|
107
110
|
|
|
108
|
-
trap 'EXIT_CODE=\$?; if [ \$EXIT_CODE -ne 0 ]; then echo "Startup script failed with exit code \$EXIT_CODE" > /var/log/startup-error; fi' EXIT
|
|
111
|
+
trap 'EXIT_CODE=\$?; if [ \$EXIT_CODE -ne 0 ]; then echo "Startup script failed with exit code \$EXIT_CODE at line \$LINENO" > /var/log/startup-error; echo "Last 20 log lines:" >> /var/log/startup-error; tail -20 /var/log/startup-script.log >> /var/log/startup-error 2>/dev/null || true; fi' EXIT
|
|
109
112
|
${userSetup}
|
|
110
113
|
ANTHROPIC_API_KEY=${anthropicApiKey}
|
|
111
114
|
GATEWAY_RUNTIME_PROXY_ENABLED=true
|
|
@@ -117,6 +120,7 @@ ANTHROPIC_API_KEY=\$ANTHROPIC_API_KEY
|
|
|
117
120
|
GATEWAY_RUNTIME_PROXY_ENABLED=\$GATEWAY_RUNTIME_PROXY_ENABLED
|
|
118
121
|
RUNTIME_PROXY_BEARER_TOKEN=\$RUNTIME_PROXY_BEARER_TOKEN
|
|
119
122
|
INTERFACES_SEED_DIR=\$INTERFACES_SEED_DIR
|
|
123
|
+
RUNTIME_HTTP_PORT=7821
|
|
120
124
|
DOTENV_EOF
|
|
121
125
|
|
|
122
126
|
mkdir -p "\$HOME/.vellum/workspace"
|
|
@@ -131,8 +135,11 @@ CONFIG_EOF
|
|
|
131
135
|
${ownershipFixup}
|
|
132
136
|
|
|
133
137
|
export VELLUM_SSH_USER="\$SSH_USER"
|
|
138
|
+
echo "Downloading install script from ${platformUrl}/install.sh..."
|
|
134
139
|
curl -fsSL ${platformUrl}/install.sh -o ${INSTALL_SCRIPT_REMOTE_PATH}
|
|
140
|
+
echo "Install script downloaded (\$(wc -c < ${INSTALL_SCRIPT_REMOTE_PATH}) bytes)"
|
|
135
141
|
chmod +x ${INSTALL_SCRIPT_REMOTE_PATH}
|
|
142
|
+
echo "Running install script..."
|
|
136
143
|
source ${INSTALL_SCRIPT_REMOTE_PATH}
|
|
137
144
|
`;
|
|
138
145
|
}
|
|
@@ -192,6 +199,7 @@ export interface PollResult {
|
|
|
192
199
|
lastLine: string | null;
|
|
193
200
|
done: boolean;
|
|
194
201
|
failed: boolean;
|
|
202
|
+
errorContent: string;
|
|
195
203
|
}
|
|
196
204
|
|
|
197
205
|
async function pollInstance(
|
|
@@ -223,7 +231,7 @@ async function pollInstance(
|
|
|
223
231
|
const output = await execOutput("gcloud", args);
|
|
224
232
|
const sepIdx = output.indexOf("===HATCH_SEP===");
|
|
225
233
|
if (sepIdx === -1) {
|
|
226
|
-
return { lastLine: output.trim() || null, done: false, failed: false };
|
|
234
|
+
return { lastLine: output.trim() || null, done: false, failed: false, errorContent: "" };
|
|
227
235
|
}
|
|
228
236
|
const errIdx = output.indexOf("===HATCH_ERR===");
|
|
229
237
|
const lastLine = output.substring(0, sepIdx).trim() || null;
|
|
@@ -233,9 +241,9 @@ async function pollInstance(
|
|
|
233
241
|
errIdx === -1 ? "" : output.substring(errIdx + "===HATCH_ERR===".length).trim();
|
|
234
242
|
const done = lastLine !== null && status !== "active" && status !== "activating";
|
|
235
243
|
const failed = errorContent.length > 0 || status === "failed";
|
|
236
|
-
return { lastLine, done, failed };
|
|
244
|
+
return { lastLine, done, failed, errorContent };
|
|
237
245
|
} catch {
|
|
238
|
-
return { lastLine: null, done: false, failed: false };
|
|
246
|
+
return { lastLine: null, done: false, failed: false, errorContent: "" };
|
|
239
247
|
}
|
|
240
248
|
}
|
|
241
249
|
|
|
@@ -322,17 +330,23 @@ async function recoverFromCurlFailure(
|
|
|
322
330
|
await exec("gcloud", sshArgs);
|
|
323
331
|
}
|
|
324
332
|
|
|
333
|
+
export interface WatchHatchingResult {
|
|
334
|
+
success: boolean;
|
|
335
|
+
errorContent: string;
|
|
336
|
+
}
|
|
337
|
+
|
|
325
338
|
export async function watchHatching(
|
|
326
339
|
pollFn: () => Promise<PollResult>,
|
|
327
340
|
instanceName: string,
|
|
328
341
|
startTime: number,
|
|
329
342
|
species: Species,
|
|
330
|
-
): Promise<
|
|
343
|
+
): Promise<WatchHatchingResult> {
|
|
331
344
|
let spinnerIdx = 0;
|
|
332
345
|
let lastLogLine: string | null = null;
|
|
333
346
|
let linesDrawn = 0;
|
|
334
347
|
let finished = false;
|
|
335
348
|
let failed = false;
|
|
349
|
+
let lastErrorContent = "";
|
|
336
350
|
let pollInFlight = false;
|
|
337
351
|
let nextPollAt = Date.now() + 15000;
|
|
338
352
|
|
|
@@ -382,6 +396,9 @@ export async function watchHatching(
|
|
|
382
396
|
if (result.lastLine) {
|
|
383
397
|
lastLogLine = result.lastLine;
|
|
384
398
|
}
|
|
399
|
+
if (result.errorContent) {
|
|
400
|
+
lastErrorContent = result.errorContent;
|
|
401
|
+
}
|
|
385
402
|
if (result.done) {
|
|
386
403
|
finished = true;
|
|
387
404
|
failed = result.failed;
|
|
@@ -392,12 +409,12 @@ export async function watchHatching(
|
|
|
392
409
|
}
|
|
393
410
|
}
|
|
394
411
|
|
|
395
|
-
return new Promise<
|
|
412
|
+
return new Promise<WatchHatchingResult>((resolve) => {
|
|
396
413
|
const interval = setInterval(() => {
|
|
397
414
|
if (finished) {
|
|
398
415
|
draw();
|
|
399
416
|
clearInterval(interval);
|
|
400
|
-
resolve(!failed);
|
|
417
|
+
resolve({ success: !failed, errorContent: lastErrorContent });
|
|
401
418
|
return;
|
|
402
419
|
}
|
|
403
420
|
|
|
@@ -408,7 +425,7 @@ export async function watchHatching(
|
|
|
408
425
|
console.log(` ⏰ Timed out after ${formatElapsed(elapsed)}. Instance is still running.`);
|
|
409
426
|
console.log(` Monitor with: vel logs ${instanceName}`);
|
|
410
427
|
console.log("");
|
|
411
|
-
resolve(true);
|
|
428
|
+
resolve({ success: true, errorContent: lastErrorContent });
|
|
412
429
|
return;
|
|
413
430
|
}
|
|
414
431
|
|
|
@@ -485,7 +502,7 @@ async function hatchGcp(
|
|
|
485
502
|
console.error("Error: ANTHROPIC_API_KEY environment variable is not set.");
|
|
486
503
|
process.exit(1);
|
|
487
504
|
}
|
|
488
|
-
const startupScript = buildStartupScript(species, bearerToken, sshUser, anthropicApiKey);
|
|
505
|
+
const startupScript = await buildStartupScript(species, bearerToken, sshUser, anthropicApiKey);
|
|
489
506
|
const startupScriptPath = join(tmpdir(), `${instanceName}-startup.sh`);
|
|
490
507
|
writeFileSync(startupScriptPath, startupScript);
|
|
491
508
|
|
|
@@ -570,25 +587,32 @@ async function hatchGcp(
|
|
|
570
587
|
console.log(" Press Ctrl+C to detach (instance will keep running)");
|
|
571
588
|
console.log("");
|
|
572
589
|
|
|
573
|
-
const
|
|
590
|
+
const result = await watchHatching(
|
|
574
591
|
() => pollInstance(instanceName, project, zone, account),
|
|
575
592
|
instanceName,
|
|
576
593
|
startTime,
|
|
577
594
|
species,
|
|
578
595
|
);
|
|
579
596
|
|
|
580
|
-
if (!success) {
|
|
597
|
+
if (!result.success) {
|
|
598
|
+
console.log("");
|
|
599
|
+
if (result.errorContent) {
|
|
600
|
+
console.log("📋 Startup error:");
|
|
601
|
+
console.log(` ${result.errorContent}`);
|
|
602
|
+
console.log("");
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
await fetchAndDisplayStartupLogs(instanceName, project, zone, account);
|
|
606
|
+
|
|
581
607
|
if (
|
|
582
608
|
species === "vellum" &&
|
|
583
609
|
(await checkCurlFailure(instanceName, project, zone, account))
|
|
584
610
|
) {
|
|
585
|
-
console.log("");
|
|
586
611
|
const installScriptUrl = `${process.env.VELLUM_ASSISTANT_PLATFORM_URL ?? "https://assistant.vellum.ai"}/install.sh`;
|
|
587
612
|
console.log(`🔄 Detected install script curl failure for ${installScriptUrl}, attempting recovery...`);
|
|
588
613
|
await recoverFromCurlFailure(instanceName, project, zone, sshUser, account);
|
|
589
614
|
console.log("✅ Recovery successful!");
|
|
590
615
|
} else {
|
|
591
|
-
console.log("");
|
|
592
616
|
process.exit(1);
|
|
593
617
|
}
|
|
594
618
|
}
|
|
@@ -650,7 +674,7 @@ async function hatchCustom(
|
|
|
650
674
|
process.exit(1);
|
|
651
675
|
}
|
|
652
676
|
|
|
653
|
-
const startupScript = buildStartupScript(species, bearerToken, sshUser, anthropicApiKey);
|
|
677
|
+
const startupScript = await buildStartupScript(species, bearerToken, sshUser, anthropicApiKey);
|
|
654
678
|
const startupScriptPath = join(tmpdir(), `${instanceName}-startup.sh`);
|
|
655
679
|
writeFileSync(startupScriptPath, startupScript);
|
|
656
680
|
|
|
@@ -715,6 +739,22 @@ async function hatchCustom(
|
|
|
715
739
|
}
|
|
716
740
|
}
|
|
717
741
|
|
|
742
|
+
function resolveGatewayDir(): string {
|
|
743
|
+
const sourceDir = join(import.meta.dir, "..", "..", "..", "gateway");
|
|
744
|
+
if (existsSync(sourceDir)) {
|
|
745
|
+
return sourceDir;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
try {
|
|
749
|
+
const pkgPath = _require.resolve("@vellumai/vellum-gateway/package.json");
|
|
750
|
+
return dirname(pkgPath);
|
|
751
|
+
} catch {
|
|
752
|
+
throw new Error(
|
|
753
|
+
"Gateway not found. Ensure @vellumai/vellum-gateway is installed or run from the source tree.",
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
718
758
|
async function hatchLocal(species: Species, name: string | null): Promise<void> {
|
|
719
759
|
const instanceName = name ?? `${species}-${generateRandomSuffix()}`;
|
|
720
760
|
|
|
@@ -777,24 +817,19 @@ async function hatchLocal(species: Species, name: string | null): Promise<void>
|
|
|
777
817
|
}
|
|
778
818
|
|
|
779
819
|
console.log("🌐 Starting gateway...");
|
|
780
|
-
const gatewayDir =
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
},
|
|
794
|
-
});
|
|
795
|
-
gateway.unref();
|
|
796
|
-
console.log("✅ Gateway started\n");
|
|
797
|
-
}
|
|
820
|
+
const gatewayDir = resolveGatewayDir();
|
|
821
|
+
const gateway = spawn("bun", ["run", "src/index.ts"], {
|
|
822
|
+
cwd: gatewayDir,
|
|
823
|
+
detached: true,
|
|
824
|
+
stdio: "ignore",
|
|
825
|
+
env: {
|
|
826
|
+
...process.env,
|
|
827
|
+
GATEWAY_RUNTIME_PROXY_ENABLED: "true",
|
|
828
|
+
GATEWAY_RUNTIME_PROXY_REQUIRE_AUTH: "false",
|
|
829
|
+
},
|
|
830
|
+
});
|
|
831
|
+
gateway.unref();
|
|
832
|
+
console.log("✅ Gateway started\n");
|
|
798
833
|
|
|
799
834
|
const runtimeUrl = `http://localhost:${GATEWAY_PORT}`;
|
|
800
835
|
const localEntry: AssistantEntry = {
|
|
@@ -815,7 +850,20 @@ async function hatchLocal(species: Species, name: string | null): Promise<void>
|
|
|
815
850
|
console.log("");
|
|
816
851
|
}
|
|
817
852
|
|
|
853
|
+
function getCliVersion(): string {
|
|
854
|
+
try {
|
|
855
|
+
const pkgPath = join(import.meta.dir, "..", "package.json");
|
|
856
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
857
|
+
return pkg.version ?? "unknown";
|
|
858
|
+
} catch {
|
|
859
|
+
return "unknown";
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
818
863
|
export async function hatch(): Promise<void> {
|
|
864
|
+
const cliVersion = getCliVersion();
|
|
865
|
+
console.log(`@vellumai/cli v${cliVersion}`);
|
|
866
|
+
|
|
819
867
|
const { species, detached, name, remote } = parseArgs();
|
|
820
868
|
|
|
821
869
|
if (remote === "local") {
|
package/src/lib/aws.ts
CHANGED
|
@@ -338,7 +338,7 @@ async function pollAwsInstance(
|
|
|
338
338
|
const output = await awsSshExec(ip, keyPath, remoteCmd);
|
|
339
339
|
const sepIdx = output.indexOf("===HATCH_SEP===");
|
|
340
340
|
if (sepIdx === -1) {
|
|
341
|
-
return { lastLine: output.trim() || null, done: false, failed: false };
|
|
341
|
+
return { lastLine: output.trim() || null, done: false, failed: false, errorContent: "" };
|
|
342
342
|
}
|
|
343
343
|
const errIdx = output.indexOf("===HATCH_ERR===");
|
|
344
344
|
const lastLine = output.substring(0, sepIdx).trim() || null;
|
|
@@ -348,9 +348,9 @@ async function pollAwsInstance(
|
|
|
348
348
|
errIdx === -1 ? "" : output.substring(errIdx + "===HATCH_ERR===".length).trim();
|
|
349
349
|
const done = lastLine !== null && status !== "running" && status !== "pending";
|
|
350
350
|
const failed = errorContent.length > 0 || status === "error";
|
|
351
|
-
return { lastLine, done, failed };
|
|
351
|
+
return { lastLine, done, failed, errorContent };
|
|
352
352
|
} catch {
|
|
353
|
-
return { lastLine: null, done: false, failed: false };
|
|
353
|
+
return { lastLine: null, done: false, failed: false, errorContent: "" };
|
|
354
354
|
}
|
|
355
355
|
}
|
|
356
356
|
|
|
@@ -420,7 +420,7 @@ export async function hatchAws(
|
|
|
420
420
|
console.log("\u{1F50D} Finding latest Debian AMI...");
|
|
421
421
|
const amiId = await getLatestDebianAmi(region);
|
|
422
422
|
|
|
423
|
-
const startupScript = buildStartupScript(species, bearerToken, sshUser, anthropicApiKey);
|
|
423
|
+
const startupScript = await buildStartupScript(species, bearerToken, sshUser, anthropicApiKey);
|
|
424
424
|
const startupScriptPath = join(tmpdir(), `${instanceName}-startup.sh`);
|
|
425
425
|
writeFileSync(startupScriptPath, startupScript);
|
|
426
426
|
|
|
@@ -488,15 +488,20 @@ export async function hatchAws(
|
|
|
488
488
|
|
|
489
489
|
if (externalIp) {
|
|
490
490
|
const ip = externalIp;
|
|
491
|
-
const
|
|
491
|
+
const result = await watchHatching(
|
|
492
492
|
() => pollAwsInstance(ip, keyPath),
|
|
493
493
|
instanceName,
|
|
494
494
|
startTime,
|
|
495
495
|
species,
|
|
496
496
|
);
|
|
497
497
|
|
|
498
|
-
if (!success) {
|
|
498
|
+
if (!result.success) {
|
|
499
499
|
console.log("");
|
|
500
|
+
if (result.errorContent) {
|
|
501
|
+
console.log("📋 Startup error:");
|
|
502
|
+
console.log(` ${result.errorContent}`);
|
|
503
|
+
console.log("");
|
|
504
|
+
}
|
|
500
505
|
process.exit(1);
|
|
501
506
|
}
|
|
502
507
|
} else {
|
package/src/lib/gcp.ts
CHANGED
|
@@ -139,15 +139,14 @@ export async function syncFirewallRules(
|
|
|
139
139
|
"firewall-rules",
|
|
140
140
|
"list",
|
|
141
141
|
`--project=${project}`,
|
|
142
|
-
|
|
143
|
-
"--format=value(name)",
|
|
142
|
+
"--format=json(name,targetTags)",
|
|
144
143
|
];
|
|
145
144
|
if (account) listArgs.push(`--account=${account}`);
|
|
146
145
|
const output = await execOutput("gcloud", listArgs);
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
.
|
|
150
|
-
.
|
|
146
|
+
const allRules = JSON.parse(output) as Array<{ name: string; targetTags?: string[] }>;
|
|
147
|
+
existingNames = allRules
|
|
148
|
+
.filter((r) => r.targetTags?.includes(tag))
|
|
149
|
+
.map((r) => r.name);
|
|
151
150
|
} catch {
|
|
152
151
|
existingNames = [];
|
|
153
152
|
}
|
|
@@ -190,10 +189,11 @@ export async function fetchFirewallRules(
|
|
|
190
189
|
"firewall-rules",
|
|
191
190
|
"list",
|
|
192
191
|
`--project=${project}`,
|
|
193
|
-
`--filter=targetTags:${tag}`,
|
|
194
192
|
"--format=json",
|
|
195
193
|
]);
|
|
196
|
-
|
|
194
|
+
const rules = JSON.parse(output) as Array<{ targetTags?: string[] }>;
|
|
195
|
+
const filtered = rules.filter((r) => r.targetTags?.includes(tag));
|
|
196
|
+
return JSON.stringify(filtered, null, 2);
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
export interface GcpInstance {
|
|
@@ -274,6 +274,45 @@ export async function sshCommand(
|
|
|
274
274
|
]);
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
+
export async function fetchAndDisplayStartupLogs(
|
|
278
|
+
instanceName: string,
|
|
279
|
+
project: string,
|
|
280
|
+
zone: string,
|
|
281
|
+
account?: string,
|
|
282
|
+
): Promise<void> {
|
|
283
|
+
try {
|
|
284
|
+
const remoteCmd =
|
|
285
|
+
'echo "=== Last 50 lines of /var/log/startup-script.log ==="; ' +
|
|
286
|
+
"tail -50 /var/log/startup-script.log 2>/dev/null || echo '(no startup log found)'; " +
|
|
287
|
+
'echo ""; ' +
|
|
288
|
+
'echo "=== /var/log/startup-error ==="; ' +
|
|
289
|
+
"cat /var/log/startup-error 2>/dev/null || echo '(no error file found)'";
|
|
290
|
+
const args = [
|
|
291
|
+
"compute",
|
|
292
|
+
"ssh",
|
|
293
|
+
instanceName,
|
|
294
|
+
`--project=${project}`,
|
|
295
|
+
`--zone=${zone}`,
|
|
296
|
+
"--quiet",
|
|
297
|
+
"--ssh-flag=-o StrictHostKeyChecking=no",
|
|
298
|
+
"--ssh-flag=-o UserKnownHostsFile=/dev/null",
|
|
299
|
+
"--ssh-flag=-o ConnectTimeout=10",
|
|
300
|
+
"--ssh-flag=-o LogLevel=ERROR",
|
|
301
|
+
`--command=${remoteCmd}`,
|
|
302
|
+
];
|
|
303
|
+
if (account) args.push(`--account=${account}`);
|
|
304
|
+
const output = await execOutput("gcloud", args);
|
|
305
|
+
console.log("📋 Startup logs from instance:");
|
|
306
|
+
for (const line of output.split("\n")) {
|
|
307
|
+
console.log(` ${line}`);
|
|
308
|
+
}
|
|
309
|
+
console.log("");
|
|
310
|
+
} catch {
|
|
311
|
+
console.log("⚠️ Could not retrieve startup logs from instance");
|
|
312
|
+
console.log("");
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
277
316
|
export async function retireInstance(
|
|
278
317
|
name: string,
|
|
279
318
|
project: string,
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { join } from "path";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export function buildOpenclawRuntimeServer(): string {
|
|
3
|
+
export async function buildOpenclawRuntimeServer(): Promise<string> {
|
|
4
|
+
const serverSource = await Bun.file(join(import.meta.dir, "..", "adapters", "openclaw-http-server.ts")).text();
|
|
6
5
|
|
|
7
6
|
return `
|
|
8
7
|
cat > /opt/openclaw-runtime-server.ts << 'RUNTIME_SERVER_EOF'
|