hybrid 2.0.0 → 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/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
- // ../../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
39
- var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.src || new URL("main.js", document.baseURI).href;
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, import_node_fs.existsSync)((0, import_node_path.resolve)(current, marker))) {
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, import_node_path.resolve)(current, "..");
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, import_node_path.resolve)(projectRoot, envFile);
70
- if ((0, import_node_fs.existsSync)(path)) {
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, import_node_path.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
80
- var packageDir = (0, import_node_path.resolve)(__dirname, "..");
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") return build(args[1]);
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 deploy(args[1], args.includes("--keygen"));
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> Initialize a new agent");
152
- console.log(" dev Start development server");
153
- console.log(" build [--target] Build for deployment (fly, railway)");
154
- console.log(" start Run built agent");
155
- console.log(" deploy [platform] Deploy (fly, railway)");
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("Wallet:");
158
- console.log(" keygen [prefix] Generate a new wallet");
159
- console.log(" register Register wallet on XMTP");
160
- console.log(" revoke <inboxId> Revoke installations");
161
- console.log(" revoke-all Revoke all installations");
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: existsSync2,
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 { createInterface } = await import("readline");
275
- const templateDir = (0, import_node_path.resolve)(packageDir, "templates", "agent");
276
- const targetDir = (0, import_node_path.resolve)(process.cwd(), name);
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
- cpSync(templateDir, targetDir, { recursive: true });
285
- const pkgPath = (0, import_node_path.resolve)(targetDir, "package.json");
286
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
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
- writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
289
- const skillsDir = (0, import_node_path.resolve)(packageDir, "skills");
290
- const targetSkillsDir = (0, import_node_path.resolve)(targetDir, "skills");
291
- if (existsSync2(skillsDir)) {
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
- mkdirSync(targetSkillsDir, { recursive: true });
1675
+ mkdirSync2(targetSkillsDir, { recursive: true });
295
1676
  for (const skill of coreSkills) {
296
- cpSync((0, import_node_path.resolve)(skillsDir, skill), (0, import_node_path.resolve)(targetSkillsDir, skill), {
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
- writeFileSync(
307
- (0, import_node_path.resolve)(targetDir, "skills-lock.json"),
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, import_node_path.resolve)(targetDir, ".env.example");
312
- const envPath = (0, import_node_path.resolve)(targetDir, ".env");
313
- if (existsSync2(envExamplePath)) {
314
- cpSync(envExamplePath, envPath);
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
- const normalized = ownerAddress.toLowerCase();
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: ${normalized}`);
1744
+ \u2705 Added owner: ${ownerAddress.toLowerCase()}`);
378
1745
  }
379
- let envContent = readFileSync(envPath, "utf-8");
380
- envContent = envContent.replace(
381
- /AGENT_WALLET_KEY=.*/,
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
- writeFileSync(envPath, envContent);
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: existsSync2,
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, import_node_path.resolve)(projectDir, "dist");
434
- const buildTarget = target || "fly";
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 (existsSync2(distDir)) {
437
- rmSync(distDir, { recursive: true, force: true });
1803
+ if (existsSync7(distDir)) {
1804
+ rmSync2(distDir, { recursive: true, force: true });
438
1805
  }
439
- mkdirSync(distDir, { recursive: true });
440
- mkdirSync((0, import_node_path.resolve)(distDir, "server"), { recursive: true });
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, import_node_path.resolve)(packageDir, "dist");
443
- const files = ["server/index.cjs", "xmtp.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, import_node_path.resolve)(agentDistDir, file);
446
- if (existsSync2(src)) {
447
- cpSync(src, (0, import_node_path.resolve)(distDir, file));
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, import_node_path.resolve)(projectDir, file);
464
- if (existsSync2(src)) {
465
- cpSync(src, (0, import_node_path.resolve)(distDir, file));
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, import_node_path.resolve)(projectDir, "hybrid.config.ts");
470
- const openclawConfig = (0, import_node_path.resolve)(projectDir, "openclaw.json");
471
- const agentConfig = (0, import_node_path.resolve)(projectDir, "agent.ts");
472
- if (existsSync2(hybridConfig)) {
473
- cpSync(hybridConfig, (0, import_node_path.resolve)(distDir, "hybrid.config.ts"));
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 (existsSync2(openclawConfig)) {
476
- const content = readFileSync(openclawConfig, "utf-8");
477
- writeFileSync(
478
- (0, import_node_path.resolve)(projectDir, "hybrid.config.ts"),
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 (existsSync2(agentConfig)) {
484
- cpSync(agentConfig, (0, import_node_path.resolve)(distDir, "agent.ts"));
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, import_node_path.resolve)(projectDir, "skills");
488
- if (existsSync2(skillsDir)) {
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, import_node_path.resolve)(projectDir, "credentials");
495
- if (existsSync2(credsDir)) {
496
- mkdirSync((0, import_node_path.resolve)(distDir, "credentials"), { recursive: true });
497
- cpSync(credsDir, (0, import_node_path.resolve)(distDir, "credentials"), { recursive: true });
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
- writeFileSync(
520
- (0, import_node_path.resolve)(distDir, "package.json"),
1882
+ writeFileSync2(
1883
+ (0, import_node_path6.resolve)(distDir, "package.json"),
521
1884
  JSON.stringify(deployPkg, null, 2)
522
1885
  );
523
- writeFileSync((0, import_node_path.resolve)(distDir, "Dockerfile"), generateDockerfile(buildTarget));
524
- if (buildTarget === "fly") {
525
- const projectFlyToml = (0, import_node_path.resolve)(projectDir, "fly.toml");
526
- if (existsSync2(projectFlyToml)) {
527
- cpSync(projectFlyToml, (0, import_node_path.resolve)(distDir, "fly.toml"));
528
- } else {
529
- writeFileSync((0, import_node_path.resolve)(distDir, "fly.toml"), generateFlyToml());
530
- }
531
- }
532
- writeFileSync(
533
- (0, import_node_path.resolve)(distDir, "start.sh"),
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(target) {
545
- if (target === "fly" || target === "railway") {
546
- return `FROM node:20
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
- # Create data directories and set ownership
574
- RUN mkdir -p /app/data/xmtp && \\
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 ["sh", "start.sh"]
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 walletKey = process.env.AGENT_WALLET_KEY || process.env.WALLET_KEY;
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
- `npx concurrently --names "server,xmtp" --prefix-colors "cyan,magenta" "node ${agentServer}" "node ${agentXmtp}"`,
686
- {
687
- cwd: projectDir,
688
- stdio: "inherit",
689
- env: {
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: existsSync2 } = await import("fs");
1971
+ const { spawn: spawn4 } = await import("child_process");
1972
+ const { existsSync: existsSync7 } = await import("fs");
703
1973
  const projectDir = projectRoot;
704
- const distDir = (0, import_node_path.resolve)(projectDir, "dist");
705
- if (!existsSync2((0, import_node_path.resolve)(distDir, "server", "simple.cjs"))) {
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 = spawn("node", [(0, import_node_path.resolve)(distDir, "server", "simple.cjs")], {
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
- async function deploy(platform, keygen2 = false) {
732
- const { spawn, execSync } = await import("child_process");
733
- const { existsSync: existsSync2, readFileSync, writeFileSync } = await import("fs");
734
- const prompts = (await import("prompts")).default;
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
- if (!deployPlatform) {
754
- const choice = await prompts({
755
- type: "select",
756
- name: "platform",
757
- message: "Where do you want to deploy?",
758
- choices: [
759
- { title: "Fly.io", value: "fly" },
760
- { title: "Railway", value: "railway" }
761
- ],
762
- initial: 0
763
- });
764
- if (!choice.platform) {
765
- console.log("\n Cancelled.\n");
766
- process.exit(0);
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 flyTomlPath = (0, import_node_path.resolve)(distDir, "fly.toml");
787
- const projectFlyToml = (0, import_node_path.resolve)(projectDir, "fly.toml");
788
- const projectName = (0, import_node_path.basename)(projectDir);
789
- let appName = projectName;
790
- if (existsSync2(projectFlyToml)) {
791
- const flyToml = readFileSync(projectFlyToml, "utf-8");
792
- const match = flyToml.match(/^app\s*=\s*["']([^"']+)["']/m);
793
- if (match) appName = match[1];
794
- } else {
795
- const result = await prompts({
796
- type: "text",
797
- name: "appName",
798
- message: "App name:",
799
- initial: projectName
800
- });
801
- if (result.appName) appName = result.appName;
802
- }
803
- let walletKey = process.env.AGENT_WALLET_KEY || process.env.WALLET_KEY;
804
- if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(appName)) {
805
- console.error(`
806
- \u274C Invalid app name: ${appName}`);
807
- console.error("App name must start with a letter/digit and contain only letters, digits, hyphens, and underscores.");
808
- process.exit(1);
809
- }
810
- const { execFileSync } = await import("child_process");
811
- let appExists = false;
812
- try {
813
- execFileSync("fly", ["status", "--app", appName], { stdio: "pipe" });
814
- appExists = true;
815
- } catch {
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
- let openRouterKey = process.env.OPENROUTER_API_KEY;
818
- if (!appExists) {
819
- if (!walletKey || keygen2) {
820
- if (keygen2) {
821
- console.log("\n\u{1F510} Generating new wallet key...");
822
- } else {
823
- console.log("\n\u{1F510} No AGENT_WALLET_KEY found. Generating new wallet...");
824
- }
825
- walletKey = await generateVanityWallet(
826
- "",
827
- privateKeyToAccount,
828
- randomBytes
829
- );
830
- const account = privateKeyToAccount(walletKey);
831
- console.log(`\u2705 Wallet: ${account.address}
832
- `);
833
- }
834
- if (!openRouterKey) {
835
- console.log("\u{1F511} No OPENROUTER_API_KEY found.");
836
- while (true) {
837
- const result = await prompts({
838
- type: "password",
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
- const aclPath = (0, import_node_path.resolve)(projectDir, "credentials", "xmtp-allowFrom.json");
851
- if (!existsSync2(aclPath)) {
852
- const result = await prompts({
853
- type: "text",
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
- await build(deployPlatform);
875
- if (deployPlatform === "fly") {
876
- console.log("\n\u{1F680} Deploying to Fly.io...");
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
- await new Promise((resolve2, reject) => {
897
- const deploy2 = spawn("fly", ["deploy", "--config", "fly.toml"], {
898
- cwd: distDir,
899
- stdio: "inherit"
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
- if (openRouterKey) {
926
- console.log("\n\u{1F511} Setting OpenRouter key as secret...");
927
- try {
928
- execFileSync(
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
- console.log("\n\u2705 Deployed!");
941
- console.log(` Dashboard: https://fly.io/apps/${appName}`);
942
- if (!existsSync2(projectFlyToml)) {
943
- const { cpSync } = await import("fs");
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
- async function keygen(prefix) {
959
- if (prefix === "-h" || prefix === "--help") {
960
- console.log("\nUsage: hybrid keygen [prefix]");
961
- console.log("\nGenerate a new wallet key.");
962
- console.log(
963
- " prefix Optional hex prefix for vanity address (max 6 chars)\n"
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
- const account = privateKeyToAccount(walletKey);
986
- console.log(`
987
- \u2705 Wallet generated!`);
988
- console.log(` Address: ${account.address}`);
989
- console.log(` Private key: ${walletKey}
990
- `);
991
- console.log("\u26A0\uFE0F Save this key securely!");
992
- console.log(` Add to .env: AGENT_WALLET_KEY=${walletKey}
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: existsSync2, mkdirSync, writeFileSync, readFileSync } = await import("fs");
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 = join(projectDir, "credentials", "xmtp-allowFrom.json");
2155
+ const aclPath = join5(projectDir, "credentials", "allowFrom.json");
1098
2156
  const normalized = address.toLowerCase().trim();
1099
- mkdirSync(join(projectDir, "credentials"), { recursive: true });
2157
+ mkdirSync2(join5(projectDir, "credentials"), { recursive: true });
1100
2158
  let acl = {
1101
2159
  version: 1,
1102
2160
  allowFrom: []
1103
2161
  };
1104
- if (existsSync2(aclPath)) {
2162
+ if (existsSync7(aclPath)) {
1105
2163
  try {
1106
- acl = JSON.parse(readFileSync(aclPath, "utf-8"));
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
- writeFileSync(aclPath, JSON.stringify(acl, null, " "));
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: existsSync2, writeFileSync, readFileSync } = await import("fs");
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 = join(projectDir, "credentials", "xmtp-allowFrom.json");
1132
- if (!existsSync2(aclPath)) {
1133
- console.error("No ACL file. Run 'hybrid register' first.");
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
- readFileSync(aclPath, "utf-8")
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
- writeFileSync(aclPath, JSON.stringify(acl, null, " "));
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: existsSync2, readFileSync } = await import("fs");
2217
+ const { join: join5 } = await import("path");
2218
+ const { existsSync: existsSync7, readFileSync: readFileSync2 } = await import("fs");
1161
2219
  const projectDir = projectRoot;
1162
- const aclPath = join(projectDir, "credentials", "xmtp-allowFrom.json");
1163
- if (!existsSync2(aclPath)) {
1164
- console.log("\n\u26A0\uFE0F No ACL file. Run 'hybrid register' first.");
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
- readFileSync(aclPath, "utf-8")
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.");