hybrid 2.0.1 → 2.1.0

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