hybrid 2.0.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -29
- package/dist/cli.cjs +1701 -643
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1714 -639
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1714 -639
- package/dist/index.js.map +1 -1
- package/dist/server/index.cjs +229318 -17432
- package/package.json +12 -7
- package/skills/skills-manager/SKILL.md +1 -1
- package/templates/agent/.env.example +0 -4
- package/dist/xmtp.cjs +0 -6122
- package/skills/xmtp/SKILL.md +0 -85
package/dist/cli.cjs
CHANGED
|
@@ -6,6 +6,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
9
12
|
var __export = (target, all) => {
|
|
10
13
|
for (var name in all)
|
|
11
14
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -28,20 +31,1393 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
28
31
|
));
|
|
29
32
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
33
|
|
|
34
|
+
// ../../node_modules/.pnpm/tsup@8.5.0_jiti@2.6.0_postcss@8.5.6_tsx@4.20.5_typescript@5.9.3_yaml@2.8.1/node_modules/tsup/assets/cjs_shims.js
|
|
35
|
+
var getImportMetaUrl, importMetaUrl;
|
|
36
|
+
var init_cjs_shims = __esm({
|
|
37
|
+
"../../node_modules/.pnpm/tsup@8.5.0_jiti@2.6.0_postcss@8.5.6_tsx@4.20.5_typescript@5.9.3_yaml@2.8.1/node_modules/tsup/assets/cjs_shims.js"() {
|
|
38
|
+
"use strict";
|
|
39
|
+
getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.src || new URL("main.js", document.baseURI).href;
|
|
40
|
+
importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// src/deploy/providers/sprite.provider.ts
|
|
45
|
+
var sprite_provider_exports = {};
|
|
46
|
+
__export(sprite_provider_exports, {
|
|
47
|
+
spriteProvider: () => spriteProvider
|
|
48
|
+
});
|
|
49
|
+
var import_node_child_process, import_node_fs, import_node_os, import_node_path, spriteProvider;
|
|
50
|
+
var init_sprite_provider = __esm({
|
|
51
|
+
"src/deploy/providers/sprite.provider.ts"() {
|
|
52
|
+
"use strict";
|
|
53
|
+
init_cjs_shims();
|
|
54
|
+
import_node_child_process = require("child_process");
|
|
55
|
+
import_node_fs = require("fs");
|
|
56
|
+
import_node_os = require("os");
|
|
57
|
+
import_node_path = require("path");
|
|
58
|
+
spriteProvider = {
|
|
59
|
+
name: "sprites",
|
|
60
|
+
label: "Sprites.dev (Firecracker)",
|
|
61
|
+
defaultName(projectDir) {
|
|
62
|
+
return (0, import_node_path.basename)(projectDir).toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
63
|
+
},
|
|
64
|
+
async authCheck() {
|
|
65
|
+
try {
|
|
66
|
+
(0, import_node_child_process.execFileSync)("sprite", ["list"], { stdio: "pipe", timeout: 5e3 });
|
|
67
|
+
} catch (err) {
|
|
68
|
+
if (err.code === "ENOENT") {
|
|
69
|
+
throw new Error(
|
|
70
|
+
"'sprite' CLI not found.\n Install: https://sprites.dev/docs/install\n Or: npm i -g sprite-cli"
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
throw new Error(
|
|
74
|
+
`Sprite CLI authentication failed.
|
|
75
|
+
Run: sprite login
|
|
76
|
+
Error: ${err.stderr || err.message}`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
async provision(name, _opts) {
|
|
81
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(name)) {
|
|
82
|
+
throw new Error(
|
|
83
|
+
`Invalid sprite name: ${name}
|
|
84
|
+
Name must start with a letter/digit and contain only letters, digits, hyphens, and underscores.`
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const existing = (0, import_node_child_process.execFileSync)("sprite", ["list"], {
|
|
89
|
+
encoding: "utf-8"
|
|
90
|
+
});
|
|
91
|
+
if (existing.includes(name)) {
|
|
92
|
+
return name;
|
|
93
|
+
}
|
|
94
|
+
} catch {
|
|
95
|
+
}
|
|
96
|
+
console.log(`
|
|
97
|
+
\u{1F4E6} Creating sprite: ${name}`);
|
|
98
|
+
try {
|
|
99
|
+
(0, import_node_child_process.execFileSync)("sprite", ["create", "-skip-console", name], {
|
|
100
|
+
stdio: "inherit"
|
|
101
|
+
});
|
|
102
|
+
} catch {
|
|
103
|
+
const existing = (0, import_node_child_process.execFileSync)("sprite", ["list"], {
|
|
104
|
+
encoding: "utf-8"
|
|
105
|
+
});
|
|
106
|
+
if (existing.includes(name)) {
|
|
107
|
+
console.log(` Sprite ${name} already exists, using it`);
|
|
108
|
+
return name;
|
|
109
|
+
}
|
|
110
|
+
throw new Error("Failed to create sprite");
|
|
111
|
+
}
|
|
112
|
+
console.log("\n\u23F3 Waiting for sprite to be ready...");
|
|
113
|
+
for (let i = 0; i < 30; i++) {
|
|
114
|
+
try {
|
|
115
|
+
(0, import_node_child_process.execFileSync)("sprite", ["exec", "-s", name, "--", "echo", "ready"], {
|
|
116
|
+
stdio: "pipe"
|
|
117
|
+
});
|
|
118
|
+
console.log(" \u2713 Sprite ready");
|
|
119
|
+
return name;
|
|
120
|
+
} catch {
|
|
121
|
+
if (i % 5 === 4) {
|
|
122
|
+
console.log(` Still waiting... (${i + 1}/30)`);
|
|
123
|
+
}
|
|
124
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
throw new Error("Sprite did not become ready in time");
|
|
128
|
+
},
|
|
129
|
+
async deploy(instanceId, distDir) {
|
|
130
|
+
console.log("\n\u{1F4E4} Uploading build artifacts...");
|
|
131
|
+
const tarPath = (0, import_node_path.join)((0, import_node_os.tmpdir)(), `hybrid-deploy-${Date.now()}.tar.gz`);
|
|
132
|
+
(0, import_node_child_process.execFileSync)("tar", ["-czf", tarPath, "-C", distDir, "."], {
|
|
133
|
+
stdio: "pipe"
|
|
134
|
+
});
|
|
135
|
+
(0, import_node_child_process.execFileSync)(
|
|
136
|
+
"sprite",
|
|
137
|
+
["exec", "-s", instanceId, "--", "mkdir", "-p", "/app"],
|
|
138
|
+
{
|
|
139
|
+
stdio: "pipe"
|
|
140
|
+
}
|
|
141
|
+
);
|
|
142
|
+
let uploadSuccess = false;
|
|
143
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
144
|
+
try {
|
|
145
|
+
(0, import_node_child_process.execFileSync)(
|
|
146
|
+
"sprite",
|
|
147
|
+
[
|
|
148
|
+
"exec",
|
|
149
|
+
"-s",
|
|
150
|
+
instanceId,
|
|
151
|
+
"-file",
|
|
152
|
+
`${tarPath}:/tmp/hybrid-deploy.tar.gz`,
|
|
153
|
+
"--",
|
|
154
|
+
"tar",
|
|
155
|
+
"-xzf",
|
|
156
|
+
"/tmp/hybrid-deploy.tar.gz",
|
|
157
|
+
"-C",
|
|
158
|
+
"/app"
|
|
159
|
+
],
|
|
160
|
+
{ stdio: "inherit" }
|
|
161
|
+
);
|
|
162
|
+
uploadSuccess = true;
|
|
163
|
+
break;
|
|
164
|
+
} catch {
|
|
165
|
+
if (attempt < 2) {
|
|
166
|
+
console.log(` Upload failed, retrying... (${attempt + 1}/3)`);
|
|
167
|
+
await new Promise((r) => setTimeout(r, 5e3));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if ((0, import_node_fs.existsSync)(tarPath)) {
|
|
172
|
+
try {
|
|
173
|
+
(0, import_node_fs.unlinkSync)(tarPath);
|
|
174
|
+
} catch {
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (!uploadSuccess) {
|
|
178
|
+
throw new Error("Failed to upload build artifacts after 3 attempts");
|
|
179
|
+
}
|
|
180
|
+
console.log("\n\u{1F4E6} Installing dependencies...");
|
|
181
|
+
(0, import_node_child_process.execFileSync)(
|
|
182
|
+
"sprite",
|
|
183
|
+
[
|
|
184
|
+
"exec",
|
|
185
|
+
"-s",
|
|
186
|
+
instanceId,
|
|
187
|
+
"--",
|
|
188
|
+
"bash",
|
|
189
|
+
"-c",
|
|
190
|
+
"cd /app && npm install --production"
|
|
191
|
+
],
|
|
192
|
+
{ stdio: "inherit" }
|
|
193
|
+
);
|
|
194
|
+
console.log("\n\u{1F527} Starting agent...");
|
|
195
|
+
const { writeFileSync: writeFileSync2 } = await import("fs");
|
|
196
|
+
const scriptPath = (0, import_node_path.join)((0, import_node_os.tmpdir)(), `hybrid-start-${Date.now()}.sh`);
|
|
197
|
+
const startupScript = `#!/bin/bash
|
|
198
|
+
cd /app
|
|
199
|
+
export NODE_ENV=production
|
|
200
|
+
export AGENT_PORT=8454
|
|
201
|
+
exec nohup node server/index.cjs > /app/agent.log 2>&1 &
|
|
202
|
+
echo $! > /app/agent.pid
|
|
203
|
+
`;
|
|
204
|
+
writeFileSync2(scriptPath, startupScript, { mode: 493 });
|
|
205
|
+
(0, import_node_child_process.execFileSync)(
|
|
206
|
+
"sprite",
|
|
207
|
+
[
|
|
208
|
+
"exec",
|
|
209
|
+
"-s",
|
|
210
|
+
instanceId,
|
|
211
|
+
"-file",
|
|
212
|
+
`${scriptPath}:/app/start-agent.sh`,
|
|
213
|
+
"--",
|
|
214
|
+
"chmod",
|
|
215
|
+
"+x",
|
|
216
|
+
"/app/start-agent.sh"
|
|
217
|
+
],
|
|
218
|
+
{ stdio: "pipe" }
|
|
219
|
+
);
|
|
220
|
+
(0, import_node_child_process.execFileSync)(
|
|
221
|
+
"sprite",
|
|
222
|
+
["exec", "-s", instanceId, "--", "bash", "/app/start-agent.sh"],
|
|
223
|
+
{ stdio: "inherit" }
|
|
224
|
+
);
|
|
225
|
+
try {
|
|
226
|
+
(0, import_node_fs.unlinkSync)(scriptPath);
|
|
227
|
+
} catch {
|
|
228
|
+
}
|
|
229
|
+
console.log("\n\u23F3 Waiting for agent to start...");
|
|
230
|
+
let ready = false;
|
|
231
|
+
for (let i = 0; i < 15; i++) {
|
|
232
|
+
try {
|
|
233
|
+
const result = (0, import_node_child_process.execFileSync)(
|
|
234
|
+
"sprite",
|
|
235
|
+
[
|
|
236
|
+
"exec",
|
|
237
|
+
"-s",
|
|
238
|
+
instanceId,
|
|
239
|
+
"--",
|
|
240
|
+
"curl",
|
|
241
|
+
"-s",
|
|
242
|
+
"http://localhost:8454/health"
|
|
243
|
+
],
|
|
244
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
245
|
+
);
|
|
246
|
+
if (result) {
|
|
247
|
+
ready = true;
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
} catch {
|
|
251
|
+
}
|
|
252
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
253
|
+
}
|
|
254
|
+
if (!ready) {
|
|
255
|
+
console.log(" \u26A0\uFE0F Agent may not be fully ready (health check timed out)");
|
|
256
|
+
} else {
|
|
257
|
+
console.log(" \u2713 Agent running");
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
async status(instanceId) {
|
|
261
|
+
try {
|
|
262
|
+
const output = (0, import_node_child_process.execFileSync)("sprite", ["list"], {
|
|
263
|
+
encoding: "utf-8"
|
|
264
|
+
});
|
|
265
|
+
if (!output.includes(instanceId)) {
|
|
266
|
+
return "stopped";
|
|
267
|
+
}
|
|
268
|
+
const lines = output.split("\n");
|
|
269
|
+
for (const line of lines) {
|
|
270
|
+
if (line.includes(instanceId)) {
|
|
271
|
+
if (line.includes("sleeping") || line.includes("paused")) {
|
|
272
|
+
return "sleeping";
|
|
273
|
+
}
|
|
274
|
+
if (line.includes("running")) {
|
|
275
|
+
return "running";
|
|
276
|
+
}
|
|
277
|
+
if (line.includes("stopped") || line.includes("terminated")) {
|
|
278
|
+
return "stopped";
|
|
279
|
+
}
|
|
280
|
+
return "running";
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return "unknown";
|
|
284
|
+
} catch {
|
|
285
|
+
return "unknown";
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
async sleep(instanceId) {
|
|
289
|
+
console.log(`
|
|
290
|
+
\u{1F4A4} Sleeping sprite: ${instanceId}`);
|
|
291
|
+
try {
|
|
292
|
+
(0, import_node_child_process.execFileSync)("sprite", ["sleep", instanceId], { stdio: "inherit" });
|
|
293
|
+
console.log(" \u2713 Sprite sleeping");
|
|
294
|
+
} catch (err) {
|
|
295
|
+
throw new Error(`Failed to sleep sprite: ${err.stderr || err.message}`);
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
async wake(instanceId) {
|
|
299
|
+
console.log(`
|
|
300
|
+
\u2600\uFE0F Waking sprite: ${instanceId}`);
|
|
301
|
+
try {
|
|
302
|
+
(0, import_node_child_process.execFileSync)("sprite", ["exec", "-s", instanceId, "--", "echo", "wake"], {
|
|
303
|
+
stdio: "pipe"
|
|
304
|
+
});
|
|
305
|
+
console.log(" \u2713 Sprite awake");
|
|
306
|
+
} catch (err) {
|
|
307
|
+
throw new Error(`Failed to wake sprite: ${err.stderr || err.message}`);
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
async logs(instanceId, follow = true) {
|
|
311
|
+
const args = follow ? ["logs", "-s", instanceId, "-f"] : ["logs", "-s", instanceId];
|
|
312
|
+
const child = (0, import_node_child_process.spawn)("sprite", args, { stdio: "inherit" });
|
|
313
|
+
return new Promise((resolve3, reject) => {
|
|
314
|
+
child.on("exit", (code) => {
|
|
315
|
+
if (code === 0) resolve3();
|
|
316
|
+
else reject(new Error(`sprite logs exited with code ${code}`));
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
},
|
|
320
|
+
async endpoint(instanceId) {
|
|
321
|
+
return `https://${instanceId}.sprites.dev`;
|
|
322
|
+
},
|
|
323
|
+
async teardown(instanceId) {
|
|
324
|
+
console.log(`
|
|
325
|
+
\u{1F5D1}\uFE0F Destroying sprite: ${instanceId}`);
|
|
326
|
+
try {
|
|
327
|
+
(0, import_node_child_process.execFileSync)("sprite", ["delete", instanceId], { stdio: "inherit" });
|
|
328
|
+
console.log(" \u2713 Sprite destroyed");
|
|
329
|
+
} catch (err) {
|
|
330
|
+
throw new Error(`Failed to destroy sprite: ${err.stderr || err.message}`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// src/deploy/providers/e2b.provider.ts
|
|
338
|
+
var e2b_provider_exports = {};
|
|
339
|
+
__export(e2b_provider_exports, {
|
|
340
|
+
e2bProvider: () => e2bProvider
|
|
341
|
+
});
|
|
342
|
+
async function findSandbox(name) {
|
|
343
|
+
const Sandbox = await getSDK();
|
|
344
|
+
const paginator = Sandbox.list();
|
|
345
|
+
while (paginator.hasNext) {
|
|
346
|
+
const items = await paginator.nextItems();
|
|
347
|
+
for (const s of items) {
|
|
348
|
+
const info = s;
|
|
349
|
+
const meta = info.metadata;
|
|
350
|
+
if (meta?.["hybrid-name"] === name || info.sandboxId === name || info.sandboxId?.startsWith(name)) {
|
|
351
|
+
return s;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
async function getSDK() {
|
|
358
|
+
try {
|
|
359
|
+
const { Sandbox } = await import("e2b");
|
|
360
|
+
return Sandbox;
|
|
361
|
+
} catch {
|
|
362
|
+
throw new Error(
|
|
363
|
+
"e2b not installed.\nInstall with: npm install e2b"
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
function getAPIKey() {
|
|
368
|
+
const key = process.env.E2B_API_KEY;
|
|
369
|
+
if (!key) {
|
|
370
|
+
throw new Error(
|
|
371
|
+
"E2B_API_KEY not set.\nGet your key: https://e2b.dev/dashboard?tab=keys\nSet with: export E2B_API_KEY=e2b_xxx"
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
return key;
|
|
375
|
+
}
|
|
376
|
+
async function getSandbox(name) {
|
|
377
|
+
const cached = sandboxes.get(name);
|
|
378
|
+
if (cached) return cached;
|
|
379
|
+
const found = await findSandbox(name);
|
|
380
|
+
if (!found) return null;
|
|
381
|
+
const Sandbox = await getSDK();
|
|
382
|
+
const sandbox = await Sandbox.connect(found.sandboxId);
|
|
383
|
+
sandboxes.set(name, sandbox);
|
|
384
|
+
return sandbox;
|
|
385
|
+
}
|
|
386
|
+
var import_node_child_process2, import_node_fs2, import_node_os2, import_node_path2, DEFAULT_TEMPLATE, sandboxes, e2bProvider;
|
|
387
|
+
var init_e2b_provider = __esm({
|
|
388
|
+
"src/deploy/providers/e2b.provider.ts"() {
|
|
389
|
+
"use strict";
|
|
390
|
+
init_cjs_shims();
|
|
391
|
+
import_node_child_process2 = require("child_process");
|
|
392
|
+
import_node_fs2 = require("fs");
|
|
393
|
+
import_node_os2 = require("os");
|
|
394
|
+
import_node_path2 = require("path");
|
|
395
|
+
DEFAULT_TEMPLATE = "base";
|
|
396
|
+
sandboxes = /* @__PURE__ */ new Map();
|
|
397
|
+
e2bProvider = {
|
|
398
|
+
name: "e2b",
|
|
399
|
+
label: "E2B.dev (Firecracker)",
|
|
400
|
+
defaultName(projectDir) {
|
|
401
|
+
return (0, import_node_path2.basename)(projectDir).toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
402
|
+
},
|
|
403
|
+
async authCheck() {
|
|
404
|
+
getAPIKey();
|
|
405
|
+
const Sandbox = await getSDK();
|
|
406
|
+
try {
|
|
407
|
+
const paginator = Sandbox.list();
|
|
408
|
+
await paginator.nextItems();
|
|
409
|
+
} catch (err) {
|
|
410
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
411
|
+
if (msg.includes("401") || msg.includes("unauthorized")) {
|
|
412
|
+
throw new Error(
|
|
413
|
+
"E2B API key is invalid.\nGet your key: https://e2b.dev/dashboard?tab=keys"
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
throw new Error(`E2B connection failed: ${msg}`);
|
|
417
|
+
}
|
|
418
|
+
},
|
|
419
|
+
async provision(name, _opts) {
|
|
420
|
+
const Sandbox = await getSDK();
|
|
421
|
+
console.log(`
|
|
422
|
+
\u{1F4E6} Creating E2B sandbox: ${name}`);
|
|
423
|
+
const existing = await findSandbox(name);
|
|
424
|
+
if (existing) {
|
|
425
|
+
const sandbox2 = await Sandbox.connect(
|
|
426
|
+
existing.sandboxId
|
|
427
|
+
);
|
|
428
|
+
sandboxes.set(name, sandbox2);
|
|
429
|
+
console.log(
|
|
430
|
+
` \u2713 Resumed existing sandbox: ${existing.sandboxId}`
|
|
431
|
+
);
|
|
432
|
+
return existing.sandboxId;
|
|
433
|
+
}
|
|
434
|
+
const sandbox = await Sandbox.create(DEFAULT_TEMPLATE, {
|
|
435
|
+
metadata: { "hybrid-name": name }
|
|
436
|
+
});
|
|
437
|
+
sandboxes.set(name, sandbox);
|
|
438
|
+
console.log(` \u2713 Sandbox created: ${sandbox.sandboxId}`);
|
|
439
|
+
return sandbox.sandboxId;
|
|
440
|
+
},
|
|
441
|
+
async deploy(instanceId, distDir) {
|
|
442
|
+
const sandbox = await getSandbox(instanceId);
|
|
443
|
+
if (!sandbox) {
|
|
444
|
+
throw new Error(
|
|
445
|
+
`Sandbox ${instanceId} not found.
|
|
446
|
+
Run 'hybrid deploy' again to reconnect.`
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
console.log("\n\u{1F4E4} Uploading build artifacts...");
|
|
450
|
+
const tarPath = (0, import_node_path2.join)(
|
|
451
|
+
(0, import_node_os2.tmpdir)(),
|
|
452
|
+
`hybrid-e2b-deploy-${Date.now()}.tar.gz`
|
|
453
|
+
);
|
|
454
|
+
(0, import_node_child_process2.execFileSync)("tar", ["-czf", tarPath, "-C", distDir, "."], {
|
|
455
|
+
stdio: "pipe"
|
|
456
|
+
});
|
|
457
|
+
try {
|
|
458
|
+
const fs = await import("fs/promises");
|
|
459
|
+
const tarBuffer = await fs.readFile(tarPath);
|
|
460
|
+
await sandbox.files.write("/tmp/hybrid-deploy.tar.gz", tarBuffer, {
|
|
461
|
+
onProgress: () => {
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
console.log(" \u2713 Upload complete");
|
|
465
|
+
} catch (err) {
|
|
466
|
+
throw new Error(
|
|
467
|
+
`Upload failed: ${err instanceof Error ? err.message : String(err)}`
|
|
468
|
+
);
|
|
469
|
+
} finally {
|
|
470
|
+
if ((0, import_node_fs2.existsSync)(tarPath)) {
|
|
471
|
+
try {
|
|
472
|
+
(0, import_node_fs2.unlinkSync)(tarPath);
|
|
473
|
+
} catch {
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
console.log("\n\u{1F4E6} Extracting and installing dependencies...");
|
|
478
|
+
const result = await sandbox.commands.run(
|
|
479
|
+
"cd /home/user && mkdir -p app && tar -xzf /tmp/hybrid-deploy.tar.gz -C app && cd app && npm install --production 2>&1",
|
|
480
|
+
{ timeout: 12e4 }
|
|
481
|
+
);
|
|
482
|
+
if (result.stderr) {
|
|
483
|
+
console.log(` \u26A0\uFE0F ${result.stderr.slice(0, 200)}`);
|
|
484
|
+
}
|
|
485
|
+
if (result.exitCode !== 0) {
|
|
486
|
+
throw new Error(
|
|
487
|
+
`Dependency installation failed (exit ${result.exitCode})`
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
console.log("\n\u{1F527} Starting agent...");
|
|
491
|
+
await sandbox.commands.run(
|
|
492
|
+
"cd /home/user/app && export NODE_ENV=production AGENT_PORT=8454 && nohup node server/index.cjs > /tmp/agent.log 2>&1 & echo $! > /tmp/agent.pid",
|
|
493
|
+
{ timeout: 1e4 }
|
|
494
|
+
);
|
|
495
|
+
console.log("\n\u23F3 Waiting for agent to start...");
|
|
496
|
+
let ready = false;
|
|
497
|
+
for (let i = 0; i < 15; i++) {
|
|
498
|
+
try {
|
|
499
|
+
const health = await sandbox.commands.run(
|
|
500
|
+
"curl -sf http://localhost:8454/health || echo 'not ready'"
|
|
501
|
+
);
|
|
502
|
+
if (health.stdout && !health.stdout.includes("not ready")) {
|
|
503
|
+
ready = true;
|
|
504
|
+
break;
|
|
505
|
+
}
|
|
506
|
+
} catch {
|
|
507
|
+
}
|
|
508
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
509
|
+
}
|
|
510
|
+
if (!ready) {
|
|
511
|
+
console.log(
|
|
512
|
+
" \u26A0\uFE0F Agent health check timed out (may still be starting)"
|
|
513
|
+
);
|
|
514
|
+
} else {
|
|
515
|
+
console.log(" \u2713 Agent running");
|
|
516
|
+
}
|
|
517
|
+
},
|
|
518
|
+
async status(instanceId) {
|
|
519
|
+
const info = await findSandbox(instanceId);
|
|
520
|
+
if (!info) return "stopped";
|
|
521
|
+
const sbInfo = info;
|
|
522
|
+
if (sbInfo.status === "paused") return "sleeping";
|
|
523
|
+
if (sbInfo.status === "running") return "running";
|
|
524
|
+
return "unknown";
|
|
525
|
+
},
|
|
526
|
+
async sleep(instanceId) {
|
|
527
|
+
const sandbox = await getSandbox(instanceId);
|
|
528
|
+
if (!sandbox) {
|
|
529
|
+
throw new Error(
|
|
530
|
+
`Sandbox ${instanceId} not found.
|
|
531
|
+
Run 'hybrid deploy' again to reconnect.`
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
console.log(`
|
|
535
|
+
\u{1F4A4} Pausing E2B sandbox: ${instanceId}`);
|
|
536
|
+
await sandbox.pause();
|
|
537
|
+
sandboxes.delete(instanceId);
|
|
538
|
+
console.log(" \u2713 Sandbox paused (state preserved on disk)");
|
|
539
|
+
},
|
|
540
|
+
async wake(instanceId) {
|
|
541
|
+
const Sandbox = await getSDK();
|
|
542
|
+
console.log(`
|
|
543
|
+
\u2600\uFE0F Resuming E2B sandbox: ${instanceId}`);
|
|
544
|
+
try {
|
|
545
|
+
const sandbox = await Sandbox.connect(instanceId);
|
|
546
|
+
sandboxes.set(instanceId, sandbox);
|
|
547
|
+
console.log(" \u2713 Sandbox resumed");
|
|
548
|
+
} catch (err) {
|
|
549
|
+
throw new Error(
|
|
550
|
+
`Failed to resume sandbox: ${err instanceof Error ? err.message : String(err)}
|
|
551
|
+
The sandbox may have expired (E2B sandboxes have a maximum lifetime).`
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
},
|
|
555
|
+
async logs(instanceId, follow = true) {
|
|
556
|
+
const sandbox = await getSandbox(instanceId);
|
|
557
|
+
if (!sandbox) {
|
|
558
|
+
throw new Error(
|
|
559
|
+
`Sandbox ${instanceId} not found.
|
|
560
|
+
Run 'hybrid deploy' again to reconnect.`
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
if (follow) {
|
|
564
|
+
let offset = 0;
|
|
565
|
+
while (true) {
|
|
566
|
+
try {
|
|
567
|
+
const result = await sandbox.commands.run(
|
|
568
|
+
"cat /tmp/agent.log 2>/dev/null || echo 'No logs yet'"
|
|
569
|
+
);
|
|
570
|
+
const output = result.stdout || "";
|
|
571
|
+
if (output.length > offset) {
|
|
572
|
+
process.stdout.write(output.slice(offset));
|
|
573
|
+
offset = output.length;
|
|
574
|
+
}
|
|
575
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
576
|
+
} catch {
|
|
577
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
} else {
|
|
581
|
+
const result = await sandbox.commands.run(
|
|
582
|
+
"cat /tmp/agent.log 2>/dev/null || echo 'No logs yet'"
|
|
583
|
+
);
|
|
584
|
+
console.log(result.stdout || "No logs yet");
|
|
585
|
+
}
|
|
586
|
+
},
|
|
587
|
+
async endpoint(instanceId) {
|
|
588
|
+
const sandbox = sandboxes.get(instanceId);
|
|
589
|
+
if (!sandbox) return `https://${instanceId}-8454.e2b.dev`;
|
|
590
|
+
try {
|
|
591
|
+
const host = await sandbox.getHost(8454);
|
|
592
|
+
return `https://${host}`;
|
|
593
|
+
} catch {
|
|
594
|
+
return `https://${instanceId}-8454.e2b.dev`;
|
|
595
|
+
}
|
|
596
|
+
},
|
|
597
|
+
async teardown(instanceId) {
|
|
598
|
+
const Sandbox = await getSDK();
|
|
599
|
+
console.log(`
|
|
600
|
+
\u{1F5D1}\uFE0F Destroying E2B sandbox: ${instanceId}`);
|
|
601
|
+
try {
|
|
602
|
+
const cached = sandboxes.get(instanceId);
|
|
603
|
+
if (cached) {
|
|
604
|
+
await cached.kill();
|
|
605
|
+
} else {
|
|
606
|
+
const sandbox = await Sandbox.connect(instanceId);
|
|
607
|
+
await sandbox.kill();
|
|
608
|
+
}
|
|
609
|
+
sandboxes.delete(instanceId);
|
|
610
|
+
console.log(" \u2713 Sandbox destroyed");
|
|
611
|
+
} catch (err) {
|
|
612
|
+
throw new Error(
|
|
613
|
+
`Failed to destroy sandbox: ${err instanceof Error ? err.message : String(err)}`
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
// src/deploy/providers/northflank.provider.ts
|
|
622
|
+
var northflank_provider_exports = {};
|
|
623
|
+
__export(northflank_provider_exports, {
|
|
624
|
+
northflankProvider: () => northflankProvider
|
|
625
|
+
});
|
|
626
|
+
function runNf(args) {
|
|
627
|
+
return (0, import_node_child_process3.execFileSync)(CLI, args, {
|
|
628
|
+
encoding: "utf-8",
|
|
629
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
function runNfInherit(args) {
|
|
633
|
+
(0, import_node_child_process3.execFileSync)(CLI, args, { stdio: "inherit" });
|
|
634
|
+
}
|
|
635
|
+
var import_node_child_process3, import_node_fs3, import_node_os3, import_node_path3, CLI, northflankProvider;
|
|
636
|
+
var init_northflank_provider = __esm({
|
|
637
|
+
"src/deploy/providers/northflank.provider.ts"() {
|
|
638
|
+
"use strict";
|
|
639
|
+
init_cjs_shims();
|
|
640
|
+
import_node_child_process3 = require("child_process");
|
|
641
|
+
import_node_fs3 = require("fs");
|
|
642
|
+
import_node_os3 = require("os");
|
|
643
|
+
import_node_path3 = require("path");
|
|
644
|
+
CLI = "nf";
|
|
645
|
+
northflankProvider = {
|
|
646
|
+
name: "northflank",
|
|
647
|
+
label: "Northflank (Firecracker / Auto-scale)",
|
|
648
|
+
defaultName(projectDir) {
|
|
649
|
+
return (0, import_node_path3.basename)(projectDir).toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
650
|
+
},
|
|
651
|
+
async authCheck() {
|
|
652
|
+
try {
|
|
653
|
+
const result = runNf(["auth", "whoami"]);
|
|
654
|
+
if (!result || result.includes("not logged in")) {
|
|
655
|
+
throw new Error(
|
|
656
|
+
"Not authenticated with Northflank.\n Run: nf auth login"
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
} catch (err) {
|
|
660
|
+
if (err.code === "ENOENT") {
|
|
661
|
+
throw new Error(
|
|
662
|
+
"`nf` CLI not found.\n Install: https://docs.northflank.com/docs/cli\n Or: npm install -g @northflank/cli"
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
throw new Error(
|
|
666
|
+
`Northflank CLI error: ${err.stderr || err.message}
|
|
667
|
+
Run: nf auth login`
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
},
|
|
671
|
+
async provision(name, opts) {
|
|
672
|
+
const projectId = process.env.NF_PROJECT_ID;
|
|
673
|
+
if (!projectId) {
|
|
674
|
+
throw new Error(
|
|
675
|
+
"NF_PROJECT_ID not set.\n Create a project on Northflank and set: export NF_PROJECT_ID=your-project-id"
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
try {
|
|
679
|
+
const existing = runNf(["services", "list", "--projectId", projectId]);
|
|
680
|
+
if (existing.toLowerCase().includes(name)) {
|
|
681
|
+
console.log(` \u2713 Service already exists: ${name}`);
|
|
682
|
+
return name;
|
|
683
|
+
}
|
|
684
|
+
} catch {
|
|
685
|
+
}
|
|
686
|
+
const apiUrl = process.env.NF_API_URL || "https://api.northflank.com";
|
|
687
|
+
const memory = opts?.memory || 512;
|
|
688
|
+
const cpus = opts?.cpus || 1;
|
|
689
|
+
console.log(`
|
|
690
|
+
\u{1F4E6} Creating Northflank service: ${name}`);
|
|
691
|
+
console.log(` Project: ${projectId}`);
|
|
692
|
+
console.log(` Memory: ${memory}MB, CPUs: ${cpus}`);
|
|
693
|
+
const spec = {
|
|
694
|
+
name,
|
|
695
|
+
projectId,
|
|
696
|
+
type: "service",
|
|
697
|
+
image: {
|
|
698
|
+
image: `registry.northflank.com/${projectId}/${name}:latest`
|
|
699
|
+
},
|
|
700
|
+
resources: {
|
|
701
|
+
instances: 1,
|
|
702
|
+
containerResources: {
|
|
703
|
+
cpu: {
|
|
704
|
+
req: cpus,
|
|
705
|
+
limit: cpus
|
|
706
|
+
},
|
|
707
|
+
memory: {
|
|
708
|
+
req: memory,
|
|
709
|
+
limit: memory
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
},
|
|
713
|
+
ports: [
|
|
714
|
+
{
|
|
715
|
+
name: "default",
|
|
716
|
+
protocol: "HTTP",
|
|
717
|
+
containerPort: 8454,
|
|
718
|
+
ingress: {
|
|
719
|
+
autoSubdomain: true
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
],
|
|
723
|
+
autoscaling: {
|
|
724
|
+
enabled: true,
|
|
725
|
+
minReplicas: 0,
|
|
726
|
+
maxReplicas: 1
|
|
727
|
+
}
|
|
728
|
+
};
|
|
729
|
+
const specPath = (0, import_node_path3.join)((0, import_node_os3.tmpdir)(), `nf-spec-${Date.now()}.json`);
|
|
730
|
+
const { writeFileSync: writeFileSync2 } = await import("fs");
|
|
731
|
+
writeFileSync2(specPath, JSON.stringify(spec, null, 2));
|
|
732
|
+
try {
|
|
733
|
+
runNfInherit(["deploy", "service", specPath]);
|
|
734
|
+
} finally {
|
|
735
|
+
if ((0, import_node_fs3.existsSync)(specPath)) {
|
|
736
|
+
try {
|
|
737
|
+
(0, import_node_fs3.unlinkSync)(specPath);
|
|
738
|
+
} catch {
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
console.log(" \u2713 Service created with auto-scale-to-zero enabled");
|
|
743
|
+
return name;
|
|
744
|
+
},
|
|
745
|
+
async deploy(instanceId, distDir) {
|
|
746
|
+
const projectId = process.env.NF_PROJECT_ID;
|
|
747
|
+
if (!projectId) {
|
|
748
|
+
throw new Error("NF_PROJECT_ID not set.");
|
|
749
|
+
}
|
|
750
|
+
console.log("\n\u{1F4E6} Building and pushing Docker image...");
|
|
751
|
+
const imageTag = `registry.northflank.com/${projectId}/${instanceId}:latest`;
|
|
752
|
+
runNf(["info"]);
|
|
753
|
+
console.log(" Building Docker image...");
|
|
754
|
+
(0, import_node_child_process3.execFileSync)("docker", ["build", "-t", imageTag, "."], {
|
|
755
|
+
cwd: distDir,
|
|
756
|
+
stdio: "inherit"
|
|
757
|
+
});
|
|
758
|
+
console.log(" Pushing to Northflank registry...");
|
|
759
|
+
(0, import_node_child_process3.execFileSync)("docker", ["push", imageTag], {
|
|
760
|
+
stdio: "inherit"
|
|
761
|
+
});
|
|
762
|
+
console.log("\n\u{1F527} Deploying service...");
|
|
763
|
+
try {
|
|
764
|
+
runNfInherit([
|
|
765
|
+
"services",
|
|
766
|
+
"set-image",
|
|
767
|
+
"--projectId",
|
|
768
|
+
projectId,
|
|
769
|
+
"--serviceId",
|
|
770
|
+
instanceId,
|
|
771
|
+
"--image",
|
|
772
|
+
imageTag
|
|
773
|
+
]);
|
|
774
|
+
} catch (err) {
|
|
775
|
+
console.log(" \u26A0\uFE0F Service update initiated");
|
|
776
|
+
}
|
|
777
|
+
console.log("\n\u23F3 Waiting for deployment to be ready...");
|
|
778
|
+
for (let i = 0; i < 60; i++) {
|
|
779
|
+
try {
|
|
780
|
+
const status = runNf([
|
|
781
|
+
"services",
|
|
782
|
+
"status",
|
|
783
|
+
"--projectId",
|
|
784
|
+
projectId,
|
|
785
|
+
"--serviceId",
|
|
786
|
+
instanceId
|
|
787
|
+
]);
|
|
788
|
+
if (status.toLowerCase().includes("deployed") || status.toLowerCase().includes("running")) {
|
|
789
|
+
console.log(" \u2713 Service deployed");
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
} catch {
|
|
793
|
+
}
|
|
794
|
+
if (i % 10 === 9) {
|
|
795
|
+
console.log(` Still deploying... (${i + 1}/60)`);
|
|
796
|
+
}
|
|
797
|
+
await new Promise((r) => setTimeout(r, 5e3));
|
|
798
|
+
}
|
|
799
|
+
console.log(" \u26A0\uFE0F Deployment may still be in progress");
|
|
800
|
+
},
|
|
801
|
+
async status(instanceId) {
|
|
802
|
+
const projectId = process.env.NF_PROJECT_ID;
|
|
803
|
+
if (!projectId) return "unknown";
|
|
804
|
+
try {
|
|
805
|
+
const status = runNf([
|
|
806
|
+
"services",
|
|
807
|
+
"status",
|
|
808
|
+
"--projectId",
|
|
809
|
+
projectId,
|
|
810
|
+
"--serviceId",
|
|
811
|
+
instanceId
|
|
812
|
+
]);
|
|
813
|
+
const lower = status.toLowerCase();
|
|
814
|
+
if (lower.includes("deployed") || lower.includes("running")) {
|
|
815
|
+
return "running";
|
|
816
|
+
}
|
|
817
|
+
if (lower.includes("stopped") || lower.includes("paused")) {
|
|
818
|
+
return "sleeping";
|
|
819
|
+
}
|
|
820
|
+
if (lower.includes("deploying")) {
|
|
821
|
+
return "provisioning";
|
|
822
|
+
}
|
|
823
|
+
return "unknown";
|
|
824
|
+
} catch {
|
|
825
|
+
return "stopped";
|
|
826
|
+
}
|
|
827
|
+
},
|
|
828
|
+
async sleep(instanceId) {
|
|
829
|
+
const projectId = process.env.NF_PROJECT_ID;
|
|
830
|
+
if (!projectId) {
|
|
831
|
+
throw new Error("NF_PROJECT_ID not set.");
|
|
832
|
+
}
|
|
833
|
+
console.log(`
|
|
834
|
+
\u{1F4A4} Scaling Northflank service to 0: ${instanceId}`);
|
|
835
|
+
try {
|
|
836
|
+
runNfInherit([
|
|
837
|
+
"services",
|
|
838
|
+
"scale",
|
|
839
|
+
"--projectId",
|
|
840
|
+
projectId,
|
|
841
|
+
"--serviceId",
|
|
842
|
+
instanceId,
|
|
843
|
+
"--replicas",
|
|
844
|
+
"0"
|
|
845
|
+
]);
|
|
846
|
+
console.log(" \u2713 Service scaled to 0 (sleeping)");
|
|
847
|
+
} catch (err) {
|
|
848
|
+
throw new Error(`Failed to scale down: ${err.stderr || err.message}`);
|
|
849
|
+
}
|
|
850
|
+
},
|
|
851
|
+
async wake(instanceId) {
|
|
852
|
+
const projectId = process.env.NF_PROJECT_ID;
|
|
853
|
+
if (!projectId) {
|
|
854
|
+
throw new Error("NF_PROJECT_ID not set.");
|
|
855
|
+
}
|
|
856
|
+
console.log(`
|
|
857
|
+
\u2600\uFE0F Scaling Northflank service to 1: ${instanceId}`);
|
|
858
|
+
try {
|
|
859
|
+
runNfInherit([
|
|
860
|
+
"services",
|
|
861
|
+
"scale",
|
|
862
|
+
"--projectId",
|
|
863
|
+
projectId,
|
|
864
|
+
"--serviceId",
|
|
865
|
+
instanceId,
|
|
866
|
+
"--replicas",
|
|
867
|
+
"1"
|
|
868
|
+
]);
|
|
869
|
+
console.log(" \u2713 Service scaling up (may take 30-60s)");
|
|
870
|
+
} catch (err) {
|
|
871
|
+
throw new Error(`Failed to scale up: ${err.stderr || err.message}`);
|
|
872
|
+
}
|
|
873
|
+
},
|
|
874
|
+
async logs(instanceId, follow = true) {
|
|
875
|
+
const projectId = process.env.NF_PROJECT_ID;
|
|
876
|
+
if (!projectId) {
|
|
877
|
+
throw new Error("NF_PROJECT_ID not set.");
|
|
878
|
+
}
|
|
879
|
+
const args = [
|
|
880
|
+
"services",
|
|
881
|
+
"logs",
|
|
882
|
+
"--projectId",
|
|
883
|
+
projectId,
|
|
884
|
+
"--serviceId",
|
|
885
|
+
instanceId
|
|
886
|
+
];
|
|
887
|
+
if (follow) args.push("--follow");
|
|
888
|
+
const child = (0, import_node_child_process3.spawn)(CLI, args, { stdio: "inherit" });
|
|
889
|
+
return new Promise((resolve3, reject) => {
|
|
890
|
+
child.on("exit", (code) => {
|
|
891
|
+
if (code === 0) resolve3();
|
|
892
|
+
else reject(new Error(`nf logs exited with code ${code}`));
|
|
893
|
+
});
|
|
894
|
+
});
|
|
895
|
+
},
|
|
896
|
+
async endpoint(instanceId) {
|
|
897
|
+
const projectId = process.env.NF_PROJECT_ID;
|
|
898
|
+
if (!projectId) return `https://${instanceId}.northflank.app`;
|
|
899
|
+
try {
|
|
900
|
+
const info = runNf([
|
|
901
|
+
"services",
|
|
902
|
+
"info",
|
|
903
|
+
"--projectId",
|
|
904
|
+
projectId,
|
|
905
|
+
"--serviceId",
|
|
906
|
+
instanceId
|
|
907
|
+
]);
|
|
908
|
+
const match = info.match(/https?:\/\/[^\s]+/i);
|
|
909
|
+
if (match) return match[0];
|
|
910
|
+
} catch {
|
|
911
|
+
}
|
|
912
|
+
return `https://${instanceId}.${projectId}.northflank.app`;
|
|
913
|
+
},
|
|
914
|
+
async teardown(instanceId) {
|
|
915
|
+
const projectId = process.env.NF_PROJECT_ID;
|
|
916
|
+
if (!projectId) {
|
|
917
|
+
throw new Error("NF_PROJECT_ID not set.");
|
|
918
|
+
}
|
|
919
|
+
console.log(`
|
|
920
|
+
\u{1F5D1}\uFE0F Destroying Northflank service: ${instanceId}`);
|
|
921
|
+
try {
|
|
922
|
+
runNfInherit([
|
|
923
|
+
"services",
|
|
924
|
+
"delete",
|
|
925
|
+
"--projectId",
|
|
926
|
+
projectId,
|
|
927
|
+
"--serviceId",
|
|
928
|
+
instanceId,
|
|
929
|
+
"--permanent"
|
|
930
|
+
]);
|
|
931
|
+
console.log(" \u2713 Service destroyed");
|
|
932
|
+
} catch (err) {
|
|
933
|
+
throw new Error(`Failed to destroy service: ${err.stderr || err.message}`);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
// src/deploy/providers/daytona.provider.ts
|
|
941
|
+
var daytona_provider_exports = {};
|
|
942
|
+
__export(daytona_provider_exports, {
|
|
943
|
+
daytonaProvider: () => daytonaProvider
|
|
944
|
+
});
|
|
945
|
+
function runDaytona(args) {
|
|
946
|
+
return (0, import_node_child_process4.execFileSync)(CLI2, args, {
|
|
947
|
+
encoding: "utf-8",
|
|
948
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
function runDaytonaInherit(args) {
|
|
952
|
+
(0, import_node_child_process4.execFileSync)(CLI2, args, { stdio: "inherit" });
|
|
953
|
+
}
|
|
954
|
+
var import_node_child_process4, import_node_fs4, import_node_os4, import_node_path4, CLI2, daytonaProvider;
|
|
955
|
+
var init_daytona_provider = __esm({
|
|
956
|
+
"src/deploy/providers/daytona.provider.ts"() {
|
|
957
|
+
"use strict";
|
|
958
|
+
init_cjs_shims();
|
|
959
|
+
import_node_child_process4 = require("child_process");
|
|
960
|
+
import_node_fs4 = require("fs");
|
|
961
|
+
import_node_os4 = require("os");
|
|
962
|
+
import_node_path4 = require("path");
|
|
963
|
+
CLI2 = "daytona";
|
|
964
|
+
daytonaProvider = {
|
|
965
|
+
name: "daytona",
|
|
966
|
+
label: "Daytona.io (Firecracker / Dev Workspace)",
|
|
967
|
+
defaultName(projectDir) {
|
|
968
|
+
return (0, import_node_path4.basename)(projectDir).toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
969
|
+
},
|
|
970
|
+
async authCheck() {
|
|
971
|
+
try {
|
|
972
|
+
runDaytona(["info"]);
|
|
973
|
+
} catch (err) {
|
|
974
|
+
if (err.code === "ENOENT") {
|
|
975
|
+
throw new Error(
|
|
976
|
+
"`daytona` CLI not found.\n Install: https://www.daytona.io/docs/getting-started/installation/\n Or: curl -NF https://download.daytona.io/daytona/install.sh | sh"
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
throw new Error(`Daytona CLI error: ${err.stderr || err.message}`);
|
|
980
|
+
}
|
|
981
|
+
},
|
|
982
|
+
async provision(name, opts) {
|
|
983
|
+
try {
|
|
984
|
+
const existing = runDaytona(["list"]);
|
|
985
|
+
const lines = existing.split("\n");
|
|
986
|
+
const found = lines.find((l) => l.includes(name));
|
|
987
|
+
if (found) {
|
|
988
|
+
console.log(` \u26A0\uFE0F Workspace already exists: ${name}`);
|
|
989
|
+
return name;
|
|
990
|
+
}
|
|
991
|
+
} catch {
|
|
992
|
+
}
|
|
993
|
+
console.log(`
|
|
994
|
+
\u{1F4E6} Creating Daytona workspace: ${name}`);
|
|
995
|
+
const image = opts?.memory ? "node:20" : "node:20";
|
|
996
|
+
const args = ["create", "--name", name, "--image", image];
|
|
997
|
+
const provider = process.env.DAYTONA_PROVIDER;
|
|
998
|
+
if (provider) {
|
|
999
|
+
args.push("--provider", provider);
|
|
1000
|
+
}
|
|
1001
|
+
runDaytonaInherit(args);
|
|
1002
|
+
console.log(" \u2713 Workspace created");
|
|
1003
|
+
return name;
|
|
1004
|
+
},
|
|
1005
|
+
async deploy(instanceId, distDir) {
|
|
1006
|
+
console.log("\n\u{1F4E4} Uploading build artifacts...");
|
|
1007
|
+
const tarPath = (0, import_node_path4.join)((0, import_node_os4.tmpdir)(), `hybrid-daytona-${Date.now()}.tar.gz`);
|
|
1008
|
+
(0, import_node_child_process4.execFileSync)("tar", ["-czf", tarPath, "-C", distDir, "."], {
|
|
1009
|
+
stdio: "pipe"
|
|
1010
|
+
});
|
|
1011
|
+
runDaytonaInherit([
|
|
1012
|
+
"code",
|
|
1013
|
+
instanceId,
|
|
1014
|
+
"--command",
|
|
1015
|
+
"mkdir -p /workspace/app"
|
|
1016
|
+
]);
|
|
1017
|
+
try {
|
|
1018
|
+
runDaytonaInherit([
|
|
1019
|
+
"cp",
|
|
1020
|
+
tarPath,
|
|
1021
|
+
`${instanceId}:/workspace/app/hybrid-deploy.tar.gz`
|
|
1022
|
+
]);
|
|
1023
|
+
} catch {
|
|
1024
|
+
throw new Error(
|
|
1025
|
+
`daytona cp failed for instance ${instanceId}.
|
|
1026
|
+
Ensure the Daytona CLI supports file copy: daytona cp ${tarPath} ${instanceId}:/workspace/app/hybrid-deploy.tar.gz`
|
|
1027
|
+
);
|
|
1028
|
+
}
|
|
1029
|
+
runDaytonaInherit([
|
|
1030
|
+
"code",
|
|
1031
|
+
instanceId,
|
|
1032
|
+
"--command",
|
|
1033
|
+
"cd /workspace/app && tar -xzf hybrid-deploy.tar.gz && npm install --production"
|
|
1034
|
+
]);
|
|
1035
|
+
if ((0, import_node_fs4.existsSync)(tarPath)) {
|
|
1036
|
+
try {
|
|
1037
|
+
(0, import_node_fs4.unlinkSync)(tarPath);
|
|
1038
|
+
} catch {
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
console.log("\n\u{1F527} Starting agent...");
|
|
1042
|
+
runDaytonaInherit([
|
|
1043
|
+
"code",
|
|
1044
|
+
instanceId,
|
|
1045
|
+
"--command",
|
|
1046
|
+
"cd /workspace/app && export NODE_ENV=production AGENT_PORT=8454 && nohup node server/index.cjs > /workspace/app/agent.log 2>&1 &"
|
|
1047
|
+
]);
|
|
1048
|
+
console.log("\n\u23F3 Waiting for agent to start...");
|
|
1049
|
+
let ready = false;
|
|
1050
|
+
for (let i = 0; i < 15; i++) {
|
|
1051
|
+
try {
|
|
1052
|
+
const result = runDaytona([
|
|
1053
|
+
"code",
|
|
1054
|
+
instanceId,
|
|
1055
|
+
"--command",
|
|
1056
|
+
"curl -sf http://localhost:8454/health || echo not-ready"
|
|
1057
|
+
]);
|
|
1058
|
+
if (result && !result.includes("not-ready")) {
|
|
1059
|
+
ready = true;
|
|
1060
|
+
break;
|
|
1061
|
+
}
|
|
1062
|
+
} catch {
|
|
1063
|
+
}
|
|
1064
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
1065
|
+
}
|
|
1066
|
+
if (!ready) {
|
|
1067
|
+
console.log(" \u26A0\uFE0F Agent health check timed out");
|
|
1068
|
+
} else {
|
|
1069
|
+
console.log(" \u2713 Agent running");
|
|
1070
|
+
}
|
|
1071
|
+
},
|
|
1072
|
+
async status(instanceId) {
|
|
1073
|
+
try {
|
|
1074
|
+
const list = runDaytona(["list"]);
|
|
1075
|
+
const lines = list.split("\n");
|
|
1076
|
+
const found = lines.find((l) => l.includes(instanceId));
|
|
1077
|
+
if (!found) return "stopped";
|
|
1078
|
+
const lower = found.toLowerCase();
|
|
1079
|
+
if (lower.includes("started") || lower.includes("running")) {
|
|
1080
|
+
return "running";
|
|
1081
|
+
}
|
|
1082
|
+
if (lower.includes("stopped")) {
|
|
1083
|
+
return "stopped";
|
|
1084
|
+
}
|
|
1085
|
+
return "unknown";
|
|
1086
|
+
} catch {
|
|
1087
|
+
return "unknown";
|
|
1088
|
+
}
|
|
1089
|
+
},
|
|
1090
|
+
async sleep(instanceId) {
|
|
1091
|
+
console.log(`
|
|
1092
|
+
\u{1F4A4} Stopping Daytona workspace: ${instanceId}`);
|
|
1093
|
+
try {
|
|
1094
|
+
runDaytonaInherit(["stop", instanceId]);
|
|
1095
|
+
console.log(" \u2713 Workspace stopped");
|
|
1096
|
+
} catch (err) {
|
|
1097
|
+
throw new Error(`Failed to stop workspace: ${err.stderr || err.message}`);
|
|
1098
|
+
}
|
|
1099
|
+
},
|
|
1100
|
+
async wake(instanceId) {
|
|
1101
|
+
console.log(`
|
|
1102
|
+
\u2600\uFE0F Starting Daytona workspace: ${instanceId}`);
|
|
1103
|
+
try {
|
|
1104
|
+
runDaytonaInherit(["start", instanceId]);
|
|
1105
|
+
console.log(" \u2713 Workspace starting (~30s)");
|
|
1106
|
+
} catch (err) {
|
|
1107
|
+
throw new Error(`Failed to start workspace: ${err.stderr || err.message}`);
|
|
1108
|
+
}
|
|
1109
|
+
},
|
|
1110
|
+
async logs(instanceId, follow = true) {
|
|
1111
|
+
const args = follow ? ["code", instanceId, "--command", "tail -f /workspace/app/agent.log"] : ["code", instanceId, "--command", "cat /workspace/app/agent.log"];
|
|
1112
|
+
const child = (0, import_node_child_process4.spawn)(CLI2, args, { stdio: "inherit" });
|
|
1113
|
+
return new Promise((resolve3, reject) => {
|
|
1114
|
+
child.on("exit", (code) => {
|
|
1115
|
+
if (code === 0) resolve3();
|
|
1116
|
+
else reject(new Error(`daytona exited with code ${code}`));
|
|
1117
|
+
});
|
|
1118
|
+
});
|
|
1119
|
+
},
|
|
1120
|
+
async endpoint(instanceId) {
|
|
1121
|
+
return `daytona://${instanceId}`;
|
|
1122
|
+
},
|
|
1123
|
+
async teardown(instanceId) {
|
|
1124
|
+
console.log(`
|
|
1125
|
+
\u{1F5D1}\uFE0F Destroying Daytona workspace: ${instanceId}`);
|
|
1126
|
+
try {
|
|
1127
|
+
runDaytonaInherit(["remove", instanceId]);
|
|
1128
|
+
console.log(" \u2713 Workspace destroyed");
|
|
1129
|
+
} catch (err) {
|
|
1130
|
+
throw new Error(
|
|
1131
|
+
`Failed to destroy workspace: ${err.stderr || err.message}`
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
});
|
|
1138
|
+
|
|
1139
|
+
// src/deploy/index.ts
|
|
1140
|
+
async function getProvider(name) {
|
|
1141
|
+
const entry = providers[name];
|
|
1142
|
+
if (!entry) {
|
|
1143
|
+
throw new Error(
|
|
1144
|
+
`Provider "${name}" is not yet implemented.
|
|
1145
|
+
Available providers: sprites, e2b, northflank, daytona`
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1148
|
+
if (typeof entry === "function") {
|
|
1149
|
+
return entry();
|
|
1150
|
+
}
|
|
1151
|
+
return entry;
|
|
1152
|
+
}
|
|
1153
|
+
var providers;
|
|
1154
|
+
var init_deploy = __esm({
|
|
1155
|
+
"src/deploy/index.ts"() {
|
|
1156
|
+
"use strict";
|
|
1157
|
+
init_cjs_shims();
|
|
1158
|
+
providers = {
|
|
1159
|
+
sprites: () => Promise.resolve().then(() => (init_sprite_provider(), sprite_provider_exports)).then((m) => m.spriteProvider),
|
|
1160
|
+
e2b: () => Promise.resolve().then(() => (init_e2b_provider(), e2b_provider_exports)).then((m) => m.e2bProvider),
|
|
1161
|
+
northflank: () => Promise.resolve().then(() => (init_northflank_provider(), northflank_provider_exports)).then((m) => m.northflankProvider),
|
|
1162
|
+
daytona: () => Promise.resolve().then(() => (init_daytona_provider(), daytona_provider_exports)).then((m) => m.daytonaProvider)
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
});
|
|
1166
|
+
|
|
1167
|
+
// src/deploy/deploy.ts
|
|
1168
|
+
var deploy_exports = {};
|
|
1169
|
+
__export(deploy_exports, {
|
|
1170
|
+
runDeploy: () => runDeploy,
|
|
1171
|
+
runLogs: () => runLogs,
|
|
1172
|
+
runSleep: () => runSleep,
|
|
1173
|
+
runStatus: () => runStatus,
|
|
1174
|
+
runTeardown: () => runTeardown,
|
|
1175
|
+
runWake: () => runWake
|
|
1176
|
+
});
|
|
1177
|
+
async function runDeploy(options, projectRoot2, packageDir2) {
|
|
1178
|
+
const platform = resolvePlatform(options.platform, projectRoot2);
|
|
1179
|
+
const provider = await getProvider(platform);
|
|
1180
|
+
console.log(`
|
|
1181
|
+
\u{1F680} Deploying to ${provider.label}...`);
|
|
1182
|
+
try {
|
|
1183
|
+
await provider.authCheck();
|
|
1184
|
+
} catch (err) {
|
|
1185
|
+
console.error(`
|
|
1186
|
+
\u274C ${err.message}`);
|
|
1187
|
+
process.exit(1);
|
|
1188
|
+
}
|
|
1189
|
+
if (!options.skipBuild) {
|
|
1190
|
+
await runBuild(projectRoot2, packageDir2);
|
|
1191
|
+
}
|
|
1192
|
+
const distDir = (0, import_node_path5.resolve)(projectRoot2, "dist");
|
|
1193
|
+
const instanceName = options.name || provider.defaultName(projectRoot2);
|
|
1194
|
+
console.log(`
|
|
1195
|
+
\u{1F4CB} Instance: ${instanceName}`);
|
|
1196
|
+
const existingStatus = await provider.status(instanceName);
|
|
1197
|
+
if (options.force) {
|
|
1198
|
+
if (existingStatus === "running" || existingStatus === "sleeping") {
|
|
1199
|
+
await provider.teardown(instanceName);
|
|
1200
|
+
}
|
|
1201
|
+
await provider.provision(instanceName);
|
|
1202
|
+
} else if (existingStatus === "sleeping") {
|
|
1203
|
+
console.log(` \u2600\uFE0F Waking sleeping instance...`);
|
|
1204
|
+
await provider.wake(instanceName);
|
|
1205
|
+
} else if (existingStatus === "running") {
|
|
1206
|
+
console.log(` \u26A0\uFE0F Instance "${instanceName}" is already running.`);
|
|
1207
|
+
const prompts = (await import("prompts")).default;
|
|
1208
|
+
const choice = await prompts({
|
|
1209
|
+
type: "select",
|
|
1210
|
+
name: "action",
|
|
1211
|
+
message: "What do you want to do?",
|
|
1212
|
+
choices: [
|
|
1213
|
+
{ title: "Redeploy (overwrite artifacts)", value: "redeploy" },
|
|
1214
|
+
{ title: "Force recreate VM", value: "force" },
|
|
1215
|
+
{ title: "Cancel", value: "cancel" }
|
|
1216
|
+
]
|
|
1217
|
+
});
|
|
1218
|
+
if (choice.action === "cancel") {
|
|
1219
|
+
console.log("\n Cancelled.\n");
|
|
1220
|
+
process.exit(0);
|
|
1221
|
+
}
|
|
1222
|
+
if (choice.action === "force") {
|
|
1223
|
+
await provider.teardown(instanceName);
|
|
1224
|
+
await provider.provision(instanceName);
|
|
1225
|
+
}
|
|
1226
|
+
} else if (existingStatus === "unknown" || existingStatus === "stopped") {
|
|
1227
|
+
await provider.provision(instanceName);
|
|
1228
|
+
} else if (existingStatus === "provisioning") {
|
|
1229
|
+
console.error(` \u23F3 Instance still provisioning, try again shortly.`);
|
|
1230
|
+
process.exit(1);
|
|
1231
|
+
} else if (existingStatus === "error") {
|
|
1232
|
+
console.error(` \u274C Instance in error state. Run 'hybrid deploy teardown ${instanceName}' then retry.`);
|
|
1233
|
+
process.exit(1);
|
|
1234
|
+
}
|
|
1235
|
+
await provider.deploy(instanceName, distDir);
|
|
1236
|
+
const endpoint = await provider.endpoint(instanceName);
|
|
1237
|
+
console.log(`
|
|
1238
|
+
Instance: ${instanceName}`);
|
|
1239
|
+
console.log(` URL: ${endpoint}`);
|
|
1240
|
+
console.log(` Health: ${endpoint}/health`);
|
|
1241
|
+
console.log(` Chat: ${endpoint}/api/chat`);
|
|
1242
|
+
console.log("\n\u2705 Deployed!");
|
|
1243
|
+
}
|
|
1244
|
+
async function runSleep(name, platform, projectRoot2) {
|
|
1245
|
+
const provider = await getProvider(
|
|
1246
|
+
resolvePlatform(platform, projectRoot2)
|
|
1247
|
+
);
|
|
1248
|
+
await provider.authCheck();
|
|
1249
|
+
await provider.sleep(name);
|
|
1250
|
+
}
|
|
1251
|
+
async function runWake(name, platform, projectRoot2) {
|
|
1252
|
+
const provider = await getProvider(
|
|
1253
|
+
resolvePlatform(platform, projectRoot2)
|
|
1254
|
+
);
|
|
1255
|
+
await provider.authCheck();
|
|
1256
|
+
await provider.wake(name);
|
|
1257
|
+
}
|
|
1258
|
+
async function runStatus(name, platform, projectRoot2) {
|
|
1259
|
+
const provider = await getProvider(
|
|
1260
|
+
resolvePlatform(platform, projectRoot2)
|
|
1261
|
+
);
|
|
1262
|
+
await provider.authCheck();
|
|
1263
|
+
const status = await provider.status(name);
|
|
1264
|
+
const endpoint = await provider.endpoint(name);
|
|
1265
|
+
console.log(`
|
|
1266
|
+
\u{1F4CB} Instance: ${name}`);
|
|
1267
|
+
console.log(` Status: ${status}`);
|
|
1268
|
+
console.log(` URL: ${endpoint}`);
|
|
1269
|
+
}
|
|
1270
|
+
async function runLogs(name, follow, platform, projectRoot2) {
|
|
1271
|
+
const provider = await getProvider(
|
|
1272
|
+
resolvePlatform(platform, projectRoot2)
|
|
1273
|
+
);
|
|
1274
|
+
await provider.authCheck();
|
|
1275
|
+
await provider.logs(name, follow);
|
|
1276
|
+
}
|
|
1277
|
+
async function runTeardown(name, platform, projectRoot2) {
|
|
1278
|
+
const provider = await getProvider(
|
|
1279
|
+
resolvePlatform(platform, projectRoot2)
|
|
1280
|
+
);
|
|
1281
|
+
await provider.authCheck();
|
|
1282
|
+
await provider.teardown(name);
|
|
1283
|
+
}
|
|
1284
|
+
function resolvePlatform(platform, projectRoot2) {
|
|
1285
|
+
if (platform) return platform;
|
|
1286
|
+
if (projectRoot2) {
|
|
1287
|
+
const configPath = (0, import_node_path5.resolve)(projectRoot2, "hybrid.config.ts");
|
|
1288
|
+
if ((0, import_node_fs5.existsSync)(configPath)) {
|
|
1289
|
+
const content = (0, import_node_fs5.readFileSync)(configPath, "utf-8");
|
|
1290
|
+
const match = content.match(
|
|
1291
|
+
/(?:platform|deployPlatform)\s*[=:]\s*["']([^"']+)["']/
|
|
1292
|
+
);
|
|
1293
|
+
if (match) return match[1];
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
return "sprites";
|
|
1297
|
+
}
|
|
1298
|
+
async function runBuild(projectRoot2, packageDir2) {
|
|
1299
|
+
const distDir = (0, import_node_path5.resolve)(projectRoot2, "dist");
|
|
1300
|
+
console.log("\n\u{1F527} Building agent...");
|
|
1301
|
+
if ((0, import_node_fs5.existsSync)(distDir)) {
|
|
1302
|
+
(0, import_node_fs5.rmSync)(distDir, { recursive: true, force: true });
|
|
1303
|
+
}
|
|
1304
|
+
(0, import_node_fs5.mkdirSync)(distDir, { recursive: true });
|
|
1305
|
+
(0, import_node_fs5.mkdirSync)((0, import_node_path5.resolve)(distDir, "server"), { recursive: true });
|
|
1306
|
+
console.log("\u{1F4E6} Copying agent runtime...");
|
|
1307
|
+
const agentDistDir = (0, import_node_path5.resolve)(packageDir2, "dist");
|
|
1308
|
+
const files = ["server/index.cjs"];
|
|
1309
|
+
for (const file of files) {
|
|
1310
|
+
const src = (0, import_node_path5.resolve)(agentDistDir, file);
|
|
1311
|
+
if ((0, import_node_fs5.existsSync)(src)) {
|
|
1312
|
+
(0, import_node_fs5.cpSync)(src, (0, import_node_path5.resolve)(distDir, file));
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
console.log("\u{1F4CB} Copying agent config...");
|
|
1316
|
+
const configFiles = [
|
|
1317
|
+
"SOUL.md",
|
|
1318
|
+
"AGENTS.md",
|
|
1319
|
+
"IDENTITY.md",
|
|
1320
|
+
"TOOLS.md",
|
|
1321
|
+
"BOOT.md",
|
|
1322
|
+
"BOOTSTRAP.md",
|
|
1323
|
+
"HEARTBEAT.md",
|
|
1324
|
+
"USER.md"
|
|
1325
|
+
];
|
|
1326
|
+
for (const file of configFiles) {
|
|
1327
|
+
const src = (0, import_node_path5.resolve)(projectRoot2, file);
|
|
1328
|
+
if ((0, import_node_fs5.existsSync)(src)) {
|
|
1329
|
+
(0, import_node_fs5.cpSync)(src, (0, import_node_path5.resolve)(distDir, file));
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
const credsDir = (0, import_node_path5.resolve)(projectRoot2, "credentials");
|
|
1333
|
+
if ((0, import_node_fs5.existsSync)(credsDir)) {
|
|
1334
|
+
(0, import_node_fs5.cpSync)(credsDir, (0, import_node_path5.resolve)(distDir, "credentials"), { recursive: true });
|
|
1335
|
+
}
|
|
1336
|
+
const deployPkg = {
|
|
1337
|
+
name: "hybrid",
|
|
1338
|
+
version: "1.0.0",
|
|
1339
|
+
type: "module",
|
|
1340
|
+
dependencies: {
|
|
1341
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.38",
|
|
1342
|
+
"@hono/node-server": "^1.13.5",
|
|
1343
|
+
ai: "^6.0.0",
|
|
1344
|
+
"better-sqlite3": "^11.0.0",
|
|
1345
|
+
dotenv: "^16.4.5",
|
|
1346
|
+
hono: "^4.10.8",
|
|
1347
|
+
"sql.js": "^1.11.0",
|
|
1348
|
+
zod: "^4.0.0"
|
|
1349
|
+
}
|
|
1350
|
+
};
|
|
1351
|
+
(0, import_node_fs5.writeFileSync)(
|
|
1352
|
+
(0, import_node_path5.resolve)(distDir, "package.json"),
|
|
1353
|
+
JSON.stringify(deployPkg, null, 2)
|
|
1354
|
+
);
|
|
1355
|
+
const present = configFiles.filter(
|
|
1356
|
+
(f) => (0, import_node_fs5.existsSync)((0, import_node_path5.resolve)(distDir, f))
|
|
1357
|
+
);
|
|
1358
|
+
const hasCredentials = (0, import_node_fs5.existsSync)((0, import_node_path5.resolve)(distDir, "credentials"));
|
|
1359
|
+
const configCopy = present.length > 0 ? `COPY ${present.join(" ")} ./` : "";
|
|
1360
|
+
const credCopy = hasCredentials ? "COPY credentials/ ./credentials/" : "COPY .hybrid-deploy.json ./";
|
|
1361
|
+
(0, import_node_fs5.writeFileSync)(
|
|
1362
|
+
(0, import_node_path5.resolve)(distDir, "Dockerfile"),
|
|
1363
|
+
`FROM node:20-bookworm-slim
|
|
1364
|
+
WORKDIR /app
|
|
1365
|
+
COPY package.json ./
|
|
1366
|
+
RUN npm install --production
|
|
1367
|
+
COPY server/ ./server/
|
|
1368
|
+
COPY ${configCopy} ./
|
|
1369
|
+
${credCopy}
|
|
1370
|
+
ENV AGENT_PORT=8454
|
|
1371
|
+
ENV NODE_ENV=production
|
|
1372
|
+
ENV DATA_ROOT=/app/data
|
|
1373
|
+
EXPOSE 8454
|
|
1374
|
+
USER node
|
|
1375
|
+
CMD ["node", "server/index.cjs"]
|
|
1376
|
+
`
|
|
1377
|
+
);
|
|
1378
|
+
(0, import_node_fs5.writeFileSync)(
|
|
1379
|
+
(0, import_node_path5.resolve)(distDir, "start.sh"),
|
|
1380
|
+
`#!/bin/sh
|
|
1381
|
+
node server/index.cjs
|
|
1382
|
+
`
|
|
1383
|
+
);
|
|
1384
|
+
(0, import_node_fs5.writeFileSync)(
|
|
1385
|
+
(0, import_node_path5.resolve)(distDir, ".hybrid-deploy.json"),
|
|
1386
|
+
JSON.stringify(
|
|
1387
|
+
{
|
|
1388
|
+
version: 1,
|
|
1389
|
+
provider: "firecracker",
|
|
1390
|
+
startCommand: "node server/index.cjs",
|
|
1391
|
+
port: 8454,
|
|
1392
|
+
healthPath: "/health"
|
|
1393
|
+
},
|
|
1394
|
+
null,
|
|
1395
|
+
2
|
|
1396
|
+
)
|
|
1397
|
+
);
|
|
1398
|
+
console.log("\n\u2705 Build complete!");
|
|
1399
|
+
console.log(` Output: ${distDir}`);
|
|
1400
|
+
}
|
|
1401
|
+
var import_node_fs5, import_node_path5;
|
|
1402
|
+
var init_deploy2 = __esm({
|
|
1403
|
+
"src/deploy/deploy.ts"() {
|
|
1404
|
+
"use strict";
|
|
1405
|
+
init_cjs_shims();
|
|
1406
|
+
import_node_fs5 = require("fs");
|
|
1407
|
+
import_node_path5 = require("path");
|
|
1408
|
+
init_deploy();
|
|
1409
|
+
}
|
|
1410
|
+
});
|
|
1411
|
+
|
|
31
1412
|
// src/cli.ts
|
|
32
1413
|
var cli_exports = {};
|
|
33
1414
|
__export(cli_exports, {
|
|
34
1415
|
init: () => init
|
|
35
1416
|
});
|
|
36
1417
|
module.exports = __toCommonJS(cli_exports);
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
var
|
|
40
|
-
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
41
|
-
|
|
42
|
-
// src/cli.ts
|
|
43
|
-
var import_node_fs = require("fs");
|
|
44
|
-
var import_node_path = require("path");
|
|
1418
|
+
init_cjs_shims();
|
|
1419
|
+
var import_node_fs6 = require("fs");
|
|
1420
|
+
var import_node_path6 = require("path");
|
|
45
1421
|
var import_node_url = require("url");
|
|
46
1422
|
var import_dotenv = require("dotenv");
|
|
47
1423
|
function findProjectRoot(startDir) {
|
|
@@ -49,11 +1425,11 @@ function findProjectRoot(startDir) {
|
|
|
49
1425
|
let current = startDir;
|
|
50
1426
|
while (current !== "/") {
|
|
51
1427
|
for (const marker of markers) {
|
|
52
|
-
if ((0,
|
|
1428
|
+
if ((0, import_node_fs6.existsSync)((0, import_node_path6.resolve)(current, marker))) {
|
|
53
1429
|
return current;
|
|
54
1430
|
}
|
|
55
1431
|
}
|
|
56
|
-
const parent = (0,
|
|
1432
|
+
const parent = (0, import_node_path6.resolve)(current, "..");
|
|
57
1433
|
if (parent === current) break;
|
|
58
1434
|
current = parent;
|
|
59
1435
|
}
|
|
@@ -66,8 +1442,8 @@ if (cwdIndex !== -1 && process.argv[cwdIndex + 1]) {
|
|
|
66
1442
|
}
|
|
67
1443
|
var projectRoot = findProjectRoot(process.cwd());
|
|
68
1444
|
for (const envFile of [".env", ".env.local"]) {
|
|
69
|
-
const path = (0,
|
|
70
|
-
if ((0,
|
|
1445
|
+
const path = (0, import_node_path6.resolve)(projectRoot, envFile);
|
|
1446
|
+
if ((0, import_node_fs6.existsSync)(path)) {
|
|
71
1447
|
(0, import_dotenv.config)({ path });
|
|
72
1448
|
}
|
|
73
1449
|
}
|
|
@@ -76,20 +1452,19 @@ if (!major || major < 20) {
|
|
|
76
1452
|
console.error("Error: Node.js version 20 or higher is required");
|
|
77
1453
|
process.exit(1);
|
|
78
1454
|
}
|
|
79
|
-
var __dirname = (0,
|
|
80
|
-
var packageDir = (0,
|
|
1455
|
+
var __dirname = (0, import_node_path6.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
1456
|
+
var packageDir = (0, import_node_path6.resolve)(__dirname, "..");
|
|
81
1457
|
async function main() {
|
|
82
1458
|
const args = process.argv.slice(2);
|
|
83
1459
|
const command = args[0];
|
|
84
|
-
if (command === "build")
|
|
1460
|
+
if (command === "build") {
|
|
1461
|
+
const targetFlag = args.indexOf("--target");
|
|
1462
|
+
return build(targetFlag !== -1 ? args[targetFlag + 1] : args[1]);
|
|
1463
|
+
}
|
|
85
1464
|
if (command === "dev") return dev(args.includes("--docker"));
|
|
86
1465
|
if (command === "start") return start();
|
|
87
|
-
if (command === "deploy") return
|
|
1466
|
+
if (command === "deploy") return deployCommand(args);
|
|
88
1467
|
if (command === "init") return init(args[1]);
|
|
89
|
-
if (command === "keygen") return keygen(args[1]);
|
|
90
|
-
if (command === "register") return register();
|
|
91
|
-
if (command === "revoke") return revoke(args[1]);
|
|
92
|
-
if (command === "revoke-all") return revokeAll();
|
|
93
1468
|
if (command === "owner") {
|
|
94
1469
|
const subcommand = args[1];
|
|
95
1470
|
if (subcommand === "add") return ownerAdd(args[2]);
|
|
@@ -148,17 +1523,24 @@ async function main() {
|
|
|
148
1523
|
console.log("Usage: hybrid <command>");
|
|
149
1524
|
console.log("");
|
|
150
1525
|
console.log("Commands:");
|
|
151
|
-
console.log(" init <name>
|
|
152
|
-
console.log(" dev
|
|
153
|
-
console.log(" build [--target]
|
|
154
|
-
console.log(" start
|
|
155
|
-
console.log(
|
|
1526
|
+
console.log(" init <name> Initialize a new agent");
|
|
1527
|
+
console.log(" dev Start development server");
|
|
1528
|
+
console.log(" build [--target] Build for deployment (firecracker)");
|
|
1529
|
+
console.log(" start Run built agent");
|
|
1530
|
+
console.log(
|
|
1531
|
+
" deploy [platform] Deploy to a Firecracker provider"
|
|
1532
|
+
);
|
|
1533
|
+
console.log(" deploy sleep <name> Put VM to sleep");
|
|
1534
|
+
console.log(" deploy wake <name> Wake VM");
|
|
1535
|
+
console.log(" deploy status <name> Show VM status");
|
|
1536
|
+
console.log(" deploy logs <name> Stream agent logs");
|
|
1537
|
+
console.log(" deploy teardown <name> [--all] Destroy VM");
|
|
156
1538
|
console.log("");
|
|
157
|
-
console.log("
|
|
158
|
-
console.log("
|
|
159
|
-
console.log("
|
|
160
|
-
console.log("
|
|
161
|
-
console.log("
|
|
1539
|
+
console.log("Deploy flags:");
|
|
1540
|
+
console.log(" --provider <name> Override provider (sprites, e2b, northflank, daytona)");
|
|
1541
|
+
console.log(" --name <name> Override instance name");
|
|
1542
|
+
console.log(" --force Recreate VM even if it exists");
|
|
1543
|
+
console.log(" --no-build Skip build step");
|
|
162
1544
|
console.log("");
|
|
163
1545
|
console.log("Owner:");
|
|
164
1546
|
console.log(" owner add <address> Add an owner");
|
|
@@ -264,36 +1646,35 @@ async function init(name) {
|
|
|
264
1646
|
process.exit(1);
|
|
265
1647
|
}
|
|
266
1648
|
const {
|
|
267
|
-
cpSync,
|
|
268
|
-
existsSync:
|
|
269
|
-
mkdirSync,
|
|
270
|
-
writeFileSync,
|
|
1649
|
+
cpSync: cpSync2,
|
|
1650
|
+
existsSync: existsSync7,
|
|
1651
|
+
mkdirSync: mkdirSync2,
|
|
1652
|
+
writeFileSync: writeFileSync2,
|
|
271
1653
|
readdirSync,
|
|
272
|
-
readFileSync
|
|
1654
|
+
readFileSync: readFileSync2
|
|
273
1655
|
} = await import("fs");
|
|
274
|
-
const
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
if (existsSync2(targetDir)) {
|
|
1656
|
+
const templateDir = (0, import_node_path6.resolve)(packageDir, "templates", "agent");
|
|
1657
|
+
const targetDir = (0, import_node_path6.resolve)(process.cwd(), name);
|
|
1658
|
+
if (existsSync7(targetDir)) {
|
|
278
1659
|
console.error(`Error: Directory '${name}' already exists`);
|
|
279
1660
|
process.exit(1);
|
|
280
1661
|
}
|
|
281
1662
|
console.log(`
|
|
282
1663
|
\u{1F4E6} Creating agent: ${name}
|
|
283
1664
|
`);
|
|
284
|
-
|
|
285
|
-
const pkgPath = (0,
|
|
286
|
-
const pkg = JSON.parse(
|
|
1665
|
+
cpSync2(templateDir, targetDir, { recursive: true });
|
|
1666
|
+
const pkgPath = (0, import_node_path6.resolve)(targetDir, "package.json");
|
|
1667
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
287
1668
|
pkg.name = name;
|
|
288
|
-
|
|
289
|
-
const skillsDir = (0,
|
|
290
|
-
const targetSkillsDir = (0,
|
|
291
|
-
if (
|
|
1669
|
+
writeFileSync2(pkgPath, JSON.stringify(pkg, null, 2));
|
|
1670
|
+
const skillsDir = (0, import_node_path6.resolve)(packageDir, "skills");
|
|
1671
|
+
const targetSkillsDir = (0, import_node_path6.resolve)(targetDir, "skills");
|
|
1672
|
+
if (existsSync7(skillsDir)) {
|
|
292
1673
|
console.log("\u{1F4DA} Copying core skills...");
|
|
293
1674
|
const coreSkills = readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
294
|
-
|
|
1675
|
+
mkdirSync2(targetSkillsDir, { recursive: true });
|
|
295
1676
|
for (const skill of coreSkills) {
|
|
296
|
-
|
|
1677
|
+
cpSync2((0, import_node_path6.resolve)(skillsDir, skill), (0, import_node_path6.resolve)(targetSkillsDir, skill), {
|
|
297
1678
|
recursive: true
|
|
298
1679
|
});
|
|
299
1680
|
console.log(` \u2713 ${skill}`);
|
|
@@ -303,36 +1684,16 @@ async function init(name) {
|
|
|
303
1684
|
for (const skill of coreSkills) {
|
|
304
1685
|
lockfile[skill] = { source: "core", installedAt: now };
|
|
305
1686
|
}
|
|
306
|
-
|
|
307
|
-
(0,
|
|
1687
|
+
writeFileSync2(
|
|
1688
|
+
(0, import_node_path6.resolve)(targetDir, "skills-lock.json"),
|
|
308
1689
|
JSON.stringify(lockfile, null, 2)
|
|
309
1690
|
);
|
|
310
1691
|
}
|
|
311
|
-
const envExamplePath = (0,
|
|
312
|
-
const envPath = (0,
|
|
313
|
-
if (
|
|
314
|
-
|
|
1692
|
+
const envExamplePath = (0, import_node_path6.resolve)(targetDir, ".env.example");
|
|
1693
|
+
const envPath = (0, import_node_path6.resolve)(targetDir, ".env");
|
|
1694
|
+
if (existsSync7(envExamplePath)) {
|
|
1695
|
+
cpSync2(envExamplePath, envPath);
|
|
315
1696
|
}
|
|
316
|
-
const rl = createInterface({
|
|
317
|
-
input: process.stdin,
|
|
318
|
-
output: process.stdout
|
|
319
|
-
});
|
|
320
|
-
const question = (prompt) => {
|
|
321
|
-
return new Promise((resolve2) => {
|
|
322
|
-
rl.question(prompt, (answer) => {
|
|
323
|
-
resolve2(answer.trim());
|
|
324
|
-
});
|
|
325
|
-
});
|
|
326
|
-
};
|
|
327
|
-
const ownerAddress = await question("\nEnter your wallet address (owner): ");
|
|
328
|
-
console.log("\n\u{1F511} Generating wallet key...");
|
|
329
|
-
const { privateKeyToAccount } = await import("viem/accounts");
|
|
330
|
-
const { randomBytes } = await import("crypto");
|
|
331
|
-
const walletKey = `0x${randomBytes(32).toString("hex")}`;
|
|
332
|
-
const account = privateKeyToAccount(walletKey);
|
|
333
|
-
console.log(` Address: ${account.address}`);
|
|
334
|
-
console.log(` Key: ${walletKey.slice(0, 10)}...${walletKey.slice(-8)}`);
|
|
335
|
-
rl.close();
|
|
336
1697
|
const prompts = (await import("prompts")).default;
|
|
337
1698
|
const providerResponse = await prompts({
|
|
338
1699
|
type: "select",
|
|
@@ -365,22 +1726,28 @@ async function init(name) {
|
|
|
365
1726
|
});
|
|
366
1727
|
openrouterKey = keyResponse.key || "";
|
|
367
1728
|
}
|
|
1729
|
+
const credentialsDir = (0, import_node_path6.resolve)(targetDir, "credentials");
|
|
1730
|
+
mkdirSync2(credentialsDir, { recursive: true });
|
|
1731
|
+
const acl = {
|
|
1732
|
+
version: 1,
|
|
1733
|
+
allowFrom: []
|
|
1734
|
+
};
|
|
1735
|
+
const ownerResponse = await prompts({
|
|
1736
|
+
type: "text",
|
|
1737
|
+
name: "address",
|
|
1738
|
+
message: "Enter your wallet address (owner, optional)"
|
|
1739
|
+
});
|
|
1740
|
+
const ownerAddress = ownerResponse?.address?.trim() || "";
|
|
368
1741
|
if (ownerAddress) {
|
|
369
|
-
|
|
370
|
-
const credentialsDir = (0, import_node_path.resolve)(targetDir, "credentials");
|
|
371
|
-
mkdirSync(credentialsDir, { recursive: true });
|
|
372
|
-
writeFileSync(
|
|
373
|
-
(0, import_node_path.resolve)(credentialsDir, "xmtp-allowFrom.json"),
|
|
374
|
-
JSON.stringify({ version: 1, allowFrom: [normalized] }, null, 2)
|
|
375
|
-
);
|
|
1742
|
+
acl.allowFrom.push(ownerAddress.toLowerCase());
|
|
376
1743
|
console.log(`
|
|
377
|
-
\u2705 Added owner: ${
|
|
1744
|
+
\u2705 Added owner: ${ownerAddress.toLowerCase()}`);
|
|
378
1745
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
`AGENT_WALLET_KEY=${walletKey}`
|
|
1746
|
+
writeFileSync2(
|
|
1747
|
+
(0, import_node_path6.resolve)(credentialsDir, "allowFrom.json"),
|
|
1748
|
+
JSON.stringify(acl, null, 2)
|
|
383
1749
|
);
|
|
1750
|
+
let envContent = readFileSync2(envPath, "utf-8");
|
|
384
1751
|
if (anthropicKey) {
|
|
385
1752
|
envContent = envContent.replace(
|
|
386
1753
|
/ANTHROPIC_API_KEY=.*/,
|
|
@@ -409,7 +1776,7 @@ async function init(name) {
|
|
|
409
1776
|
(match) => `# ${match}`
|
|
410
1777
|
);
|
|
411
1778
|
}
|
|
412
|
-
|
|
1779
|
+
writeFileSync2(envPath, envContent);
|
|
413
1780
|
console.log("\u2705 Updated .env file");
|
|
414
1781
|
console.log(`
|
|
415
1782
|
\u2705 Created agent at: ${name}/`);
|
|
@@ -421,30 +1788,30 @@ async function init(name) {
|
|
|
421
1788
|
async function build(target) {
|
|
422
1789
|
const { execSync } = await import("child_process");
|
|
423
1790
|
const {
|
|
424
|
-
cpSync,
|
|
425
|
-
existsSync:
|
|
426
|
-
mkdirSync,
|
|
427
|
-
rmSync,
|
|
428
|
-
writeFileSync,
|
|
1791
|
+
cpSync: cpSync2,
|
|
1792
|
+
existsSync: existsSync7,
|
|
1793
|
+
mkdirSync: mkdirSync2,
|
|
1794
|
+
rmSync: rmSync2,
|
|
1795
|
+
writeFileSync: writeFileSync2,
|
|
429
1796
|
readdirSync,
|
|
430
|
-
readFileSync
|
|
1797
|
+
readFileSync: readFileSync2
|
|
431
1798
|
} = await import("fs");
|
|
432
1799
|
const projectDir = projectRoot;
|
|
433
|
-
const distDir = (0,
|
|
434
|
-
const buildTarget = target || "
|
|
1800
|
+
const distDir = (0, import_node_path6.resolve)(projectDir, "dist");
|
|
1801
|
+
const buildTarget = target || "firecracker";
|
|
435
1802
|
console.log("\n\u{1F527} Building agent...");
|
|
436
|
-
if (
|
|
437
|
-
|
|
1803
|
+
if (existsSync7(distDir)) {
|
|
1804
|
+
rmSync2(distDir, { recursive: true, force: true });
|
|
438
1805
|
}
|
|
439
|
-
|
|
440
|
-
|
|
1806
|
+
mkdirSync2(distDir, { recursive: true });
|
|
1807
|
+
mkdirSync2((0, import_node_path6.resolve)(distDir, "server"), { recursive: true });
|
|
441
1808
|
console.log("\u{1F4E6} Copying agent runtime...");
|
|
442
|
-
const agentDistDir = (0,
|
|
443
|
-
const files = ["server/index.cjs"
|
|
1809
|
+
const agentDistDir = (0, import_node_path6.resolve)(packageDir, "dist");
|
|
1810
|
+
const files = ["server/index.cjs"];
|
|
444
1811
|
for (const file of files) {
|
|
445
|
-
const src = (0,
|
|
446
|
-
if (
|
|
447
|
-
|
|
1812
|
+
const src = (0, import_node_path6.resolve)(agentDistDir, file);
|
|
1813
|
+
if (existsSync7(src)) {
|
|
1814
|
+
cpSync2(src, (0, import_node_path6.resolve)(distDir, file));
|
|
448
1815
|
} else {
|
|
449
1816
|
console.error(` Missing: ${file} - run 'pnpm build' in hybrid package`);
|
|
450
1817
|
}
|
|
@@ -460,41 +1827,41 @@ async function build(target) {
|
|
|
460
1827
|
"HEARTBEAT.md",
|
|
461
1828
|
"USER.md"
|
|
462
1829
|
]) {
|
|
463
|
-
const src = (0,
|
|
464
|
-
if (
|
|
465
|
-
|
|
1830
|
+
const src = (0, import_node_path6.resolve)(projectDir, file);
|
|
1831
|
+
if (existsSync7(src)) {
|
|
1832
|
+
cpSync2(src, (0, import_node_path6.resolve)(distDir, file));
|
|
466
1833
|
console.log(` \u2713 ${file}`);
|
|
467
1834
|
}
|
|
468
1835
|
}
|
|
469
|
-
const hybridConfig = (0,
|
|
470
|
-
const openclawConfig = (0,
|
|
471
|
-
const agentConfig = (0,
|
|
472
|
-
if (
|
|
473
|
-
|
|
1836
|
+
const hybridConfig = (0, import_node_path6.resolve)(projectDir, "hybrid.config.ts");
|
|
1837
|
+
const openclawConfig = (0, import_node_path6.resolve)(projectDir, "openclaw.json");
|
|
1838
|
+
const agentConfig = (0, import_node_path6.resolve)(projectDir, "agent.ts");
|
|
1839
|
+
if (existsSync7(hybridConfig)) {
|
|
1840
|
+
cpSync2(hybridConfig, (0, import_node_path6.resolve)(distDir, "hybrid.config.ts"));
|
|
474
1841
|
console.log(" \u2713 hybrid.config.ts");
|
|
475
|
-
} else if (
|
|
476
|
-
const content =
|
|
477
|
-
|
|
478
|
-
(0,
|
|
1842
|
+
} else if (existsSync7(openclawConfig)) {
|
|
1843
|
+
const content = readFileSync2(openclawConfig, "utf-8");
|
|
1844
|
+
writeFileSync2(
|
|
1845
|
+
(0, import_node_path6.resolve)(projectDir, "hybrid.config.ts"),
|
|
479
1846
|
`// Migrated from openclaw.json
|
|
480
1847
|
export default ${content}`
|
|
481
1848
|
);
|
|
482
1849
|
console.log(" \u2713 Migrated openclaw.json \u2192 hybrid.config.ts");
|
|
483
|
-
} else if (
|
|
484
|
-
|
|
1850
|
+
} else if (existsSync7(agentConfig)) {
|
|
1851
|
+
cpSync2(agentConfig, (0, import_node_path6.resolve)(distDir, "agent.ts"));
|
|
485
1852
|
console.log(" \u2713 agent.ts (legacy)");
|
|
486
1853
|
}
|
|
487
|
-
const skillsDir = (0,
|
|
488
|
-
if (
|
|
1854
|
+
const skillsDir = (0, import_node_path6.resolve)(projectDir, "skills");
|
|
1855
|
+
if (existsSync7(skillsDir)) {
|
|
489
1856
|
const skills = readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
490
1857
|
console.log(`\u{1F4DA} Skills: ${skills.length} in ./skills/`);
|
|
491
1858
|
} else {
|
|
492
1859
|
console.log("\u26A0\uFE0F No ./skills/ directory found - run 'hybrid init' first?");
|
|
493
1860
|
}
|
|
494
|
-
const credsDir = (0,
|
|
495
|
-
if (
|
|
496
|
-
|
|
497
|
-
|
|
1861
|
+
const credsDir = (0, import_node_path6.resolve)(projectDir, "credentials");
|
|
1862
|
+
if (existsSync7(credsDir)) {
|
|
1863
|
+
mkdirSync2((0, import_node_path6.resolve)(distDir, "credentials"), { recursive: true });
|
|
1864
|
+
cpSync2(credsDir, (0, import_node_path6.resolve)(distDir, "credentials"), { recursive: true });
|
|
498
1865
|
console.log(" \u2713 credentials/");
|
|
499
1866
|
}
|
|
500
1867
|
const deployPkg = {
|
|
@@ -504,215 +1871,113 @@ export default ${content}`
|
|
|
504
1871
|
dependencies: {
|
|
505
1872
|
"@anthropic-ai/claude-agent-sdk": "^0.2.38",
|
|
506
1873
|
"@hono/node-server": "^1.13.5",
|
|
507
|
-
"@xmtp/agent-sdk": "0.0.14",
|
|
508
|
-
"@xmtp/node-bindings": "^1.9.1",
|
|
509
|
-
"@xmtp/node-sdk": "^4.1.0",
|
|
510
1874
|
ai: "^6.0.0",
|
|
511
1875
|
"better-sqlite3": "^11.0.0",
|
|
512
1876
|
dotenv: "^16.4.5",
|
|
513
1877
|
hono: "^4.10.8",
|
|
514
1878
|
"sql.js": "^1.11.0",
|
|
515
|
-
viem: "^2.46.2",
|
|
516
1879
|
zod: "^4.0.0"
|
|
517
1880
|
}
|
|
518
1881
|
};
|
|
519
|
-
|
|
520
|
-
(0,
|
|
1882
|
+
writeFileSync2(
|
|
1883
|
+
(0, import_node_path6.resolve)(distDir, "package.json"),
|
|
521
1884
|
JSON.stringify(deployPkg, null, 2)
|
|
522
1885
|
);
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
1886
|
+
writeFileSync2((0, import_node_path6.resolve)(distDir, "Dockerfile"), generateDockerfile(distDir));
|
|
1887
|
+
writeFileSync2(
|
|
1888
|
+
(0, import_node_path6.resolve)(distDir, ".hybrid-deploy.json"),
|
|
1889
|
+
JSON.stringify(
|
|
1890
|
+
{
|
|
1891
|
+
version: 1,
|
|
1892
|
+
provider: "firecracker",
|
|
1893
|
+
startCommand: "node server/index.cjs",
|
|
1894
|
+
port: 8454,
|
|
1895
|
+
healthPath: "/health"
|
|
1896
|
+
},
|
|
1897
|
+
null,
|
|
1898
|
+
2
|
|
1899
|
+
)
|
|
1900
|
+
);
|
|
1901
|
+
writeFileSync2(
|
|
1902
|
+
(0, import_node_path6.resolve)(distDir, "start.sh"),
|
|
534
1903
|
`#!/bin/sh
|
|
535
|
-
node server/index.cjs
|
|
536
|
-
node xmtp.cjs &
|
|
537
|
-
wait
|
|
1904
|
+
node server/index.cjs
|
|
538
1905
|
`
|
|
539
1906
|
);
|
|
540
1907
|
console.log("\n\u2705 Build complete!");
|
|
541
1908
|
console.log(` Output: ${distDir}`);
|
|
542
1909
|
console.log(` Target: ${buildTarget}`);
|
|
543
1910
|
}
|
|
544
|
-
function generateDockerfile(
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
1911
|
+
function generateDockerfile(distDir) {
|
|
1912
|
+
const configFiles = [
|
|
1913
|
+
"SOUL.md",
|
|
1914
|
+
"AGENTS.md",
|
|
1915
|
+
"IDENTITY.md",
|
|
1916
|
+
"TOOLS.md",
|
|
1917
|
+
"BOOT.md",
|
|
1918
|
+
"BOOTSTRAP.md",
|
|
1919
|
+
"HEARTBEAT.md",
|
|
1920
|
+
"USER.md"
|
|
1921
|
+
];
|
|
1922
|
+
const present = configFiles.filter(
|
|
1923
|
+
(f) => (0, import_node_fs6.existsSync)((0, import_node_path6.resolve)(distDir, f))
|
|
1924
|
+
);
|
|
1925
|
+
const hasCredentials = (0, import_node_fs6.existsSync)((0, import_node_path6.resolve)(distDir, "credentials"));
|
|
1926
|
+
const configCopy = present.length > 0 ? `COPY ${present.join(" ")} ./` : "";
|
|
1927
|
+
const credCopy = hasCredentials ? "COPY credentials/ ./credentials/" : "";
|
|
1928
|
+
return `FROM node:20-bookworm-slim
|
|
548
1929
|
WORKDIR /app
|
|
549
|
-
|
|
550
|
-
# Copy built agent runtime
|
|
551
|
-
COPY server/ ./server/
|
|
552
|
-
COPY xmtp.cjs ./
|
|
553
|
-
|
|
554
|
-
# Copy config
|
|
555
|
-
COPY SOUL.md ./
|
|
556
|
-
COPY AGENTS.md ./
|
|
557
|
-
COPY IDENTITY.md ./
|
|
558
|
-
COPY TOOLS.md ./
|
|
559
|
-
COPY BOOTSTRAP.md ./
|
|
560
|
-
COPY HEARTBEAT.md ./
|
|
561
|
-
COPY USER.md ./
|
|
562
|
-
|
|
563
|
-
# Copy credentials (owner ACL)
|
|
564
|
-
COPY credentials/ ./credentials/
|
|
565
|
-
|
|
566
|
-
# Copy deployment files
|
|
567
1930
|
COPY package.json ./
|
|
568
|
-
COPY start.sh ./
|
|
569
|
-
|
|
570
|
-
# Install dependencies
|
|
571
1931
|
RUN npm install --production
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
chown -R node:node /app
|
|
576
|
-
|
|
1932
|
+
COPY server/ ./server/
|
|
1933
|
+
${configCopy}
|
|
1934
|
+
${credCopy}
|
|
577
1935
|
ENV AGENT_PORT=8454
|
|
578
1936
|
ENV NODE_ENV=production
|
|
1937
|
+
ENV DATA_ROOT=/app/data
|
|
579
1938
|
EXPOSE 8454
|
|
580
|
-
|
|
581
|
-
USER node
|
|
582
|
-
CMD ["sh", "start.sh"]
|
|
583
|
-
`;
|
|
584
|
-
}
|
|
585
|
-
return `FROM node:20
|
|
586
|
-
WORKDIR /app
|
|
587
|
-
COPY . ./
|
|
588
|
-
RUN npm install --production
|
|
589
|
-
RUN chown -R node:node /app
|
|
590
1939
|
USER node
|
|
591
|
-
CMD ["
|
|
592
|
-
`;
|
|
593
|
-
}
|
|
594
|
-
function generateFlyToml(appName = "hybrid-agent") {
|
|
595
|
-
return `# Generated by hybrid build
|
|
596
|
-
app = "${appName}"
|
|
597
|
-
primary_region = "iad"
|
|
598
|
-
|
|
599
|
-
[build]
|
|
600
|
-
dockerfile = "Dockerfile"
|
|
601
|
-
context = "."
|
|
602
|
-
|
|
603
|
-
[deployment]
|
|
604
|
-
min_machines = 1
|
|
605
|
-
max_machines = 1
|
|
606
|
-
|
|
607
|
-
[env]
|
|
608
|
-
XMTP_ENV = "production"
|
|
609
|
-
|
|
610
|
-
[[services]]
|
|
611
|
-
protocol = "tcp"
|
|
612
|
-
internal_port = 8454
|
|
613
|
-
|
|
614
|
-
[[services.ports]]
|
|
615
|
-
port = 80
|
|
616
|
-
handlers = ["http"]
|
|
617
|
-
force_https = true
|
|
618
|
-
|
|
619
|
-
[[services.ports]]
|
|
620
|
-
port = 443
|
|
621
|
-
handlers = ["tls", "http"]
|
|
622
|
-
|
|
623
|
-
[vm]
|
|
624
|
-
memory = "1gb"
|
|
625
|
-
cpu_kind = "shared"
|
|
626
|
-
cpus = 1
|
|
1940
|
+
CMD ["node", "server/index.cjs"]
|
|
627
1941
|
`;
|
|
628
1942
|
}
|
|
629
1943
|
async function dev(useDocker) {
|
|
630
1944
|
const { execSync } = await import("child_process");
|
|
631
|
-
const { existsSync: existsSync2, readFileSync } = await import("fs");
|
|
632
|
-
const { privateKeyToAccount } = await import("viem/accounts");
|
|
633
1945
|
if (useDocker) {
|
|
634
1946
|
console.log("\n\u{1F433} Docker dev not yet implemented for new structure");
|
|
635
1947
|
console.log("Use 'hybrid dev' without --docker for now");
|
|
636
1948
|
return;
|
|
637
1949
|
}
|
|
638
1950
|
const projectDir = projectRoot;
|
|
639
|
-
const
|
|
640
|
-
if (walletKey) {
|
|
641
|
-
const walletKeyFormatted = walletKey.startsWith("0x") ? walletKey : `0x${walletKey}`;
|
|
642
|
-
const account = privateKeyToAccount(walletKeyFormatted);
|
|
643
|
-
console.log(`
|
|
644
|
-
\u{1F50D} Checking XMTP registration for ${account.address}...`);
|
|
645
|
-
try {
|
|
646
|
-
const result = execSync(
|
|
647
|
-
`npx tsx -e "
|
|
648
|
-
import { createSigner, createXMTPClient } from './src/client';
|
|
649
|
-
const signer = createSigner('${walletKeyFormatted}');
|
|
650
|
-
const client = await createXMTPClient(signer, { env: 'production' });
|
|
651
|
-
console.log('REGISTERED:', client.inboxId);
|
|
652
|
-
process.exit(0);
|
|
653
|
-
"`,
|
|
654
|
-
{
|
|
655
|
-
cwd: (0, import_node_path.resolve)(packageDir, "..", "xmtp"),
|
|
656
|
-
encoding: "utf-8",
|
|
657
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
658
|
-
}
|
|
659
|
-
);
|
|
660
|
-
if (result.includes("REGISTERED:")) {
|
|
661
|
-
const inboxId = result.match(/REGISTERED: (.+)/)?.[1];
|
|
662
|
-
console.log(` \u2705 Already registered (inbox: ${inboxId})`);
|
|
663
|
-
}
|
|
664
|
-
} catch (err) {
|
|
665
|
-
const output = err.stdout || err.stderr || "";
|
|
666
|
-
if (output.includes("not registered") || output.includes("No inbox found") || output.includes("incomplete identity") || err.exitCode) {
|
|
667
|
-
console.log(" \u274C Not registered");
|
|
668
|
-
console.log("\n\u{1F4DD} Registering agent on XMTP...");
|
|
669
|
-
execSync("npx pnpm --filter @hybrd/xmtp register", {
|
|
670
|
-
cwd: (0, import_node_path.resolve)(packageDir, "..", ".."),
|
|
671
|
-
stdio: "inherit",
|
|
672
|
-
env: { ...process.env, AGENT_WALLET_KEY: walletKey }
|
|
673
|
-
});
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
const agentServer = (0, import_node_path.resolve)(packageDir, "dist", "server", "index.cjs");
|
|
678
|
-
const agentXmtp = (0, import_node_path.resolve)(packageDir, "dist", "xmtp.cjs");
|
|
1951
|
+
const agentServer = (0, import_node_path6.resolve)(packageDir, "dist", "server", "index.cjs");
|
|
679
1952
|
console.log("\n\u{1F680} Starting development server...\n");
|
|
680
1953
|
console.log(` Project: ${projectDir}`);
|
|
681
1954
|
console.log(` Runtime: ${packageDir}
|
|
682
1955
|
`);
|
|
683
1956
|
try {
|
|
684
|
-
execSync(
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
...process.env,
|
|
691
|
-
AGENT_PROJECT_ROOT: projectDir
|
|
692
|
-
}
|
|
1957
|
+
execSync(`node ${agentServer}`, {
|
|
1958
|
+
cwd: projectDir,
|
|
1959
|
+
stdio: "inherit",
|
|
1960
|
+
env: {
|
|
1961
|
+
...process.env,
|
|
1962
|
+
AGENT_PROJECT_ROOT: projectDir
|
|
693
1963
|
}
|
|
694
|
-
);
|
|
1964
|
+
});
|
|
695
1965
|
} catch {
|
|
696
1966
|
console.error("\n\u274C Failed to start dev server");
|
|
697
1967
|
process.exit(1);
|
|
698
1968
|
}
|
|
699
1969
|
}
|
|
700
1970
|
async function start() {
|
|
701
|
-
const { spawn } = await import("child_process");
|
|
702
|
-
const { existsSync:
|
|
1971
|
+
const { spawn: spawn4 } = await import("child_process");
|
|
1972
|
+
const { existsSync: existsSync7 } = await import("fs");
|
|
703
1973
|
const projectDir = projectRoot;
|
|
704
|
-
const distDir = (0,
|
|
705
|
-
if (!
|
|
1974
|
+
const distDir = (0, import_node_path6.resolve)(projectDir, "dist");
|
|
1975
|
+
if (!existsSync7((0, import_node_path6.resolve)(distDir, "server", "simple.cjs"))) {
|
|
706
1976
|
console.error("Error: No build found. Run 'hybrid build' first.");
|
|
707
1977
|
process.exit(1);
|
|
708
1978
|
}
|
|
709
1979
|
console.log("\n\u{1F680} Starting agent from ./dist/...\n");
|
|
710
|
-
const server =
|
|
711
|
-
cwd: projectDir,
|
|
712
|
-
stdio: "inherit",
|
|
713
|
-
env: { ...process.env, AGENT_PROJECT_ROOT: projectDir }
|
|
714
|
-
});
|
|
715
|
-
const xmtp = spawn("node", [(0, import_node_path.resolve)(distDir, "xmtp.cjs")], {
|
|
1980
|
+
const server = spawn4("node", [(0, import_node_path6.resolve)(distDir, "server", "simple.cjs")], {
|
|
716
1981
|
cwd: projectDir,
|
|
717
1982
|
stdio: "inherit",
|
|
718
1983
|
env: { ...process.env, AGENT_PROJECT_ROOT: projectDir }
|
|
@@ -721,395 +1986,188 @@ async function start() {
|
|
|
721
1986
|
if (code !== 0 && code !== null) process.exit(code);
|
|
722
1987
|
};
|
|
723
1988
|
server.on("exit", exitHandler);
|
|
724
|
-
xmtp.on("exit", exitHandler);
|
|
725
1989
|
process.on("SIGINT", () => {
|
|
726
1990
|
server.kill("SIGINT");
|
|
727
|
-
xmtp.kill("SIGINT");
|
|
728
1991
|
process.exit(0);
|
|
729
1992
|
});
|
|
730
1993
|
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
const { privateKeyToAccount } = await import("viem/accounts");
|
|
736
|
-
const { randomBytes } = await import("crypto");
|
|
737
|
-
const projectDir = projectRoot;
|
|
738
|
-
const distDir = (0, import_node_path.resolve)(projectDir, "dist");
|
|
739
|
-
let deployPlatform = platform;
|
|
740
|
-
if (!deployPlatform) {
|
|
741
|
-
const hybridConfig = (0, import_node_path.resolve)(projectDir, "hybrid.config.ts");
|
|
742
|
-
if (existsSync2(hybridConfig)) {
|
|
743
|
-
const configContent = readFileSync(hybridConfig, "utf-8");
|
|
744
|
-
const match = configContent.match(
|
|
745
|
-
/deployPlatform\s*[=:]\s*["']([^"']+)["']/
|
|
746
|
-
);
|
|
747
|
-
if (match) {
|
|
748
|
-
deployPlatform = match[1];
|
|
749
|
-
console.log(` Using saved platform: ${deployPlatform}`);
|
|
750
|
-
}
|
|
751
|
-
}
|
|
1994
|
+
var deployModule = null;
|
|
1995
|
+
async function loadDeploy() {
|
|
1996
|
+
if (!deployModule) {
|
|
1997
|
+
deployModule = await Promise.resolve().then(() => (init_deploy2(), deploy_exports));
|
|
752
1998
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
if (
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
}
|
|
768
|
-
deployPlatform = choice.platform;
|
|
769
|
-
const hybridConfig = (0, import_node_path.resolve)(projectDir, "hybrid.config.ts");
|
|
770
|
-
let configContent = "";
|
|
771
|
-
if (existsSync2(hybridConfig)) {
|
|
772
|
-
configContent = readFileSync(hybridConfig, "utf-8");
|
|
773
|
-
}
|
|
774
|
-
if (!configContent.includes("deployPlatform")) {
|
|
775
|
-
const newContent = configContent ? configContent.replace(
|
|
776
|
-
/export default/,
|
|
777
|
-
`const deployPlatform = "${deployPlatform}"
|
|
778
|
-
|
|
779
|
-
export default`
|
|
780
|
-
) : `const deployPlatform = "${deployPlatform}"
|
|
781
|
-
|
|
782
|
-
export default {}`;
|
|
783
|
-
writeFileSync(hybridConfig, newContent);
|
|
1999
|
+
return deployModule;
|
|
2000
|
+
}
|
|
2001
|
+
function parseDeployArgs(args) {
|
|
2002
|
+
const skipSet = /* @__PURE__ */ new Set();
|
|
2003
|
+
const valuedFlags = /* @__PURE__ */ new Set([
|
|
2004
|
+
"--provider",
|
|
2005
|
+
"-p",
|
|
2006
|
+
"--name",
|
|
2007
|
+
"-n"
|
|
2008
|
+
]);
|
|
2009
|
+
for (let i = 0; i < args.length; i++) {
|
|
2010
|
+
if (valuedFlags.has(args[i])) {
|
|
2011
|
+
skipSet.add(i);
|
|
2012
|
+
skipSet.add(i + 1);
|
|
784
2013
|
}
|
|
785
2014
|
}
|
|
786
|
-
const
|
|
787
|
-
const
|
|
788
|
-
const
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
2015
|
+
const providerIdx = args.indexOf("--provider");
|
|
2016
|
+
const providerAltIdx = args.indexOf("-p");
|
|
2017
|
+
const nameIdx = args.indexOf("--name");
|
|
2018
|
+
const nameAltIdx = args.indexOf("-n");
|
|
2019
|
+
const providerFlag = (providerIdx !== -1 ? args[providerIdx + 1] : void 0) || (providerAltIdx !== -1 ? args[providerAltIdx + 1] : void 0);
|
|
2020
|
+
const nameFlag = (nameIdx !== -1 ? args[nameIdx + 1] : void 0) || (nameAltIdx !== -1 ? args[nameAltIdx + 1] : void 0);
|
|
2021
|
+
const name = args.find(
|
|
2022
|
+
(a, i) => i > 1 && !a.startsWith("--") && !a.startsWith("-") && !skipSet.has(i) && a !== "deploy" && a !== "sleep" && a !== "wake" && a !== "status" && a !== "logs" && a !== "teardown" && a !== providerFlag && a !== nameFlag
|
|
2023
|
+
);
|
|
2024
|
+
const skipBuild = args.includes("--no-build");
|
|
2025
|
+
const force = args.includes("--force");
|
|
2026
|
+
const follow = !args.includes("--no-follow");
|
|
2027
|
+
return {
|
|
2028
|
+
platform: providerFlag,
|
|
2029
|
+
name: name || nameFlag,
|
|
2030
|
+
skipBuild,
|
|
2031
|
+
force,
|
|
2032
|
+
follow
|
|
2033
|
+
};
|
|
2034
|
+
}
|
|
2035
|
+
async function deployCommand(args) {
|
|
2036
|
+
const sub = args[1];
|
|
2037
|
+
const knownProviders = /* @__PURE__ */ new Set([
|
|
2038
|
+
"sprites",
|
|
2039
|
+
"e2b",
|
|
2040
|
+
"daytona",
|
|
2041
|
+
"northflank"
|
|
2042
|
+
]);
|
|
2043
|
+
const isPlatform = sub && !sub.startsWith("-") && knownProviders.has(sub);
|
|
2044
|
+
if (!sub || sub.startsWith("-") || isPlatform) {
|
|
2045
|
+
const flags = parseDeployArgs(args);
|
|
2046
|
+
const { runDeploy: runDeploy2 } = await loadDeploy();
|
|
2047
|
+
await runDeploy2(
|
|
2048
|
+
{
|
|
2049
|
+
platform: flags.platform || (isPlatform ? sub : void 0),
|
|
2050
|
+
name: flags.name,
|
|
2051
|
+
skipBuild: flags.skipBuild,
|
|
2052
|
+
force: flags.force
|
|
2053
|
+
},
|
|
2054
|
+
projectRoot,
|
|
2055
|
+
packageDir
|
|
2056
|
+
);
|
|
2057
|
+
return;
|
|
816
2058
|
}
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
name: "key",
|
|
840
|
-
message: "Paste OpenRouter API key:"
|
|
841
|
-
});
|
|
842
|
-
const key = result.key?.trim();
|
|
843
|
-
if (key && key.length > 0) {
|
|
844
|
-
openRouterKey = key;
|
|
845
|
-
break;
|
|
846
|
-
}
|
|
847
|
-
console.log(" OpenRouter API key is required. Please enter a value.");
|
|
2059
|
+
const pIdx = args.indexOf("--provider");
|
|
2060
|
+
const pAlt = args.indexOf("-p");
|
|
2061
|
+
const nIdx = args.indexOf("--name");
|
|
2062
|
+
const nAlt = args.indexOf("-n");
|
|
2063
|
+
const subPlatform = (pIdx !== -1 ? args[pIdx + 1] : void 0) || (pAlt !== -1 ? args[pAlt + 1] : void 0);
|
|
2064
|
+
const subSkipIdx = /* @__PURE__ */ new Set();
|
|
2065
|
+
subSkipIdx.add(pIdx);
|
|
2066
|
+
subSkipIdx.add(pAlt);
|
|
2067
|
+
subSkipIdx.add(nIdx);
|
|
2068
|
+
subSkipIdx.add(nAlt);
|
|
2069
|
+
if (pIdx !== -1) subSkipIdx.add(pIdx + 1);
|
|
2070
|
+
if (pAlt !== -1) subSkipIdx.add(pAlt + 1);
|
|
2071
|
+
if (nIdx !== -1) subSkipIdx.add(nIdx + 1);
|
|
2072
|
+
if (nAlt !== -1) subSkipIdx.add(nAlt + 1);
|
|
2073
|
+
const name = args.find(
|
|
2074
|
+
(a, i) => i > 1 && !a.startsWith("-") && !subSkipIdx.has(i)
|
|
2075
|
+
);
|
|
2076
|
+
switch (sub) {
|
|
2077
|
+
case "sleep": {
|
|
2078
|
+
if (!name) {
|
|
2079
|
+
console.error("Usage: hybrid deploy sleep <name>");
|
|
2080
|
+
process.exit(1);
|
|
848
2081
|
}
|
|
2082
|
+
const { runSleep: runSleep2 } = await loadDeploy();
|
|
2083
|
+
await runSleep2(name, subPlatform, projectRoot);
|
|
2084
|
+
break;
|
|
849
2085
|
}
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
name: "owner",
|
|
855
|
-
message: "Your wallet address (owner):",
|
|
856
|
-
validate: (v) => /^0x[a-fA-F0-9]{40}$/.test(v) || "Enter a valid Ethereum address (0x...)"
|
|
857
|
-
});
|
|
858
|
-
if (result.owner) {
|
|
859
|
-
const { mkdirSync } = await import("fs");
|
|
860
|
-
mkdirSync((0, import_node_path.resolve)(projectDir, "credentials"), { recursive: true });
|
|
861
|
-
writeFileSync(
|
|
862
|
-
aclPath,
|
|
863
|
-
JSON.stringify(
|
|
864
|
-
{ version: 1, allowFrom: [result.owner.toLowerCase()] },
|
|
865
|
-
null,
|
|
866
|
-
" "
|
|
867
|
-
)
|
|
868
|
-
);
|
|
869
|
-
console.log(`\u2705 Owner set: ${result.owner}
|
|
870
|
-
`);
|
|
2086
|
+
case "wake": {
|
|
2087
|
+
if (!name) {
|
|
2088
|
+
console.error("Usage: hybrid deploy wake <name>");
|
|
2089
|
+
process.exit(1);
|
|
871
2090
|
}
|
|
2091
|
+
const { runWake: runWake2 } = await loadDeploy();
|
|
2092
|
+
await runWake2(name, subPlatform, projectRoot);
|
|
2093
|
+
break;
|
|
872
2094
|
}
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
if (existsSync2(projectFlyToml)) {
|
|
878
|
-
const { cpSync } = await import("fs");
|
|
879
|
-
cpSync(projectFlyToml, (0, import_node_path.resolve)(distDir, "fly.toml"));
|
|
880
|
-
} else {
|
|
881
|
-
const { writeFileSync: writeFileSync2 } = await import("fs");
|
|
882
|
-
const flyTomlContent = generateFlyToml(appName);
|
|
883
|
-
writeFileSync2((0, import_node_path.resolve)(distDir, "fly.toml"), flyTomlContent);
|
|
884
|
-
}
|
|
885
|
-
if (!appExists) {
|
|
886
|
-
console.log(`\u{1F4E6} Creating Fly.io app: ${appName}`);
|
|
887
|
-
try {
|
|
888
|
-
execFileSync("fly", ["apps", "create", appName], {
|
|
889
|
-
cwd: distDir,
|
|
890
|
-
stdio: "inherit"
|
|
891
|
-
});
|
|
892
|
-
} catch {
|
|
893
|
-
console.log(` App may already exist, continuing...`);
|
|
2095
|
+
case "status": {
|
|
2096
|
+
if (!name) {
|
|
2097
|
+
console.error("Usage: hybrid deploy status <name>");
|
|
2098
|
+
process.exit(1);
|
|
894
2099
|
}
|
|
2100
|
+
const { runStatus: runStatus2 } = await loadDeploy();
|
|
2101
|
+
await runStatus2(name, subPlatform, projectRoot);
|
|
2102
|
+
break;
|
|
895
2103
|
}
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
});
|
|
901
|
-
deploy2.on(
|
|
902
|
-
"error",
|
|
903
|
-
(err) => reject(new Error(`Deploy failed: ${err.message}`))
|
|
904
|
-
);
|
|
905
|
-
deploy2.on(
|
|
906
|
-
"close",
|
|
907
|
-
(code) => code === 0 ? resolve2() : reject(new Error(`Exit ${code}`))
|
|
908
|
-
);
|
|
909
|
-
});
|
|
910
|
-
if (walletKey) {
|
|
911
|
-
console.log("\n\u{1F510} Setting wallet key as secret...");
|
|
912
|
-
try {
|
|
913
|
-
execFileSync(
|
|
914
|
-
"fly",
|
|
915
|
-
["secrets", "set", `AGENT_WALLET_KEY=${walletKey}`, "--app", appName],
|
|
916
|
-
{
|
|
917
|
-
cwd: distDir,
|
|
918
|
-
stdio: "inherit"
|
|
919
|
-
}
|
|
920
|
-
);
|
|
921
|
-
} catch (e) {
|
|
922
|
-
console.log(" \u26A0\uFE0F Could not set secret, skipping...");
|
|
2104
|
+
case "logs": {
|
|
2105
|
+
if (!name) {
|
|
2106
|
+
console.error("Usage: hybrid deploy logs <name>");
|
|
2107
|
+
process.exit(1);
|
|
923
2108
|
}
|
|
2109
|
+
const follow = !args.includes("--no-follow");
|
|
2110
|
+
const { runLogs: runLogs2 } = await loadDeploy();
|
|
2111
|
+
await runLogs2(name, follow, subPlatform, projectRoot);
|
|
2112
|
+
break;
|
|
924
2113
|
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
"fly",
|
|
930
|
-
["secrets", "set", `OPENROUTER_API_KEY=${openRouterKey}`, "--app", appName],
|
|
931
|
-
{
|
|
932
|
-
cwd: distDir,
|
|
933
|
-
stdio: "inherit"
|
|
934
|
-
}
|
|
935
|
-
);
|
|
936
|
-
} catch (e) {
|
|
937
|
-
console.log(" \u26A0\uFE0F Could not set secret, skipping...");
|
|
2114
|
+
case "teardown": {
|
|
2115
|
+
if (!name) {
|
|
2116
|
+
console.error("Usage: hybrid deploy teardown <name>");
|
|
2117
|
+
process.exit(1);
|
|
938
2118
|
}
|
|
2119
|
+
const { runTeardown: runTeardown2 } = await loadDeploy();
|
|
2120
|
+
await runTeardown2(name, subPlatform, projectRoot);
|
|
2121
|
+
break;
|
|
939
2122
|
}
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
cpSync((0, import_node_path.resolve)(distDir, "fly.toml"), projectFlyToml);
|
|
945
|
-
console.log(` Saved fly.toml to project`);
|
|
946
|
-
}
|
|
947
|
-
return;
|
|
948
|
-
}
|
|
949
|
-
if (deployPlatform === "railway") {
|
|
950
|
-
console.log("\n\u{1F682} Railway deployment not yet implemented.");
|
|
951
|
-
console.log(" See https://railway.app for manual deployment.");
|
|
952
|
-
process.exit(1);
|
|
2123
|
+
default:
|
|
2124
|
+
console.error(`Unknown deploy subcommand: ${sub}`);
|
|
2125
|
+
printDeployHelp();
|
|
2126
|
+
process.exit(1);
|
|
953
2127
|
}
|
|
954
|
-
console.error(`Unknown platform: ${deployPlatform}`);
|
|
955
|
-
console.error("Supported: fly, railway");
|
|
956
|
-
process.exit(1);
|
|
957
2128
|
}
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
return;
|
|
966
|
-
}
|
|
967
|
-
const { privateKeyToAccount } = await import("viem/accounts");
|
|
968
|
-
const { randomBytes } = await import("crypto");
|
|
969
|
-
const targetPrefix = prefix?.toLowerCase() || "";
|
|
970
|
-
if (targetPrefix && !/^[0-9a-f]+$/.test(targetPrefix)) {
|
|
971
|
-
console.error("\n\u274C Prefix must be hex characters (0-9, a-f)");
|
|
972
|
-
process.exit(1);
|
|
973
|
-
}
|
|
974
|
-
if (targetPrefix.length > 6) {
|
|
975
|
-
console.error("\n\u274C Prefix too long (max 6 characters)");
|
|
976
|
-
process.exit(1);
|
|
977
|
-
}
|
|
978
|
-
console.log("\n\u{1F511} Generating wallet...");
|
|
979
|
-
if (targetPrefix) console.log(` Looking for 0x${targetPrefix}...`);
|
|
980
|
-
const walletKey = await generateVanityWallet(
|
|
981
|
-
targetPrefix,
|
|
982
|
-
privateKeyToAccount,
|
|
983
|
-
randomBytes
|
|
2129
|
+
function printDeployHelp() {
|
|
2130
|
+
console.error("");
|
|
2131
|
+
console.error("Usage: hybrid deploy <subcommand>");
|
|
2132
|
+
console.error("");
|
|
2133
|
+
console.error("Commands:");
|
|
2134
|
+
console.error(
|
|
2135
|
+
" deploy [platform] Deploy to a Firecracker provider"
|
|
984
2136
|
);
|
|
985
|
-
|
|
986
|
-
console.
|
|
987
|
-
|
|
988
|
-
console.
|
|
989
|
-
console.
|
|
990
|
-
|
|
991
|
-
console.
|
|
992
|
-
|
|
993
|
-
`);
|
|
994
|
-
}
|
|
995
|
-
async function generateVanityWallet(prefix, privateKeyToAccount, randomBytes) {
|
|
996
|
-
let attempts = 0;
|
|
997
|
-
const max = prefix ? 1e6 : 1;
|
|
998
|
-
while (attempts < max) {
|
|
999
|
-
attempts++;
|
|
1000
|
-
const key = `0x${randomBytes(32).toString("hex")}`;
|
|
1001
|
-
if (!prefix) return key;
|
|
1002
|
-
const { address } = privateKeyToAccount(key);
|
|
1003
|
-
if (address.toLowerCase().startsWith(`0x${prefix}`)) {
|
|
1004
|
-
console.log(` Found in ${attempts} attempts!`);
|
|
1005
|
-
return key;
|
|
1006
|
-
}
|
|
1007
|
-
if (attempts % 1e3 === 0) {
|
|
1008
|
-
process.stdout.write(
|
|
1009
|
-
`\r Searching${".".repeat(attempts / 1e3 % 4)}${" ".repeat(3)}${attempts}\r`
|
|
1010
|
-
);
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
throw new Error(`No vanity address found after ${max} attempts`);
|
|
1014
|
-
}
|
|
1015
|
-
async function register() {
|
|
1016
|
-
const { execSync } = await import("child_process");
|
|
1017
|
-
const { existsSync: existsSync2, readFileSync } = await import("fs");
|
|
1018
|
-
const { join } = await import("path");
|
|
1019
|
-
const { privateKeyToAccount } = await import("viem/accounts");
|
|
1020
|
-
const walletKey = process.env.AGENT_WALLET_KEY || process.env.WALLET_KEY;
|
|
1021
|
-
if (!walletKey) {
|
|
1022
|
-
console.error("\n\u274C Set AGENT_WALLET_KEY first");
|
|
1023
|
-
process.exit(1);
|
|
1024
|
-
}
|
|
1025
|
-
const account = privateKeyToAccount(
|
|
1026
|
-
walletKey.startsWith("0x") ? walletKey : `0x${walletKey}`
|
|
2137
|
+
console.error(" deploy sleep <name> Put VM to sleep");
|
|
2138
|
+
console.error(" deploy wake <name> Wake VM");
|
|
2139
|
+
console.error(" deploy status <name> Show VM status");
|
|
2140
|
+
console.error(" deploy logs <name> Stream agent logs");
|
|
2141
|
+
console.error(" deploy teardown <name> [--all] Destroy VM");
|
|
2142
|
+
console.error("");
|
|
2143
|
+
console.error(
|
|
2144
|
+
"Flags: --provider <name> --name <name> --force --no-build --no-follow"
|
|
1027
2145
|
);
|
|
1028
|
-
const projectDir = projectRoot;
|
|
1029
|
-
const aclPath = join(projectDir, "credentials", "xmtp-allowFrom.json");
|
|
1030
|
-
if (!existsSync2(aclPath)) {
|
|
1031
|
-
console.error("\n\u274C No credentials/xmtp-allowFrom.json");
|
|
1032
|
-
console.error("Run 'hybrid init' first");
|
|
1033
|
-
process.exit(1);
|
|
1034
|
-
}
|
|
1035
|
-
try {
|
|
1036
|
-
const acl = JSON.parse(readFileSync(aclPath, "utf-8"));
|
|
1037
|
-
if (!acl.allowFrom?.length) {
|
|
1038
|
-
console.error("\n\u274C No owners in ACL");
|
|
1039
|
-
process.exit(1);
|
|
1040
|
-
}
|
|
1041
|
-
} catch {
|
|
1042
|
-
console.error("\n\u274C Invalid credentials/xmtp-allowFrom.json");
|
|
1043
|
-
process.exit(1);
|
|
1044
|
-
}
|
|
1045
|
-
console.log(`
|
|
1046
|
-
\u{1F510} Registering ${account.address} on XMTP...`);
|
|
1047
|
-
try {
|
|
1048
|
-
execSync("npx pnpm --filter @hybrd/xmtp register", {
|
|
1049
|
-
cwd: (0, import_node_path.resolve)(packageDir, "..", ".."),
|
|
1050
|
-
stdio: "inherit",
|
|
1051
|
-
env: { ...process.env, AGENT_WALLET_KEY: walletKey }
|
|
1052
|
-
});
|
|
1053
|
-
} catch {
|
|
1054
|
-
console.error("\n\u274C Registration failed");
|
|
1055
|
-
process.exit(1);
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
async function revoke(inboxId) {
|
|
1059
|
-
const { execSync } = await import("child_process");
|
|
1060
|
-
if (!inboxId) {
|
|
1061
|
-
console.log("\nUsage: hybrid revoke <inboxId>");
|
|
1062
|
-
console.log("Or use: hybrid revoke-all\n");
|
|
1063
|
-
process.exit(1);
|
|
1064
|
-
}
|
|
1065
|
-
console.log("\n\u{1F504} Revoking XMTP installations...\n");
|
|
1066
|
-
try {
|
|
1067
|
-
execSync(`npx pnpm --filter @hybrd/xmtp revoke ${inboxId}`, {
|
|
1068
|
-
cwd: (0, import_node_path.resolve)(packageDir, "..", ".."),
|
|
1069
|
-
stdio: "inherit"
|
|
1070
|
-
});
|
|
1071
|
-
} catch {
|
|
1072
|
-
console.log("\n\u274C Revoke failed. Check AGENT_WALLET_KEY");
|
|
1073
|
-
process.exit(1);
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
async function revokeAll() {
|
|
1077
|
-
const { execSync } = await import("child_process");
|
|
1078
|
-
console.log("\n\u{1F504} Revoking all XMTP installations...\n");
|
|
1079
|
-
try {
|
|
1080
|
-
execSync("npx pnpm --filter @hybrd/xmtp revoke-all", {
|
|
1081
|
-
cwd: (0, import_node_path.resolve)(packageDir, "..", ".."),
|
|
1082
|
-
stdio: "inherit"
|
|
1083
|
-
});
|
|
1084
|
-
} catch {
|
|
1085
|
-
console.log("\n\u274C Revoke failed. Check AGENT_WALLET_KEY");
|
|
1086
|
-
process.exit(1);
|
|
1087
|
-
}
|
|
1088
2146
|
}
|
|
1089
2147
|
async function ownerAdd(address) {
|
|
1090
|
-
const { join } = await import("path");
|
|
1091
|
-
const { existsSync:
|
|
2148
|
+
const { join: join5 } = await import("path");
|
|
2149
|
+
const { existsSync: existsSync7, mkdirSync: mkdirSync2, writeFileSync: writeFileSync2, readFileSync: readFileSync2 } = await import("fs");
|
|
1092
2150
|
if (!address) {
|
|
1093
2151
|
console.error("Usage: hybrid owner add <address>");
|
|
1094
2152
|
process.exit(1);
|
|
1095
2153
|
}
|
|
1096
2154
|
const projectDir = projectRoot;
|
|
1097
|
-
const aclPath =
|
|
2155
|
+
const aclPath = join5(projectDir, "credentials", "allowFrom.json");
|
|
1098
2156
|
const normalized = address.toLowerCase().trim();
|
|
1099
|
-
|
|
2157
|
+
mkdirSync2(join5(projectDir, "credentials"), { recursive: true });
|
|
1100
2158
|
let acl = {
|
|
1101
2159
|
version: 1,
|
|
1102
2160
|
allowFrom: []
|
|
1103
2161
|
};
|
|
1104
|
-
if (
|
|
2162
|
+
if (existsSync7(aclPath)) {
|
|
1105
2163
|
try {
|
|
1106
|
-
acl = JSON.parse(
|
|
2164
|
+
acl = JSON.parse(readFileSync2(aclPath, "utf-8"));
|
|
1107
2165
|
} catch {
|
|
1108
2166
|
}
|
|
1109
2167
|
}
|
|
1110
2168
|
if (!acl.allowFrom.includes(normalized)) {
|
|
1111
2169
|
acl.allowFrom.push(normalized);
|
|
1112
|
-
|
|
2170
|
+
writeFileSync2(aclPath, JSON.stringify(acl, null, " "));
|
|
1113
2171
|
console.log(`
|
|
1114
2172
|
\u2705 Added owner: ${normalized}`);
|
|
1115
2173
|
} else {
|
|
@@ -1121,20 +2179,20 @@ async function ownerAdd(address) {
|
|
|
1121
2179
|
for (const owner of acl.allowFrom) console.log(` - ${owner}`);
|
|
1122
2180
|
}
|
|
1123
2181
|
async function ownerRemove(address) {
|
|
1124
|
-
const { join } = await import("path");
|
|
1125
|
-
const { existsSync:
|
|
2182
|
+
const { join: join5 } = await import("path");
|
|
2183
|
+
const { existsSync: existsSync7, writeFileSync: writeFileSync2, readFileSync: readFileSync2 } = await import("fs");
|
|
1126
2184
|
if (!address) {
|
|
1127
2185
|
console.error("Usage: hybrid owner remove <address>");
|
|
1128
2186
|
process.exit(1);
|
|
1129
2187
|
}
|
|
1130
2188
|
const projectDir = projectRoot;
|
|
1131
|
-
const aclPath =
|
|
1132
|
-
if (!
|
|
1133
|
-
console.error("No ACL file. Run 'hybrid
|
|
2189
|
+
const aclPath = join5(projectDir, "credentials", "allowFrom.json");
|
|
2190
|
+
if (!existsSync7(aclPath)) {
|
|
2191
|
+
console.error("No ACL file. Run 'hybrid init' first.");
|
|
1134
2192
|
process.exit(1);
|
|
1135
2193
|
}
|
|
1136
2194
|
const acl = JSON.parse(
|
|
1137
|
-
|
|
2195
|
+
readFileSync2(aclPath, "utf-8")
|
|
1138
2196
|
);
|
|
1139
2197
|
const normalized = address.toLowerCase().trim();
|
|
1140
2198
|
const index = acl.allowFrom.indexOf(normalized);
|
|
@@ -1144,7 +2202,7 @@ async function ownerRemove(address) {
|
|
|
1144
2202
|
process.exit(1);
|
|
1145
2203
|
}
|
|
1146
2204
|
acl.allowFrom.splice(index, 1);
|
|
1147
|
-
|
|
2205
|
+
writeFileSync2(aclPath, JSON.stringify(acl, null, " "));
|
|
1148
2206
|
console.log(`
|
|
1149
2207
|
\u2705 Removed owner: ${normalized}`);
|
|
1150
2208
|
if (acl.allowFrom.length > 0) {
|
|
@@ -1156,17 +2214,17 @@ async function ownerRemove(address) {
|
|
|
1156
2214
|
}
|
|
1157
2215
|
}
|
|
1158
2216
|
async function ownerList() {
|
|
1159
|
-
const { join } = await import("path");
|
|
1160
|
-
const { existsSync:
|
|
2217
|
+
const { join: join5 } = await import("path");
|
|
2218
|
+
const { existsSync: existsSync7, readFileSync: readFileSync2 } = await import("fs");
|
|
1161
2219
|
const projectDir = projectRoot;
|
|
1162
|
-
const aclPath =
|
|
1163
|
-
if (!
|
|
1164
|
-
console.log("\n\u26A0\uFE0F No ACL file. Run 'hybrid
|
|
2220
|
+
const aclPath = join5(projectDir, "credentials", "allowFrom.json");
|
|
2221
|
+
if (!existsSync7(aclPath)) {
|
|
2222
|
+
console.log("\n\u26A0\uFE0F No ACL file. Run 'hybrid init' first.");
|
|
1165
2223
|
console.log("\n Agent is open to all users.");
|
|
1166
2224
|
return;
|
|
1167
2225
|
}
|
|
1168
2226
|
const acl = JSON.parse(
|
|
1169
|
-
|
|
2227
|
+
readFileSync2(aclPath, "utf-8")
|
|
1170
2228
|
);
|
|
1171
2229
|
if (acl.allowFrom.length === 0) {
|
|
1172
2230
|
console.log("\n\u{1F4CB} No owners. Agent is open to all users.");
|