@ghalex/kvbox 0.0.1 → 0.0.3

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/kvbox.js CHANGED
@@ -1,76 +1,149 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // src/cli/output.ts
3
+ // src/output.ts
4
4
  import pc from "picocolors";
5
5
  var color = pc.createColors(process.stdout.isTTY === true && !process.env["NO_COLOR"]);
6
6
  function json(value) {
7
7
  process.stdout.write(JSON.stringify(value, null, 2) + "\n");
8
8
  }
9
+ function table(rows, columns) {
10
+ if (rows.length === 0) return;
11
+ const cols = columns ?? Object.keys(rows[0]);
12
+ const cells = rows.map((r) => cols.map((c) => String(r[c] ?? "")));
13
+ const widths = cols.map((c, i) => Math.max(c.length, ...cells.map((r) => r[i].length)));
14
+ const line = (parts) => parts.map((p, i) => p.padEnd(widths[i])).join(" ").trimEnd();
15
+ process.stdout.write(color.dim(line(cols.map((c) => c.toUpperCase()))) + "\n");
16
+ for (const row of cells) process.stdout.write(line(row) + "\n");
17
+ }
9
18
  function info(msg) {
10
19
  process.stderr.write(msg + "\n");
11
20
  }
21
+ function warn(msg) {
22
+ process.stderr.write(color.yellow(msg) + "\n");
23
+ }
12
24
  function notImplemented(command) {
13
25
  process.stderr.write(color.yellow(`kvbox ${command}: not implemented yet
14
26
  `));
15
27
  process.exit(2);
16
28
  }
29
+ async function confirm(question, yes) {
30
+ if (yes) return true;
31
+ if (!process.stdin.isTTY) {
32
+ warn("refusing without -y when stdin is not a terminal");
33
+ return false;
34
+ }
35
+ const { createInterface } = await import("readline/promises");
36
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
37
+ try {
38
+ const answer = (await rl.question(`${question} [y/N] `)).trim().toLowerCase();
39
+ return answer === "y" || answer === "yes";
40
+ } finally {
41
+ rl.close();
42
+ }
43
+ }
17
44
 
18
- // src/cli/program.ts
45
+ // src/program.ts
19
46
  import { Command as Command14 } from "commander";
20
47
 
21
- // src/shared/version.ts
48
+ // ../shared/src/version.ts
22
49
  import { createRequire } from "module";
23
- var VERSION = true ? "0.0.1" : fromPackageJson();
50
+ function versionOf(srcFileUrl, injected) {
51
+ if (typeof injected === "string") return injected;
52
+ try {
53
+ const require2 = createRequire(srcFileUrl);
54
+ return require2("../package.json").version;
55
+ } catch {
56
+ return "0.0.0-dev";
57
+ }
58
+ }
59
+
60
+ // src/version.ts
61
+ var VERSION = versionOf(import.meta.url, true ? "0.0.3" : void 0);
24
62
 
25
- // src/cli/commands/machine/index.ts
63
+ // src/commands/vm/index.ts
26
64
  import { Command as Command5 } from "commander";
27
65
 
28
- // src/cli/commands/machine/connect.ts
66
+ // src/commands/vm/connect.ts
67
+ import { spawnSync } from "child_process";
29
68
  import { Command } from "commander";
30
- var connectCommand = new Command("connect").description("open an interactive shell in a box over SSH").argument("<name>", "box name").action(() => notImplemented("machine connect"));
31
69
 
32
- // src/cli/commands/machine/ls.ts
33
- import { Command as Command2 } from "commander";
34
- var lsCommand = new Command2("ls").description("list boxes with status and IP").option("--json", "print as JSON").action(() => notImplemented("machine ls"));
70
+ // ../shared/src/errors.ts
71
+ var KvboxError = class extends Error {
72
+ constructor(message, exitCode = 1) {
73
+ super(message);
74
+ this.exitCode = exitCode;
75
+ this.name = "KvboxError";
76
+ }
77
+ exitCode;
78
+ };
35
79
 
36
- // src/cli/commands/machine/new.ts
37
- import { Command as Command3, Option } from "commander";
80
+ // src/client.ts
81
+ import { spawn } from "child_process";
82
+ import { openSync } from "fs";
83
+ import { mkdir, stat } from "fs/promises";
84
+ import { setTimeout as sleep } from "timers/promises";
85
+ import { Agent, request } from "undici";
86
+ import { z as z2 } from "zod";
87
+
88
+ // ../shared/src/api.ts
89
+ import { z } from "zod";
38
90
 
39
- // src/shared/sizes.ts
91
+ // ../shared/src/sizes.ts
40
92
  var SIZES = {
41
93
  s: { vcpu: 2, memMib: 4096 },
42
94
  m: { vcpu: 4, memMib: 8192 },
43
95
  l: { vcpu: 8, memMib: 16384 }
44
96
  };
45
97
  var SIZE_NAMES = Object.keys(SIZES);
46
- var DEFAULT_SIZE = "s";
47
-
48
- // src/cli/commands/machine/new.ts
49
- var newCommand = new Command3("new").description("boot a new box from the base image or a snapshot").argument("[name]", "box name (generated when omitted)").addOption(new Option("-s, --size <size>", "box size").choices(SIZE_NAMES).default(DEFAULT_SIZE)).option("-f, --from <snapshot>", "start from a snapshot instead of the base image").option("--force", "skip the capacity guard").option("--json", "print the box as JSON").action(() => notImplemented("machine new"));
50
-
51
- // src/cli/commands/machine/rm.ts
52
- import { Command as Command4 } from "commander";
53
- var rmCommand = new Command4("rm").description("destroy a box").argument("<name>", "box name").option("-y, --yes", "do not ask for confirmation").action(() => notImplemented("machine rm"));
54
-
55
- // src/cli/commands/machine/index.ts
56
- var machineCommand = new Command5("machine").alias("m").description("manage boxes").addCommand(newCommand).addCommand(lsCommand).addCommand(connectCommand).addCommand(rmCommand);
57
- var machineAliases = [newCommand, lsCommand, connectCommand, rmCommand];
58
-
59
- // src/cli/commands/server/index.ts
60
- import { Command as Command9 } from "commander";
61
98
 
62
- // src/cli/commands/server/start.ts
63
- import { Command as Command6 } from "commander";
64
-
65
- // src/server/listen.ts
66
- import { rm, writeFile as writeFile3 } from "fs/promises";
67
- import { createAdaptorServer } from "@hono/node-server";
68
-
69
- // src/core/state.ts
70
- import { mkdir, readFile, readdir, writeFile } from "fs/promises";
71
- import { z } from "zod";
99
+ // ../shared/src/api.ts
100
+ var SizeSchema = z.enum(SIZE_NAMES);
101
+ var MachineSchema = z.object({
102
+ name: z.string(),
103
+ ip: z.string(),
104
+ mac: z.string(),
105
+ pid: z.number().int().nullable(),
106
+ vcpu: z.number().int(),
107
+ memMib: z.number().int(),
108
+ createdAt: z.string(),
109
+ source: z.string(),
110
+ // "base" or "snapshot:<name>"
111
+ status: z.enum(["running", "stopped"])
112
+ });
113
+ var CreateMachineRequest = z.object({
114
+ name: z.string().optional(),
115
+ size: SizeSchema.optional(),
116
+ from: z.string().optional(),
117
+ force: z.boolean().optional()
118
+ });
119
+ var SnapshotSchema = z.object({
120
+ name: z.string(),
121
+ source: z.string(),
122
+ vcpu: z.number().int(),
123
+ memMib: z.number().int(),
124
+ createdAt: z.string()
125
+ });
126
+ var CreateSnapshotRequest = z.object({
127
+ box: z.string(),
128
+ name: z.string()
129
+ });
130
+ var CapacitySchema = z.object({
131
+ hostMemMib: z.number().int(),
132
+ hostCores: z.number().int(),
133
+ reservedMemMib: z.number().int(),
134
+ reservedVcpu: z.number().int(),
135
+ running: z.number().int()
136
+ });
137
+ var HealthSchema = z.object({
138
+ ok: z.literal(true),
139
+ version: z.string(),
140
+ pid: z.number().int()
141
+ });
142
+ var ApiErrorSchema = z.object({
143
+ error: z.string()
144
+ });
72
145
 
73
- // src/shared/paths.ts
146
+ // ../shared/src/paths.ts
74
147
  import { homedir } from "os";
75
148
  import { join } from "path";
76
149
  function kvboxHome() {
@@ -82,6 +155,7 @@ function paths(home = kvboxHome()) {
82
155
  config: join(home, "config.json"),
83
156
  serverSock: join(home, "server.sock"),
84
157
  serverPid: join(home, "server.pid"),
158
+ serverLog: join(home, "server.log"),
85
159
  hosts: join(home, "hosts.json"),
86
160
  ssh: join(home, "ssh"),
87
161
  sshKey: join(home, "ssh", "id_ed25519"),
@@ -109,261 +183,7 @@ function paths(home = kvboxHome()) {
109
183
  };
110
184
  }
111
185
 
112
- // src/core/state.ts
113
- var MachineMetaSchema = z.object({
114
- name: z.string(),
115
- ip: z.string(),
116
- mac: z.string(),
117
- pid: z.number().int(),
118
- vcpu: z.number().int(),
119
- memMib: z.number().int(),
120
- createdAt: z.string(),
121
- source: z.string()
122
- });
123
- var SnapshotMetaSchema = z.object({
124
- name: z.string(),
125
- source: z.string(),
126
- vcpu: z.number().int(),
127
- memMib: z.number().int(),
128
- createdAt: z.string()
129
- });
130
- async function ensureLayout(p = paths()) {
131
- for (const dir of [p.home, p.ssh, p.bin, p.assets, p.machines, p.snapshots]) {
132
- await mkdir(dir, { recursive: true, mode: 448 });
133
- }
134
- return p;
135
- }
136
-
137
- // src/core/config.ts
138
- import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
139
- import { z as z2 } from "zod";
140
- var ConfigSchema = z2.object({
141
- kernel: z2.string(),
142
- rootfs: z2.string(),
143
- firecracker: z2.string(),
144
- bridge: z2.string().default("zbr0"),
145
- subnet: z2.string().default("172.30.0.0/24"),
146
- gateway: z2.string().default("172.30.0.1"),
147
- defaultSize: z2.enum(SIZE_NAMES).default(DEFAULT_SIZE)
148
- });
149
- function defaultConfig(p = paths()) {
150
- return ConfigSchema.parse({
151
- kernel: p.kernel,
152
- rootfs: p.rootfs,
153
- firecracker: p.firecracker
154
- });
155
- }
156
- async function loadConfig(p = paths()) {
157
- try {
158
- const raw = await readFile2(p.config, "utf8");
159
- return ConfigSchema.parse(JSON.parse(raw));
160
- } catch (err) {
161
- if (err.code === "ENOENT") {
162
- const cfg = defaultConfig(p);
163
- await writeFile2(p.config, JSON.stringify(cfg, null, 2) + "\n");
164
- return cfg;
165
- }
166
- throw err;
167
- }
168
- }
169
-
170
- // src/shared/errors.ts
171
- var KvboxError = class extends Error {
172
- constructor(message, exitCode = 1) {
173
- super(message);
174
- this.exitCode = exitCode;
175
- this.name = "KvboxError";
176
- }
177
- exitCode;
178
- };
179
- var NotImplementedError = class extends KvboxError {
180
- constructor(what) {
181
- super(`${what}: not implemented yet`, 2);
182
- this.name = "NotImplementedError";
183
- }
184
- };
185
-
186
- // src/server/app.ts
187
- import { Hono as Hono4 } from "hono";
188
-
189
- // src/server/routes/capacity.ts
190
- import { Hono } from "hono";
191
-
192
- // src/core/capacity.ts
193
- async function capacity() {
194
- throw new NotImplementedError("capacity.capacity");
195
- }
196
-
197
- // src/server/routes/capacity.ts
198
- function capacityRoutes() {
199
- const r = new Hono();
200
- r.get("/", async (c) => c.json(await capacity()));
201
- return r;
202
- }
203
-
204
- // src/server/routes/machines.ts
205
- import { Hono as Hono2 } from "hono";
206
-
207
- // src/shared/api.ts
208
- import { z as z3 } from "zod";
209
- var SizeSchema = z3.enum(SIZE_NAMES);
210
- var MachineSchema = z3.object({
211
- name: z3.string(),
212
- ip: z3.string(),
213
- mac: z3.string(),
214
- pid: z3.number().int().nullable(),
215
- vcpu: z3.number().int(),
216
- memMib: z3.number().int(),
217
- createdAt: z3.string(),
218
- source: z3.string(),
219
- // "base" or "snapshot:<name>"
220
- status: z3.enum(["running", "stopped"])
221
- });
222
- var CreateMachineRequest = z3.object({
223
- name: z3.string().optional(),
224
- size: SizeSchema.optional(),
225
- from: z3.string().optional(),
226
- force: z3.boolean().optional()
227
- });
228
- var SnapshotSchema = z3.object({
229
- name: z3.string(),
230
- source: z3.string(),
231
- vcpu: z3.number().int(),
232
- memMib: z3.number().int(),
233
- createdAt: z3.string()
234
- });
235
- var CreateSnapshotRequest = z3.object({
236
- box: z3.string(),
237
- name: z3.string()
238
- });
239
- var CapacitySchema = z3.object({
240
- hostMemMib: z3.number().int(),
241
- hostCores: z3.number().int(),
242
- reservedMemMib: z3.number().int(),
243
- reservedVcpu: z3.number().int(),
244
- running: z3.number().int()
245
- });
246
- var HealthSchema = z3.object({
247
- ok: z3.literal(true),
248
- version: z3.string(),
249
- pid: z3.number().int()
250
- });
251
- var ApiErrorSchema = z3.object({
252
- error: z3.string()
253
- });
254
-
255
- // src/core/machines.ts
256
- async function listMachines() {
257
- throw new NotImplementedError("machines.listMachines");
258
- }
259
- async function createMachine(_req) {
260
- throw new NotImplementedError("machines.createMachine");
261
- }
262
- async function destroyMachine(_name) {
263
- throw new NotImplementedError("machines.destroyMachine");
264
- }
265
-
266
- // src/server/routes/machines.ts
267
- function machineRoutes() {
268
- const r = new Hono2();
269
- r.get("/", async (c) => c.json(await listMachines()));
270
- r.post("/", async (c) => {
271
- const body = CreateMachineRequest.parse(await c.req.json().catch(() => ({})));
272
- return c.json(await createMachine(body), 201);
273
- });
274
- r.delete("/:name", async (c) => {
275
- await destroyMachine(c.req.param("name"));
276
- return c.body(null, 204);
277
- });
278
- return r;
279
- }
280
-
281
- // src/server/routes/snapshots.ts
282
- import { Hono as Hono3 } from "hono";
283
-
284
- // src/core/snapshots.ts
285
- async function listSnapshots() {
286
- throw new NotImplementedError("snapshots.listSnapshots");
287
- }
288
- async function saveSnapshot(_req) {
289
- throw new NotImplementedError("snapshots.saveSnapshot");
290
- }
291
- async function deleteSnapshot(_name) {
292
- throw new NotImplementedError("snapshots.deleteSnapshot");
293
- }
294
-
295
- // src/server/routes/snapshots.ts
296
- function snapshotRoutes() {
297
- const r = new Hono3();
298
- r.get("/", async (c) => c.json(await listSnapshots()));
299
- r.post("/", async (c) => {
300
- const body = CreateSnapshotRequest.parse(await c.req.json());
301
- return c.json(await saveSnapshot(body), 201);
302
- });
303
- r.delete("/:name", async (c) => {
304
- await deleteSnapshot(c.req.param("name"));
305
- return c.body(null, 204);
306
- });
307
- return r;
308
- }
309
-
310
- // src/server/app.ts
311
- function createApp() {
312
- const app = new Hono4();
313
- app.get("/health", (c) => c.json({ ok: true, version: VERSION, pid: process.pid }));
314
- app.route("/machines", machineRoutes());
315
- app.route("/snapshots", snapshotRoutes());
316
- app.route("/capacity", capacityRoutes());
317
- app.notFound((c) => c.json({ error: `no route: ${c.req.method} ${c.req.path}` }, 404));
318
- app.onError((err, c) => {
319
- if (err instanceof KvboxError) {
320
- return c.json({ error: err.message }, err.exitCode === 2 ? 501 : 400);
321
- }
322
- console.error(err);
323
- return c.json({ error: err.message }, 500);
324
- });
325
- return app;
326
- }
327
-
328
- // src/server/listen.ts
329
- async function listen(opts = {}) {
330
- const p = await ensureLayout(paths());
331
- await loadConfig(p);
332
- const socketPath = opts.socketPath ?? p.serverSock;
333
- if (Buffer.byteLength(socketPath) > 100) {
334
- throw new KvboxError(`socket path too long for a Unix socket (${socketPath}); set KVBOX_HOME to a shorter path`);
335
- }
336
- await rm(socketPath, { force: true });
337
- const server = createAdaptorServer({ fetch: createApp().fetch });
338
- await new Promise((resolve, reject) => {
339
- server.once("error", reject);
340
- server.listen(socketPath, () => resolve());
341
- });
342
- await writeFile3(p.serverPid, `${process.pid}
343
- `);
344
- const shutdown = async () => {
345
- server.close();
346
- await rm(socketPath, { force: true });
347
- await rm(p.serverPid, { force: true });
348
- process.exit(0);
349
- };
350
- process.once("SIGINT", () => void shutdown());
351
- process.once("SIGTERM", () => void shutdown());
352
- return { socketPath, server };
353
- }
354
-
355
- // src/cli/commands/server/start.ts
356
- var startCommand = new Command6("start").description("run the kvbox server (owns Firecracker and ~/.kvbox)").option("--foreground", "stay attached to the terminal (default for now)").action(async () => {
357
- const { socketPath } = await listen();
358
- info(`kvbox server listening on ${socketPath} (pid ${process.pid})`);
359
- });
360
-
361
- // src/cli/commands/server/status.ts
362
- import { Command as Command7 } from "commander";
363
-
364
- // src/cli/client.ts
365
- import { Agent, request } from "undici";
366
- import { z as z4 } from "zod";
186
+ // src/client.ts
367
187
  function resolveTarget(hostFlag) {
368
188
  if (hostFlag) {
369
189
  throw new KvboxError(`--host ${hostFlag}: remote hosts arrive in M2`);
@@ -371,8 +191,9 @@ function resolveTarget(hostFlag) {
371
191
  return { kind: "local", socketPath: paths().serverSock };
372
192
  }
373
193
  var ServerClient = class {
374
- constructor(target) {
194
+ constructor(target, opts = {}) {
375
195
  this.target = target;
196
+ this.autoStart = opts.autoStart ?? true;
376
197
  if (target.kind === "local") {
377
198
  this.dispatcher = new Agent({ connect: { socketPath: target.socketPath } });
378
199
  this.base = "http://kvbox";
@@ -384,6 +205,8 @@ var ServerClient = class {
384
205
  target;
385
206
  dispatcher;
386
207
  base;
208
+ autoStart;
209
+ startedOnce = false;
387
210
  async call(method, path, schema, body) {
388
211
  let res;
389
212
  try {
@@ -396,7 +219,12 @@ var ServerClient = class {
396
219
  } catch (err) {
397
220
  const code = err.code ?? err.cause;
398
221
  if (this.target.kind === "local") {
399
- throw new KvboxError(`kvbox server is not running (${String(code)}); start it with: kvbox server start`);
222
+ if (this.autoStart && !this.startedOnce) {
223
+ this.startedOnce = true;
224
+ await startLocalServer(paths(), 5e3);
225
+ return this.call(method, path, schema, body);
226
+ }
227
+ throw new KvboxError(`kvbox-server is not running (${String(code)}); start it with: kvbox server start`);
400
228
  }
401
229
  throw new KvboxError(`cannot reach ${this.target.name}: ${String(code)}`);
402
230
  }
@@ -415,90 +243,254 @@ var ServerClient = class {
415
243
  return this.call("GET", "/capacity", CapacitySchema);
416
244
  }
417
245
  listMachines() {
418
- return this.call("GET", "/machines", z4.array(MachineSchema));
246
+ return this.call("GET", "/machines", z2.array(MachineSchema));
419
247
  }
420
248
  createMachine(req) {
421
249
  return this.call("POST", "/machines", MachineSchema, req);
422
250
  }
423
251
  deleteMachine(name) {
424
- return this.call("DELETE", `/machines/${encodeURIComponent(name)}`, z4.undefined());
252
+ return this.call("DELETE", `/machines/${encodeURIComponent(name)}`, z2.undefined());
425
253
  }
426
254
  listSnapshots() {
427
- return this.call("GET", "/snapshots", z4.array(SnapshotSchema));
255
+ return this.call("GET", "/snapshots", z2.array(SnapshotSchema));
428
256
  }
429
257
  createSnapshot(req) {
430
258
  return this.call("POST", "/snapshots", SnapshotSchema, req);
431
259
  }
432
260
  deleteSnapshot(name) {
433
- return this.call("DELETE", `/snapshots/${encodeURIComponent(name)}`, z4.undefined());
261
+ return this.call("DELETE", `/snapshots/${encodeURIComponent(name)}`, z2.undefined());
434
262
  }
435
263
  async close() {
436
264
  await this.dispatcher.close();
437
265
  }
438
266
  };
439
- function clientFor(opts) {
440
- return new ServerClient(resolveTarget(opts.host));
267
+ function clientFor(opts, client = {}) {
268
+ return new ServerClient(resolveTarget(opts.host), client);
269
+ }
270
+ function sshArgs(ip, keyPath = paths().sshKey) {
271
+ return [
272
+ "-i",
273
+ keyPath,
274
+ "-o",
275
+ "StrictHostKeyChecking=no",
276
+ "-o",
277
+ "UserKnownHostsFile=/dev/null",
278
+ "-o",
279
+ "LogLevel=ERROR",
280
+ "-o",
281
+ "SetEnv=LC_ALL=C.UTF-8",
282
+ `kvbox@${ip}`
283
+ ];
284
+ }
285
+ var NOT_A_HOST_HINT = "kvbox-server is not installed, so this machine is not a kvbox host. Set it up with install-server.sh, or point the CLI at a remote host (M2: kvbox host add).";
286
+ async function startLocalServer(p = paths(), timeoutMs = 5e3) {
287
+ await mkdir(p.home, { recursive: true });
288
+ const log = openSync(p.serverLog, "a");
289
+ const child = spawn("kvbox-server", ["start"], { detached: true, stdio: ["ignore", log, log] });
290
+ const failed = new Promise((_, reject) => {
291
+ child.once("error", (err) => {
292
+ reject(new KvboxError(err.code === "ENOENT" ? NOT_A_HOST_HINT : `could not start kvbox-server: ${err.message}`));
293
+ });
294
+ child.once("exit", (code) => {
295
+ reject(new KvboxError(`kvbox-server exited with code ${String(code)} before listening; see ${p.serverLog}`));
296
+ });
297
+ });
298
+ const ready = (async () => {
299
+ const deadline = Date.now() + timeoutMs;
300
+ while (Date.now() < deadline) {
301
+ if (await stat(p.serverSock).then(() => true, () => false)) {
302
+ const probe = new ServerClient({ kind: "local", socketPath: p.serverSock }, { autoStart: false });
303
+ try {
304
+ await probe.health();
305
+ return;
306
+ } catch {
307
+ } finally {
308
+ await probe.close();
309
+ }
310
+ }
311
+ await sleep(100);
312
+ }
313
+ throw new KvboxError(`kvbox-server did not start within ${timeoutMs / 1e3}s; see ${p.serverLog}`);
314
+ })();
315
+ await Promise.race([ready, failed]);
316
+ child.removeAllListeners("exit");
317
+ child.unref();
318
+ return { pid: child.pid ?? -1, socketPath: p.serverSock };
319
+ }
320
+
321
+ // src/commands/vm/connect.ts
322
+ var connectCommand = new Command("connect").description("open a shell in a box over SSH (or run a command: kvbox connect <name> -- <cmd>)").argument("<name>", "box name").argument("[cmd...]", "command to run instead of a shell").passThroughOptions().action(async (name, cmdArgs, _opts, cmd) => {
323
+ const client = clientFor(cmd.optsWithGlobals());
324
+ let ip;
325
+ try {
326
+ const box = (await client.listMachines()).find((b) => b.name === name);
327
+ if (!box) throw new KvboxError(`box ${name}: not found (kvbox ls)`);
328
+ if (box.status !== "running") throw new KvboxError(`box ${name} is ${box.status}`);
329
+ ip = box.ip;
330
+ } finally {
331
+ await client.close();
332
+ }
333
+ const res = spawnSync("ssh", [...sshArgs(ip), ...cmdArgs], { stdio: "inherit" });
334
+ if (res.error) throw new KvboxError(`could not run ssh: ${res.error.message}`);
335
+ process.exit(res.status ?? 1);
336
+ });
337
+
338
+ // src/commands/vm/ls.ts
339
+ import { Command as Command2 } from "commander";
340
+ var lsCommand = new Command2("ls").description("list boxes with status and IP").option("--json", "print as JSON").action(async (opts, cmd) => {
341
+ const client = clientFor(cmd.optsWithGlobals());
342
+ try {
343
+ const boxes = await client.listMachines();
344
+ if (opts.json) {
345
+ json(boxes);
346
+ return;
347
+ }
348
+ if (boxes.length === 0) {
349
+ info("no boxes yet; create one with: kvbox new");
350
+ return;
351
+ }
352
+ table(
353
+ boxes.map((b) => ({
354
+ name: b.name,
355
+ status: b.status,
356
+ ip: b.ip,
357
+ size: `${b.vcpu} vCPU / ${b.memMib / 1024} GB`,
358
+ source: b.source,
359
+ created: b.createdAt.replace("T", " ").slice(0, 16)
360
+ }))
361
+ );
362
+ } finally {
363
+ await client.close();
364
+ }
365
+ });
366
+
367
+ // src/commands/vm/new.ts
368
+ import { Command as Command3, Option } from "commander";
369
+ var newCommand = new Command3("new").description("boot a new box from the base image or a snapshot").argument("[name]", "box name (box1, box2, \u2026 when omitted)").addOption(new Option("-s, --size <size>", "box size (default: server config, s)").choices(SIZE_NAMES)).option("-f, --from <snapshot>", "start from a snapshot instead of the base image").option("--force", "skip the capacity guard").option("--json", "print the box as JSON").action(async (name, opts, cmd) => {
370
+ const client = clientFor(cmd.optsWithGlobals());
371
+ try {
372
+ const box = await client.createMachine({
373
+ ...name !== void 0 ? { name } : {},
374
+ ...opts.size !== void 0 ? { size: opts.size } : {},
375
+ ...opts.from !== void 0 ? { from: opts.from } : {},
376
+ ...opts.force ? { force: true } : {}
377
+ });
378
+ if (opts.json) {
379
+ json(box);
380
+ return;
381
+ }
382
+ process.stdout.write(`${box.name}
383
+ `);
384
+ info(`${color.green("\u2713")} ${describe(box)}`);
385
+ info(` connect with: kvbox connect ${box.name} (SSH is up a couple of seconds after boot)`);
386
+ } finally {
387
+ await client.close();
388
+ }
389
+ });
390
+ function describe(box) {
391
+ return `box ${color.bold(box.name)} is ${box.status} at ${box.ip}, ${box.vcpu} vCPU / ${box.memMib / 1024} GB`;
441
392
  }
442
393
 
443
- // src/cli/commands/server/status.ts
394
+ // src/commands/vm/rm.ts
395
+ import { Command as Command4 } from "commander";
396
+ var rmCommand = new Command4("rm").description("destroy a box (its disk is deleted; snapshots are kept)").argument("<name>", "box name").option("-y, --yes", "do not ask for confirmation").action(async (name, opts, cmd) => {
397
+ if (!await confirm(`Destroy box ${name} and its disk?`, opts.yes ?? false)) {
398
+ info("cancelled");
399
+ process.exitCode = 1;
400
+ return;
401
+ }
402
+ const client = clientFor(cmd.optsWithGlobals());
403
+ try {
404
+ await client.deleteMachine(name);
405
+ info(`${color.green("\u2713")} box ${name} destroyed`);
406
+ } finally {
407
+ await client.close();
408
+ }
409
+ });
410
+
411
+ // src/commands/vm/index.ts
412
+ var vmCommand = new Command5("vm").enablePositionalOptions().description("manage boxes (each one is a Firecracker microVM)").addCommand(newCommand).addCommand(lsCommand).addCommand(connectCommand).addCommand(rmCommand);
413
+ var vmAliases = [newCommand, lsCommand, connectCommand, rmCommand];
414
+
415
+ // src/commands/server/index.ts
416
+ import { Command as Command9 } from "commander";
417
+
418
+ // src/commands/server/start.ts
419
+ import { Command as Command6 } from "commander";
420
+ var startCommand = new Command6("start").description("start the local kvbox-server in the background").action(async (_opts, cmd) => {
421
+ const client = clientFor(cmd.optsWithGlobals(), { autoStart: false });
422
+ try {
423
+ const health = await client.health();
424
+ info(`kvbox-server ${health.version} is already running (pid ${health.pid})`);
425
+ return;
426
+ } catch {
427
+ } finally {
428
+ await client.close();
429
+ }
430
+ const { pid, socketPath } = await startLocalServer(paths());
431
+ info(`kvbox-server started (pid ${pid}), listening on ${socketPath}`);
432
+ });
433
+
434
+ // src/commands/server/status.ts
435
+ import { Command as Command7 } from "commander";
444
436
  var statusCommand = new Command7("status").description("show whether the server is running").option("--json", "print as JSON").action(async (opts, cmd) => {
445
437
  const client = clientFor(cmd.optsWithGlobals());
446
438
  try {
447
439
  const health = await client.health();
448
440
  if (opts.json) json(health);
449
- else info(`kvbox server ${health.version} running, pid ${health.pid}`);
441
+ else info(`kvbox-server ${health.version} running, pid ${health.pid}`);
450
442
  } finally {
451
443
  await client.close();
452
444
  }
453
445
  });
454
446
 
455
- // src/cli/commands/server/stop.ts
456
- import { readFile as readFile3 } from "fs/promises";
447
+ // src/commands/server/stop.ts
448
+ import { readFile } from "fs/promises";
457
449
  import { Command as Command8 } from "commander";
458
450
  var stopCommand = new Command8("stop").description("stop the local server (running boxes are not affected)").action(async () => {
459
451
  const p = paths();
460
452
  let pid;
461
453
  try {
462
- pid = Number.parseInt(await readFile3(p.serverPid, "utf8"), 10);
454
+ pid = Number.parseInt(await readFile(p.serverPid, "utf8"), 10);
463
455
  } catch {
464
- throw new KvboxError("kvbox server is not running (no pid file)");
456
+ throw new KvboxError("kvbox-server is not running (no pid file)");
465
457
  }
466
458
  try {
467
459
  process.kill(pid, "SIGTERM");
468
460
  } catch {
469
- throw new KvboxError(`kvbox server pid ${pid} is not running; remove ${p.serverPid}`);
461
+ throw new KvboxError(`kvbox-server pid ${pid} is not running; remove ${p.serverPid}`);
470
462
  }
471
- info(`sent SIGTERM to kvbox server (pid ${pid})`);
463
+ info(`sent SIGTERM to kvbox-server (pid ${pid})`);
472
464
  });
473
465
 
474
- // src/cli/commands/server/index.ts
466
+ // src/commands/server/index.ts
475
467
  var serverCommand = new Command9("server").description("run or manage the kvbox server").addCommand(startCommand).addCommand(statusCommand).addCommand(stopCommand);
476
468
 
477
- // src/cli/commands/snapshot/index.ts
469
+ // src/commands/snapshot/index.ts
478
470
  import { Command as Command13 } from "commander";
479
471
 
480
- // src/cli/commands/snapshot/ls.ts
472
+ // src/commands/snapshot/ls.ts
481
473
  import { Command as Command10 } from "commander";
482
474
  var lsCommand2 = new Command10("ls").description("list snapshots").option("--json", "print as JSON").action(() => notImplemented("snapshot ls"));
483
475
 
484
- // src/cli/commands/snapshot/rm.ts
476
+ // src/commands/snapshot/rm.ts
485
477
  import { Command as Command11 } from "commander";
486
478
  var rmCommand2 = new Command11("rm").description("delete a snapshot").argument("<name>", "snapshot name").option("-y, --yes", "do not ask for confirmation").action(() => notImplemented("snapshot rm"));
487
479
 
488
- // src/cli/commands/snapshot/save.ts
480
+ // src/commands/snapshot/save.ts
489
481
  import { Command as Command12 } from "commander";
490
482
  var saveCommand = new Command12("save").description("freeze a running box (memory + disk) into a snapshot").argument("<box>", "box name").argument("<name>", "snapshot name").option("--json", "print the snapshot as JSON").action(() => notImplemented("snapshot save"));
491
483
 
492
- // src/cli/commands/snapshot/index.ts
484
+ // src/commands/snapshot/index.ts
493
485
  var snapshotCommand = new Command13("snapshot").alias("snap").description("manage snapshots").addCommand(saveCommand).addCommand(lsCommand2).addCommand(rmCommand2);
494
486
 
495
- // src/cli/program.ts
487
+ // src/program.ts
496
488
  function buildProgram() {
497
489
  const program = new Command14("kvbox").description("self-hosted Firecracker microVMs with Docker-like ergonomics").version(VERSION, "-v, --version").option("--host <name>", "target a configured remote host (M2)").showHelpAfterError().enablePositionalOptions();
498
- program.addCommand(machineCommand);
490
+ program.addCommand(vmCommand);
499
491
  program.addCommand(snapshotCommand);
500
492
  program.addCommand(serverCommand);
501
- for (const cmd of machineAliases) program.addCommand(cmd);
493
+ for (const cmd of vmAliases) program.addCommand(cmd);
502
494
  return program;
503
495
  }
504
496
 
package/dist/kvbox.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli/output.ts","../src/cli/program.ts","../src/shared/version.ts","../src/cli/commands/machine/index.ts","../src/cli/commands/machine/connect.ts","../src/cli/commands/machine/ls.ts","../src/cli/commands/machine/new.ts","../src/shared/sizes.ts","../src/cli/commands/machine/rm.ts","../src/cli/commands/server/index.ts","../src/cli/commands/server/start.ts","../src/server/listen.ts","../src/core/state.ts","../src/shared/paths.ts","../src/core/config.ts","../src/shared/errors.ts","../src/server/app.ts","../src/server/routes/capacity.ts","../src/core/capacity.ts","../src/server/routes/machines.ts","../src/shared/api.ts","../src/core/machines.ts","../src/server/routes/snapshots.ts","../src/core/snapshots.ts","../src/cli/commands/server/status.ts","../src/cli/client.ts","../src/cli/commands/server/stop.ts","../src/cli/commands/snapshot/index.ts","../src/cli/commands/snapshot/ls.ts","../src/cli/commands/snapshot/rm.ts","../src/cli/commands/snapshot/save.ts","../src/index.ts"],"sourcesContent":["/** Printing: tables for humans, `--json` for agents, colors only on a TTY. */\nimport pc from \"picocolors\";\n\nexport const color = pc.createColors(process.stdout.isTTY === true && !process.env[\"NO_COLOR\"]);\n\nexport function json(value: unknown): void {\n process.stdout.write(JSON.stringify(value, null, 2) + \"\\n\");\n}\n\nexport function table(rows: Record<string, string | number | null | undefined>[], columns?: string[]): void {\n if (rows.length === 0) return;\n const cols = columns ?? Object.keys(rows[0]!);\n const cells = rows.map((r) => cols.map((c) => String(r[c] ?? \"\")));\n const widths = cols.map((c, i) => Math.max(c.length, ...cells.map((r) => r[i]!.length)));\n const line = (parts: string[]) => parts.map((p, i) => p.padEnd(widths[i]!)).join(\" \").trimEnd();\n process.stdout.write(color.dim(line(cols.map((c) => c.toUpperCase()))) + \"\\n\");\n for (const row of cells) process.stdout.write(line(row) + \"\\n\");\n}\n\nexport function info(msg: string): void {\n process.stderr.write(msg + \"\\n\");\n}\n\nexport function warn(msg: string): void {\n process.stderr.write(color.yellow(msg) + \"\\n\");\n}\n\nexport function notImplemented(command: string): never {\n process.stderr.write(color.yellow(`kvbox ${command}: not implemented yet\\n`));\n process.exit(2);\n}\n\n/** y/N prompt on a TTY; requires `-y` otherwise (boxd's rule for destructive commands). */\nexport async function confirm(question: string, yes: boolean): Promise<boolean> {\n if (yes) return true;\n if (!process.stdin.isTTY) {\n warn(\"refusing without -y when stdin is not a terminal\");\n return false;\n }\n const { createInterface } = await import(\"node:readline/promises\");\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n try {\n const answer = (await rl.question(`${question} [y/N] `)).trim().toLowerCase();\n return answer === \"y\" || answer === \"yes\";\n } finally {\n rl.close();\n }\n}\n","/** Builds the commander tree. Importable without side effects (tests); src/index.ts runs it. */\nimport { Command } from \"commander\";\nimport { VERSION } from \"../shared/version.js\";\nimport { machineAliases, machineCommand } from \"./commands/machine/index.js\";\nimport { serverCommand } from \"./commands/server/index.js\";\nimport { snapshotCommand } from \"./commands/snapshot/index.js\";\n\nexport function buildProgram(): Command {\n const program = new Command(\"kvbox\")\n .description(\"self-hosted Firecracker microVMs with Docker-like ergonomics\")\n .version(VERSION, \"-v, --version\")\n .option(\"--host <name>\", \"target a configured remote host (M2)\")\n .showHelpAfterError()\n .enablePositionalOptions();\n\n program.addCommand(machineCommand);\n program.addCommand(snapshotCommand);\n program.addCommand(serverCommand);\n // Top-level shortcuts: the same Command objects as under `machine`.\n for (const cmd of machineAliases) program.addCommand(cmd);\n\n return program;\n}\n","/**\n * The version comes from package.json: tsup injects it at build time, and the\n * dev path (tsx) reads the file. `pnpm version` is the only place it changes.\n */\nimport { createRequire } from \"node:module\";\n\ndeclare const __KVBOX_VERSION__: string | undefined;\n\nfunction fromPackageJson(): string {\n try {\n const require = createRequire(import.meta.url);\n return (require(\"../../package.json\") as { version: string }).version;\n } catch {\n return \"0.0.0-dev\";\n }\n}\n\nexport const VERSION: string = typeof __KVBOX_VERSION__ === \"string\" ? __KVBOX_VERSION__ : fromPackageJson();\n","import { Command } from \"commander\";\nimport { connectCommand } from \"./connect.js\";\nimport { lsCommand } from \"./ls.js\";\nimport { newCommand } from \"./new.js\";\nimport { rmCommand } from \"./rm.js\";\n\nexport const machineCommand = new Command(\"machine\")\n .alias(\"m\")\n .description(\"manage boxes\")\n .addCommand(newCommand)\n .addCommand(lsCommand)\n .addCommand(connectCommand)\n .addCommand(rmCommand);\n\n/** The same Command objects, added again at the top level so they cannot drift. */\nexport const machineAliases = [newCommand, lsCommand, connectCommand, rmCommand];\n","import { Command } from \"commander\";\nimport { notImplemented } from \"../../output.js\";\n\n/** kvbox machine connect <name> — exec ssh kvbox@<ip> with the kvbox key */\nexport const connectCommand = new Command(\"connect\")\n .description(\"open an interactive shell in a box over SSH\")\n .argument(\"<name>\", \"box name\")\n .action(() => notImplemented(\"machine connect\"));\n","import { Command } from \"commander\";\nimport { notImplemented } from \"../../output.js\";\n\n/** kvbox machine ls [--json] — --json output: Machine[] */\nexport const lsCommand = new Command(\"ls\")\n .description(\"list boxes with status and IP\")\n .option(\"--json\", \"print as JSON\")\n .action(() => notImplemented(\"machine ls\"));\n","import { Command, Option } from \"commander\";\nimport { DEFAULT_SIZE, SIZE_NAMES } from \"../../../shared/sizes.js\";\nimport { notImplemented } from \"../../output.js\";\n\n/**\n * kvbox machine new [name] [--size s|m|l] [--from <snapshot>] [--force] [--json]\n * --json output: the Machine object (see src/shared/api.ts).\n */\nexport const newCommand = new Command(\"new\")\n .description(\"boot a new box from the base image or a snapshot\")\n .argument(\"[name]\", \"box name (generated when omitted)\")\n .addOption(new Option(\"-s, --size <size>\", \"box size\").choices(SIZE_NAMES).default(DEFAULT_SIZE))\n .option(\"-f, --from <snapshot>\", \"start from a snapshot instead of the base image\")\n .option(\"--force\", \"skip the capacity guard\")\n .option(\"--json\", \"print the box as JSON\")\n .action(() => notImplemented(\"machine new\"));\n","/** Fixed box sizes. Firecracker backs guest memory lazily, so a box only costs what the guest touches. */\nexport const SIZES = {\n s: { vcpu: 2, memMib: 4096 },\n m: { vcpu: 4, memMib: 8192 },\n l: { vcpu: 8, memMib: 16384 },\n} as const;\n\nexport type SizeName = keyof typeof SIZES;\nexport const SIZE_NAMES = Object.keys(SIZES) as SizeName[];\nexport const DEFAULT_SIZE: SizeName = \"s\";\n\nexport function isSizeName(value: string): value is SizeName {\n return value in SIZES;\n}\n","import { Command } from \"commander\";\nimport { notImplemented } from \"../../output.js\";\n\n/** kvbox machine rm <name> [-y] — destructive: asks y/N on a TTY, needs -y otherwise */\nexport const rmCommand = new Command(\"rm\")\n .description(\"destroy a box\")\n .argument(\"<name>\", \"box name\")\n .option(\"-y, --yes\", \"do not ask for confirmation\")\n .action(() => notImplemented(\"machine rm\"));\n","import { Command } from \"commander\";\nimport { startCommand } from \"./start.js\";\nimport { statusCommand } from \"./status.js\";\nimport { stopCommand } from \"./stop.js\";\n\nexport const serverCommand = new Command(\"server\")\n .description(\"run or manage the kvbox server\")\n .addCommand(startCommand)\n .addCommand(statusCommand)\n .addCommand(stopCommand);\n","/**\n * kvbox server start [--foreground]\n * The daemon mode of the executable and the ONLY command that imports src/server.\n */\nimport { Command } from \"commander\";\nimport { listen } from \"../../../server/listen.js\";\nimport { info } from \"../../output.js\";\n\nexport const startCommand = new Command(\"start\")\n .description(\"run the kvbox server (owns Firecracker and ~/.kvbox)\")\n .option(\"--foreground\", \"stay attached to the terminal (default for now)\")\n .action(async () => {\n const { socketPath } = await listen();\n info(`kvbox server listening on ${socketPath} (pid ${process.pid})`);\n });\n","/** Bind the app to the local Unix socket and write the pid file. */\nimport { rm, writeFile } from \"node:fs/promises\";\nimport { createAdaptorServer } from \"@hono/node-server\";\nimport { ensureLayout } from \"../core/state.js\";\nimport { loadConfig } from \"../core/config.js\";\nimport { KvboxError } from \"../shared/errors.js\";\nimport { paths } from \"../shared/paths.js\";\nimport { createApp } from \"./app.js\";\n\nexport interface ListenOptions {\n socketPath?: string;\n}\n\nexport async function listen(opts: ListenOptions = {}) {\n const p = await ensureLayout(paths());\n await loadConfig(p);\n const socketPath = opts.socketPath ?? p.serverSock;\n // Linux limits a Unix socket path (sun_path) to 108 bytes; bind() fails with EINVAL past it.\n if (Buffer.byteLength(socketPath) > 100) {\n throw new KvboxError(`socket path too long for a Unix socket (${socketPath}); set KVBOX_HOME to a shorter path`);\n }\n\n // A stale socket file from a crashed server blocks bind(); the pid file tells\n // `server status` whether anything is actually alive.\n await rm(socketPath, { force: true });\n\n const server = createAdaptorServer({ fetch: createApp().fetch });\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(socketPath, () => resolve());\n });\n await writeFile(p.serverPid, `${process.pid}\\n`);\n\n const shutdown = async () => {\n server.close();\n await rm(socketPath, { force: true });\n await rm(p.serverPid, { force: true });\n process.exit(0);\n };\n process.once(\"SIGINT\", () => void shutdown());\n process.once(\"SIGTERM\", () => void shutdown());\n\n return { socketPath, server };\n}\n","/**\n * The `~/.kvbox` layout and the per-box / per-snapshot `meta.json` files.\n * The server is the only writer; every read is validated with zod.\n */\nimport { mkdir, readFile, readdir, writeFile } from \"node:fs/promises\";\nimport { z } from \"zod\";\nimport { paths, type Paths } from \"../shared/paths.js\";\n\nexport const MachineMetaSchema = z.object({\n name: z.string(),\n ip: z.string(),\n mac: z.string(),\n pid: z.number().int(),\n vcpu: z.number().int(),\n memMib: z.number().int(),\n createdAt: z.string(),\n source: z.string(),\n});\nexport type MachineMeta = z.infer<typeof MachineMetaSchema>;\n\nexport const SnapshotMetaSchema = z.object({\n name: z.string(),\n source: z.string(),\n vcpu: z.number().int(),\n memMib: z.number().int(),\n createdAt: z.string(),\n});\nexport type SnapshotMeta = z.infer<typeof SnapshotMetaSchema>;\n\n/** Create every directory of the layout. Idempotent; called by `kvbox server start`. */\nexport async function ensureLayout(p: Paths = paths()): Promise<Paths> {\n for (const dir of [p.home, p.ssh, p.bin, p.assets, p.machines, p.snapshots]) {\n await mkdir(dir, { recursive: true, mode: 0o700 });\n }\n return p;\n}\n\nasync function readJson<T>(file: string, schema: z.ZodType<T>): Promise<T> {\n return schema.parse(JSON.parse(await readFile(file, \"utf8\")));\n}\n\nasync function listDirs(dir: string): Promise<string[]> {\n try {\n const entries = await readdir(dir, { withFileTypes: true });\n return entries.filter((e) => e.isDirectory()).map((e) => e.name);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return [];\n throw err;\n }\n}\n\nexport async function readMachineMeta(name: string, p: Paths = paths()) {\n return readJson(p.machine(name).meta, MachineMetaSchema);\n}\n\nexport async function writeMachineMeta(meta: MachineMeta, p: Paths = paths()) {\n await mkdir(p.machine(meta.name).dir, { recursive: true });\n await writeFile(p.machine(meta.name).meta, JSON.stringify(meta, null, 2) + \"\\n\");\n}\n\nexport async function listMachineNames(p: Paths = paths()) {\n return listDirs(p.machines);\n}\n\nexport async function readSnapshotMeta(name: string, p: Paths = paths()) {\n return readJson(p.snapshot(name).meta, SnapshotMetaSchema);\n}\n\nexport async function writeSnapshotMeta(meta: SnapshotMeta, p: Paths = paths()) {\n await mkdir(p.snapshot(meta.name).dir, { recursive: true });\n await writeFile(p.snapshot(meta.name).meta, JSON.stringify(meta, null, 2) + \"\\n\");\n}\n\nexport async function listSnapshotNames(p: Paths = paths()) {\n return listDirs(p.snapshots);\n}\n\n/** True when a pid is alive (signal 0). */\nexport function pidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (err) {\n return (err as NodeJS.ErrnoException).code === \"EPERM\";\n }\n}\n","/**\n * Host state layout. The filesystem is the database and `~/.kvbox` is its root.\n * This module is dependency-free so both the CLI (socket path, hosts.json)\n * and core (everything else) can use it without crossing the layer boundary.\n *\n * `KVBOX_HOME` overrides the root, mainly for tests.\n */\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport function kvboxHome(): string {\n return process.env[\"KVBOX_HOME\"] ?? join(homedir(), \".kvbox\");\n}\n\nexport function paths(home: string = kvboxHome()) {\n return {\n home,\n config: join(home, \"config.json\"),\n serverSock: join(home, \"server.sock\"),\n serverPid: join(home, \"server.pid\"),\n hosts: join(home, \"hosts.json\"),\n ssh: join(home, \"ssh\"),\n sshKey: join(home, \"ssh\", \"id_ed25519\"),\n bin: join(home, \"bin\"),\n firecracker: join(home, \"bin\", \"firecracker\"),\n assets: join(home, \"assets\"),\n kernel: join(home, \"assets\", \"vmlinux\"),\n rootfs: join(home, \"assets\", \"rootfs.ext4\"),\n machines: join(home, \"machines\"),\n snapshots: join(home, \"snapshots\"),\n machine: (name: string) => ({\n dir: join(home, \"machines\", name),\n meta: join(home, \"machines\", name, \"meta.json\"),\n disk: join(home, \"machines\", name, \"disk.ext4\"),\n sock: join(home, \"machines\", name, \"fc.sock\"),\n log: join(home, \"machines\", name, \"fc.log\"),\n }),\n snapshot: (name: string) => ({\n dir: join(home, \"snapshots\", name),\n meta: join(home, \"snapshots\", name, \"meta.json\"),\n vmstate: join(home, \"snapshots\", name, \"vmstate\"),\n mem: join(home, \"snapshots\", name, \"mem\"),\n disk: join(home, \"snapshots\", name, \"disk.ext4\"),\n }),\n } as const;\n}\n\nexport type Paths = ReturnType<typeof paths>;\n","/** `~/.kvbox/config.json`: asset paths, subnet, default size. Written with defaults on first run. */\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport { z } from \"zod\";\nimport { paths, type Paths } from \"../shared/paths.js\";\nimport { DEFAULT_SIZE, SIZE_NAMES } from \"../shared/sizes.js\";\n\nexport const ConfigSchema = z.object({\n kernel: z.string(),\n rootfs: z.string(),\n firecracker: z.string(),\n bridge: z.string().default(\"zbr0\"),\n subnet: z.string().default(\"172.30.0.0/24\"),\n gateway: z.string().default(\"172.30.0.1\"),\n defaultSize: z.enum(SIZE_NAMES as [string, ...string[]]).default(DEFAULT_SIZE),\n});\nexport type Config = z.infer<typeof ConfigSchema>;\n\nexport function defaultConfig(p: Paths = paths()): Config {\n return ConfigSchema.parse({\n kernel: p.kernel,\n rootfs: p.rootfs,\n firecracker: p.firecracker,\n });\n}\n\nexport async function loadConfig(p: Paths = paths()): Promise<Config> {\n try {\n const raw = await readFile(p.config, \"utf8\");\n return ConfigSchema.parse(JSON.parse(raw));\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n const cfg = defaultConfig(p);\n await writeFile(p.config, JSON.stringify(cfg, null, 2) + \"\\n\");\n return cfg;\n }\n throw err;\n }\n}\n","/** Errors with an exit code; the entry point prints `message` and exits with `exitCode`. */\nexport class KvboxError extends Error {\n constructor(\n message: string,\n public readonly exitCode: number = 1,\n ) {\n super(message);\n this.name = \"KvboxError\";\n }\n}\n\nexport class NotImplementedError extends KvboxError {\n constructor(what: string) {\n super(`${what}: not implemented yet`, 2);\n this.name = \"NotImplementedError\";\n }\n}\n","/**\n * The hono app behind `kvbox server start`. Served over `~/.kvbox/server.sock`\n * in M1; the same app goes behind HTTPS in M2.\n */\nimport { Hono } from \"hono\";\nimport { KvboxError } from \"../shared/errors.js\";\nimport { VERSION } from \"../shared/version.js\";\nimport { capacityRoutes } from \"./routes/capacity.js\";\nimport { machineRoutes } from \"./routes/machines.js\";\nimport { snapshotRoutes } from \"./routes/snapshots.js\";\n\nexport function createApp() {\n const app = new Hono();\n\n app.get(\"/health\", (c) => c.json({ ok: true as const, version: VERSION, pid: process.pid }));\n app.route(\"/machines\", machineRoutes());\n app.route(\"/snapshots\", snapshotRoutes());\n app.route(\"/capacity\", capacityRoutes());\n\n app.notFound((c) => c.json({ error: `no route: ${c.req.method} ${c.req.path}` }, 404));\n app.onError((err, c) => {\n if (err instanceof KvboxError) {\n return c.json({ error: err.message }, err.exitCode === 2 ? 501 : 400);\n }\n console.error(err);\n return c.json({ error: err.message }, 500);\n });\n\n return app;\n}\n\nexport type App = ReturnType<typeof createApp>;\n","import { Hono } from \"hono\";\nimport { capacity } from \"../../core/capacity.js\";\n\nexport function capacityRoutes() {\n const r = new Hono();\n r.get(\"/\", async (c) => c.json(await capacity()));\n return r;\n}\n","/**\n * The capacity guard: `kvbox new` refuses when reserved RAM of running boxes\n * plus the new one exceeds host RAM minus 2 GB (hard), or reserved vCPUs exceed\n * 2x host cores (soft). `--force` skips it.\n */\nimport { NotImplementedError } from \"../shared/errors.js\";\nimport type { Capacity } from \"../shared/api.js\";\n\nexport const MEM_HEADROOM_MIB = 2048;\nexport const VCPU_OVERSUBSCRIPTION = 2;\n\nexport async function capacity(): Promise<Capacity> {\n throw new NotImplementedError(\"capacity.capacity\");\n}\n\nexport async function assertRoomFor(_vcpu: number, _memMib: number, _force: boolean): Promise<void> {\n throw new NotImplementedError(\"capacity.assertRoomFor\");\n}\n","import { Hono } from \"hono\";\nimport { CreateMachineRequest } from \"../../shared/api.js\";\nimport { createMachine, destroyMachine, listMachines } from \"../../core/machines.js\";\n\nexport function machineRoutes() {\n const r = new Hono();\n r.get(\"/\", async (c) => c.json(await listMachines()));\n r.post(\"/\", async (c) => {\n const body = CreateMachineRequest.parse(await c.req.json().catch(() => ({})));\n return c.json(await createMachine(body), 201);\n });\n r.delete(\"/:name\", async (c) => {\n await destroyMachine(c.req.param(\"name\"));\n return c.body(null, 204);\n });\n return r;\n}\n","/**\n * Wire shapes of the kvbox HTTP API, shared by the client (src/cli) and the\n * server (src/server). Validated with zod on both sides because the client\n * may be talking to an older or newer server.\n */\nimport { z } from \"zod\";\nimport { SIZE_NAMES } from \"./sizes.js\";\n\nexport const SizeSchema = z.enum(SIZE_NAMES as [string, ...string[]]);\n\nexport const MachineSchema = z.object({\n name: z.string(),\n ip: z.string(),\n mac: z.string(),\n pid: z.number().int().nullable(),\n vcpu: z.number().int(),\n memMib: z.number().int(),\n createdAt: z.string(),\n source: z.string(), // \"base\" or \"snapshot:<name>\"\n status: z.enum([\"running\", \"stopped\"]),\n});\nexport type Machine = z.infer<typeof MachineSchema>;\n\nexport const CreateMachineRequest = z.object({\n name: z.string().optional(),\n size: SizeSchema.optional(),\n from: z.string().optional(),\n force: z.boolean().optional(),\n});\nexport type CreateMachineRequest = z.infer<typeof CreateMachineRequest>;\n\nexport const SnapshotSchema = z.object({\n name: z.string(),\n source: z.string(),\n vcpu: z.number().int(),\n memMib: z.number().int(),\n createdAt: z.string(),\n});\nexport type Snapshot = z.infer<typeof SnapshotSchema>;\n\nexport const CreateSnapshotRequest = z.object({\n box: z.string(),\n name: z.string(),\n});\nexport type CreateSnapshotRequest = z.infer<typeof CreateSnapshotRequest>;\n\nexport const CapacitySchema = z.object({\n hostMemMib: z.number().int(),\n hostCores: z.number().int(),\n reservedMemMib: z.number().int(),\n reservedVcpu: z.number().int(),\n running: z.number().int(),\n});\nexport type Capacity = z.infer<typeof CapacitySchema>;\n\nexport const HealthSchema = z.object({\n ok: z.literal(true),\n version: z.string(),\n pid: z.number().int(),\n});\nexport type Health = z.infer<typeof HealthSchema>;\n\nexport const ApiErrorSchema = z.object({\n error: z.string(),\n});\n","/** Create, list and destroy boxes. See architecture \"Process model\". */\nimport { NotImplementedError } from \"../shared/errors.js\";\nimport type { CreateMachineRequest, Machine } from \"../shared/api.js\";\n\nexport async function listMachines(): Promise<Machine[]> {\n throw new NotImplementedError(\"machines.listMachines\");\n}\n\nexport async function createMachine(_req: CreateMachineRequest): Promise<Machine> {\n throw new NotImplementedError(\"machines.createMachine\");\n}\n\nexport async function destroyMachine(_name: string): Promise<void> {\n throw new NotImplementedError(\"machines.destroyMachine\");\n}\n","import { Hono } from \"hono\";\nimport { CreateSnapshotRequest } from \"../../shared/api.js\";\nimport { deleteSnapshot, listSnapshots, saveSnapshot } from \"../../core/snapshots.js\";\n\nexport function snapshotRoutes() {\n const r = new Hono();\n r.get(\"/\", async (c) => c.json(await listSnapshots()));\n r.post(\"/\", async (c) => {\n const body = CreateSnapshotRequest.parse(await c.req.json());\n return c.json(await saveSnapshot(body), 201);\n });\n r.delete(\"/:name\", async (c) => {\n await deleteSnapshot(c.req.param(\"name\"));\n return c.body(null, 204);\n });\n return r;\n}\n","/** Full snapshots (vmstate + mem + disk copy) and their listing. */\nimport { NotImplementedError } from \"../shared/errors.js\";\nimport type { CreateSnapshotRequest, Snapshot } from \"../shared/api.js\";\n\nexport async function listSnapshots(): Promise<Snapshot[]> {\n throw new NotImplementedError(\"snapshots.listSnapshots\");\n}\n\nexport async function saveSnapshot(_req: CreateSnapshotRequest): Promise<Snapshot> {\n throw new NotImplementedError(\"snapshots.saveSnapshot\");\n}\n\nexport async function deleteSnapshot(_name: string): Promise<void> {\n throw new NotImplementedError(\"snapshots.deleteSnapshot\");\n}\n","import { Command } from \"commander\";\nimport { clientFor } from \"../../client.js\";\nimport { info, json } from \"../../output.js\";\n\n/** kvbox server status [--json] — --json output: Health */\nexport const statusCommand = new Command(\"status\")\n .description(\"show whether the server is running\")\n .option(\"--json\", \"print as JSON\")\n .action(async (opts: { json?: boolean }, cmd: Command) => {\n const client = clientFor(cmd.optsWithGlobals());\n try {\n const health = await client.health();\n if (opts.json) json(health);\n else info(`kvbox server ${health.version} running, pid ${health.pid}`);\n } finally {\n await client.close();\n }\n });\n","/**\n * ServerClient: one method per route of the kvbox API, over undici.\n * Target resolution: `--host` -> configured hosts (M2) -> local Unix socket.\n * Auto-starting the local server when nothing listens is #17.\n */\nimport { Agent, request, type Dispatcher } from \"undici\";\nimport { z } from \"zod\";\nimport {\n ApiErrorSchema,\n CapacitySchema,\n HealthSchema,\n MachineSchema,\n SnapshotSchema,\n type CreateMachineRequest,\n type CreateSnapshotRequest,\n} from \"../shared/api.js\";\nimport { KvboxError } from \"../shared/errors.js\";\nimport { paths } from \"../shared/paths.js\";\n\nexport type Target = { kind: \"local\"; socketPath: string } | { kind: \"remote\"; name: string; url: string };\n\nexport function resolveTarget(hostFlag?: string): Target {\n if (hostFlag) {\n throw new KvboxError(`--host ${hostFlag}: remote hosts arrive in M2`);\n }\n return { kind: \"local\", socketPath: paths().serverSock };\n}\n\nexport class ServerClient {\n private readonly dispatcher: Dispatcher;\n private readonly base: string;\n\n constructor(readonly target: Target) {\n if (target.kind === \"local\") {\n this.dispatcher = new Agent({ connect: { socketPath: target.socketPath } });\n this.base = \"http://kvbox\";\n } else {\n this.dispatcher = new Agent();\n this.base = target.url;\n }\n }\n\n private async call<T>(\n method: \"GET\" | \"POST\" | \"DELETE\",\n path: string,\n schema: z.ZodType<T>,\n body?: unknown,\n ): Promise<T> {\n let res;\n try {\n res = await request(this.base + path, {\n dispatcher: this.dispatcher,\n method,\n headers: body === undefined ? {} : { \"content-type\": \"application/json\" },\n body: body === undefined ? null : JSON.stringify(body),\n });\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code ?? (err as Error).cause;\n if (this.target.kind === \"local\") {\n throw new KvboxError(`kvbox server is not running (${String(code)}); start it with: kvbox server start`);\n }\n throw new KvboxError(`cannot reach ${this.target.name}: ${String(code)}`);\n }\n const text = await res.body.text();\n if (res.statusCode >= 300) {\n const parsed = ApiErrorSchema.safeParse(text ? JSON.parse(text) : {});\n const msg = parsed.success ? parsed.data.error : `${res.statusCode} ${text}`;\n throw new KvboxError(msg, res.statusCode === 501 ? 2 : 1);\n }\n return schema.parse(text ? JSON.parse(text) : undefined);\n }\n\n health() {\n return this.call(\"GET\", \"/health\", HealthSchema);\n }\n\n capacity() {\n return this.call(\"GET\", \"/capacity\", CapacitySchema);\n }\n\n listMachines() {\n return this.call(\"GET\", \"/machines\", z.array(MachineSchema));\n }\n\n createMachine(req: CreateMachineRequest) {\n return this.call(\"POST\", \"/machines\", MachineSchema, req);\n }\n\n deleteMachine(name: string) {\n return this.call(\"DELETE\", `/machines/${encodeURIComponent(name)}`, z.undefined());\n }\n\n listSnapshots() {\n return this.call(\"GET\", \"/snapshots\", z.array(SnapshotSchema));\n }\n\n createSnapshot(req: CreateSnapshotRequest) {\n return this.call(\"POST\", \"/snapshots\", SnapshotSchema, req);\n }\n\n deleteSnapshot(name: string) {\n return this.call(\"DELETE\", `/snapshots/${encodeURIComponent(name)}`, z.undefined());\n }\n\n async close() {\n await this.dispatcher.close();\n }\n}\n\n/** Build a client from the global `--host` option of the root command. */\nexport function clientFor(opts: { host?: string }): ServerClient {\n return new ServerClient(resolveTarget(opts.host));\n}\n","import { readFile } from \"node:fs/promises\";\nimport { Command } from \"commander\";\nimport { KvboxError } from \"../../../shared/errors.js\";\nimport { paths } from \"../../../shared/paths.js\";\nimport { info } from \"../../output.js\";\n\n/** kvbox server stop — SIGTERM the pid in server.pid. Boxes keep running. */\nexport const stopCommand = new Command(\"stop\")\n .description(\"stop the local server (running boxes are not affected)\")\n .action(async () => {\n const p = paths();\n let pid: number;\n try {\n pid = Number.parseInt(await readFile(p.serverPid, \"utf8\"), 10);\n } catch {\n throw new KvboxError(\"kvbox server is not running (no pid file)\");\n }\n try {\n process.kill(pid, \"SIGTERM\");\n } catch {\n throw new KvboxError(`kvbox server pid ${pid} is not running; remove ${p.serverPid}`);\n }\n info(`sent SIGTERM to kvbox server (pid ${pid})`);\n });\n","import { Command } from \"commander\";\nimport { lsCommand } from \"./ls.js\";\nimport { rmCommand } from \"./rm.js\";\nimport { saveCommand } from \"./save.js\";\n\nexport const snapshotCommand = new Command(\"snapshot\")\n .alias(\"snap\")\n .description(\"manage snapshots\")\n .addCommand(saveCommand)\n .addCommand(lsCommand)\n .addCommand(rmCommand);\n","import { Command } from \"commander\";\nimport { notImplemented } from \"../../output.js\";\n\n/** kvbox snapshot ls [--json] — --json output: Snapshot[] */\nexport const lsCommand = new Command(\"ls\")\n .description(\"list snapshots\")\n .option(\"--json\", \"print as JSON\")\n .action(() => notImplemented(\"snapshot ls\"));\n","import { Command } from \"commander\";\nimport { notImplemented } from \"../../output.js\";\n\n/** kvbox snapshot rm <name> [-y] — destructive: asks y/N on a TTY, needs -y otherwise */\nexport const rmCommand = new Command(\"rm\")\n .description(\"delete a snapshot\")\n .argument(\"<name>\", \"snapshot name\")\n .option(\"-y, --yes\", \"do not ask for confirmation\")\n .action(() => notImplemented(\"snapshot rm\"));\n","import { Command } from \"commander\";\nimport { notImplemented } from \"../../output.js\";\n\n/** kvbox snapshot save <box> <name> [--json] — --json output: Snapshot */\nexport const saveCommand = new Command(\"save\")\n .description(\"freeze a running box (memory + disk) into a snapshot\")\n .argument(\"<box>\", \"box name\")\n .argument(\"<name>\", \"snapshot name\")\n .option(\"--json\", \"print the snapshot as JSON\")\n .action(() => notImplemented(\"snapshot save\"));\n","/** Entry point: parse argv, map KvboxError to an exit code. */\nimport { color } from \"./cli/output.js\";\nimport { buildProgram } from \"./cli/program.js\";\nimport { KvboxError } from \"./shared/errors.js\";\n\ntry {\n await buildProgram().parseAsync(process.argv);\n} catch (err) {\n if (err instanceof KvboxError) {\n process.stderr.write(color.red(`error: ${err.message}`) + \"\\n\");\n process.exit(err.exitCode);\n }\n throw err;\n}\n"],"mappings":"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","names":["Command","Command","Command","Command","Command","Command","Command","Command","Command","Command","Command","writeFile","readFile","writeFile","z","z","readFile","writeFile","Hono","Hono","z","z","Hono","Hono","Hono","Hono","writeFile","Command","Command","z","z","Command","readFile","Command","Command","readFile","Command","Command","Command","lsCommand","Command","Command","rmCommand","Command","Command","Command","Command","lsCommand","rmCommand","Command"]}
1
+ {"version":3,"sources":["../src/output.ts","../src/program.ts","../../shared/src/version.ts","../src/version.ts","../src/commands/vm/index.ts","../src/commands/vm/connect.ts","../../shared/src/errors.ts","../src/client.ts","../../shared/src/api.ts","../../shared/src/sizes.ts","../../shared/src/paths.ts","../src/commands/vm/ls.ts","../src/commands/vm/new.ts","../src/commands/vm/rm.ts","../src/commands/server/index.ts","../src/commands/server/start.ts","../src/commands/server/status.ts","../src/commands/server/stop.ts","../src/commands/snapshot/index.ts","../src/commands/snapshot/ls.ts","../src/commands/snapshot/rm.ts","../src/commands/snapshot/save.ts","../src/index.ts"],"sourcesContent":["/** Printing: tables for humans, `--json` for agents, colors only on a TTY. */\nimport pc from \"picocolors\";\n\nexport const color = pc.createColors(process.stdout.isTTY === true && !process.env[\"NO_COLOR\"]);\n\nexport function json(value: unknown): void {\n process.stdout.write(JSON.stringify(value, null, 2) + \"\\n\");\n}\n\nexport function table(rows: Record<string, string | number | null | undefined>[], columns?: string[]): void {\n if (rows.length === 0) return;\n const cols = columns ?? Object.keys(rows[0]!);\n const cells = rows.map((r) => cols.map((c) => String(r[c] ?? \"\")));\n const widths = cols.map((c, i) => Math.max(c.length, ...cells.map((r) => r[i]!.length)));\n const line = (parts: string[]) => parts.map((p, i) => p.padEnd(widths[i]!)).join(\" \").trimEnd();\n process.stdout.write(color.dim(line(cols.map((c) => c.toUpperCase()))) + \"\\n\");\n for (const row of cells) process.stdout.write(line(row) + \"\\n\");\n}\n\nexport function info(msg: string): void {\n process.stderr.write(msg + \"\\n\");\n}\n\nexport function warn(msg: string): void {\n process.stderr.write(color.yellow(msg) + \"\\n\");\n}\n\nexport function notImplemented(command: string): never {\n process.stderr.write(color.yellow(`kvbox ${command}: not implemented yet\\n`));\n process.exit(2);\n}\n\n/** y/N prompt on a TTY; requires `-y` otherwise (boxd's rule for destructive commands). */\nexport async function confirm(question: string, yes: boolean): Promise<boolean> {\n if (yes) return true;\n if (!process.stdin.isTTY) {\n warn(\"refusing without -y when stdin is not a terminal\");\n return false;\n }\n const { createInterface } = await import(\"node:readline/promises\");\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n try {\n const answer = (await rl.question(`${question} [y/N] `)).trim().toLowerCase();\n return answer === \"y\" || answer === \"yes\";\n } finally {\n rl.close();\n }\n}\n","/** Builds the commander tree. Importable without side effects (tests); src/index.ts runs it. */\nimport { Command } from \"commander\";\nimport { VERSION } from \"./version.js\";\nimport { vmAliases, vmCommand } from \"./commands/vm/index.js\";\nimport { serverCommand } from \"./commands/server/index.js\";\nimport { snapshotCommand } from \"./commands/snapshot/index.js\";\n\nexport function buildProgram(): Command {\n const program = new Command(\"kvbox\")\n .description(\"self-hosted Firecracker microVMs with Docker-like ergonomics\")\n .version(VERSION, \"-v, --version\")\n .option(\"--host <name>\", \"target a configured remote host (M2)\")\n .showHelpAfterError()\n .enablePositionalOptions();\n\n program.addCommand(vmCommand);\n program.addCommand(snapshotCommand);\n program.addCommand(serverCommand);\n // Top-level shortcuts: the same Command objects as under `vm`.\n for (const cmd of vmAliases) program.addCommand(cmd);\n\n return program;\n}\n","/**\n * Each binary's version comes from its own package.json: tsup injects\n * `__KVBOX_VERSION__` at build time; the dev path (tsx) reads the file.\n * Usage in a package: `versionOf(import.meta.url)` from its `src/version.ts`.\n */\nimport { createRequire } from \"node:module\";\n\nexport function versionOf(srcFileUrl: string, injected?: string): string {\n if (typeof injected === \"string\") return injected;\n try {\n const require = createRequire(srcFileUrl);\n return (require(\"../package.json\") as { version: string }).version;\n } catch {\n return \"0.0.0-dev\";\n }\n}\n","import { versionOf } from \"@kvbox/shared/version\";\n\ndeclare const __KVBOX_VERSION__: string | undefined;\n\nexport const VERSION: string = versionOf(import.meta.url, typeof __KVBOX_VERSION__ === \"string\" ? __KVBOX_VERSION__ : undefined);\n","import { Command } from \"commander\";\nimport { connectCommand } from \"./connect.js\";\nimport { lsCommand } from \"./ls.js\";\nimport { newCommand } from \"./new.js\";\nimport { rmCommand } from \"./rm.js\";\n\nexport const vmCommand = new Command(\"vm\")\n .enablePositionalOptions()\n .description(\"manage boxes (each one is a Firecracker microVM)\")\n .addCommand(newCommand)\n .addCommand(lsCommand)\n .addCommand(connectCommand)\n .addCommand(rmCommand);\n\n/** The same Command objects, added again at the top level so they cannot drift. */\nexport const vmAliases = [newCommand, lsCommand, connectCommand, rmCommand];\n","import { spawnSync } from \"node:child_process\";\nimport { Command } from \"commander\";\nimport { KvboxError } from \"@kvbox/shared/errors\";\nimport { clientFor, sshArgs } from \"../../client.js\";\n\n/** kvbox vm connect <name> [-- cmd...] — ssh kvbox@<ip> with the kvbox key; a shell, or run a command */\nexport const connectCommand = new Command(\"connect\")\n .description(\"open a shell in a box over SSH (or run a command: kvbox connect <name> -- <cmd>)\")\n .argument(\"<name>\", \"box name\")\n .argument(\"[cmd...]\", \"command to run instead of a shell\")\n .passThroughOptions()\n .action(async (name: string, cmdArgs: string[], _opts: unknown, cmd: Command) => {\n const client = clientFor(cmd.optsWithGlobals());\n let ip: string;\n try {\n const box = (await client.listMachines()).find((b) => b.name === name);\n if (!box) throw new KvboxError(`box ${name}: not found (kvbox ls)`);\n if (box.status !== \"running\") throw new KvboxError(`box ${name} is ${box.status}`);\n ip = box.ip;\n } finally {\n await client.close();\n }\n const res = spawnSync(\"ssh\", [...sshArgs(ip), ...cmdArgs], { stdio: \"inherit\" });\n if (res.error) throw new KvboxError(`could not run ssh: ${res.error.message}`);\n process.exit(res.status ?? 1);\n });\n","/** Errors with an exit code; the entry point prints `message` and exits with `exitCode`. */\nexport class KvboxError extends Error {\n constructor(\n message: string,\n public readonly exitCode: number = 1,\n ) {\n super(message);\n this.name = \"KvboxError\";\n }\n}\n\nexport class NotImplementedError extends KvboxError {\n constructor(what: string) {\n super(`${what}: not implemented yet`, 2);\n this.name = \"NotImplementedError\";\n }\n}\n\n/** The named box/snapshot/host does not exist: HTTP 404, exit 1. */\nexport class NotFoundError extends KvboxError {\n constructor(what: string) {\n super(`${what}: not found`, 1);\n this.name = \"NotFoundError\";\n }\n}\n","/**\n * ServerClient: one method per route of the kvbox API, over undici.\n * Target resolution: `--host` -> configured hosts (M2) -> local Unix socket.\n * Auto-starting the local server from every command when nothing listens is #17;\n * `startLocalServer` below is the piece it will reuse.\n */\nimport { spawn } from \"node:child_process\";\nimport { openSync } from \"node:fs\";\nimport { mkdir, stat } from \"node:fs/promises\";\nimport { setTimeout as sleep } from \"node:timers/promises\";\nimport { Agent, request, type Dispatcher } from \"undici\";\nimport { z } from \"zod\";\nimport {\n ApiErrorSchema,\n CapacitySchema,\n HealthSchema,\n MachineSchema,\n SnapshotSchema,\n type CreateMachineRequest,\n type CreateSnapshotRequest,\n} from \"@kvbox/shared/api\";\nimport { KvboxError } from \"@kvbox/shared/errors\";\nimport { paths, type Paths } from \"@kvbox/shared/paths\";\n\nexport type Target = { kind: \"local\"; socketPath: string } | { kind: \"remote\"; name: string; url: string };\n\nexport function resolveTarget(hostFlag?: string): Target {\n if (hostFlag) {\n throw new KvboxError(`--host ${hostFlag}: remote hosts arrive in M2`);\n }\n return { kind: \"local\", socketPath: paths().serverSock };\n}\n\nexport interface ClientOptions {\n /** Spawn `kvbox-server` when the local socket does not answer (default true; the start probe turns it off). */\n autoStart?: boolean;\n}\n\nexport class ServerClient {\n private readonly dispatcher: Dispatcher;\n private readonly base: string;\n private readonly autoStart: boolean;\n private startedOnce = false;\n\n constructor(\n readonly target: Target,\n opts: ClientOptions = {},\n ) {\n this.autoStart = opts.autoStart ?? true;\n if (target.kind === \"local\") {\n this.dispatcher = new Agent({ connect: { socketPath: target.socketPath } });\n this.base = \"http://kvbox\";\n } else {\n this.dispatcher = new Agent();\n this.base = target.url;\n }\n }\n\n private async call<T>(\n method: \"GET\" | \"POST\" | \"DELETE\",\n path: string,\n schema: z.ZodType<T>,\n body?: unknown,\n ): Promise<T> {\n let res;\n try {\n res = await request(this.base + path, {\n dispatcher: this.dispatcher,\n method,\n headers: body === undefined ? {} : { \"content-type\": \"application/json\" },\n body: body === undefined ? null : JSON.stringify(body),\n });\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code ?? (err as Error).cause;\n if (this.target.kind === \"local\") {\n // Nothing listens locally: start the daemon once and retry (architecture: \"auto-start\").\n if (this.autoStart && !this.startedOnce) {\n this.startedOnce = true;\n await startLocalServer(paths(), 5000);\n return this.call(method, path, schema, body);\n }\n throw new KvboxError(`kvbox-server is not running (${String(code)}); start it with: kvbox server start`);\n }\n throw new KvboxError(`cannot reach ${this.target.name}: ${String(code)}`);\n }\n const text = await res.body.text();\n if (res.statusCode >= 300) {\n const parsed = ApiErrorSchema.safeParse(text ? JSON.parse(text) : {});\n const msg = parsed.success ? parsed.data.error : `${res.statusCode} ${text}`;\n throw new KvboxError(msg, res.statusCode === 501 ? 2 : 1);\n }\n return schema.parse(text ? JSON.parse(text) : undefined);\n }\n\n health() {\n return this.call(\"GET\", \"/health\", HealthSchema);\n }\n\n capacity() {\n return this.call(\"GET\", \"/capacity\", CapacitySchema);\n }\n\n listMachines() {\n return this.call(\"GET\", \"/machines\", z.array(MachineSchema));\n }\n\n createMachine(req: CreateMachineRequest) {\n return this.call(\"POST\", \"/machines\", MachineSchema, req);\n }\n\n deleteMachine(name: string) {\n return this.call(\"DELETE\", `/machines/${encodeURIComponent(name)}`, z.undefined());\n }\n\n listSnapshots() {\n return this.call(\"GET\", \"/snapshots\", z.array(SnapshotSchema));\n }\n\n createSnapshot(req: CreateSnapshotRequest) {\n return this.call(\"POST\", \"/snapshots\", SnapshotSchema, req);\n }\n\n deleteSnapshot(name: string) {\n return this.call(\"DELETE\", `/snapshots/${encodeURIComponent(name)}`, z.undefined());\n }\n\n async close() {\n await this.dispatcher.close();\n }\n}\n\n/** Build a client from the global `--host` option of the root command. */\nexport function clientFor(opts: { host?: string }, client: ClientOptions = {}): ServerClient {\n return new ServerClient(resolveTarget(opts.host), client);\n}\n\n/**\n * `ssh` argv for a box. Host keys are per box and IPs get reused, so no known_hosts;\n * SetEnv pins the locale because the image only ships C.UTF-8 (#24).\n */\nexport function sshArgs(ip: string, keyPath: string = paths().sshKey): string[] {\n return [\n \"-i\", keyPath,\n \"-o\", \"StrictHostKeyChecking=no\",\n \"-o\", \"UserKnownHostsFile=/dev/null\",\n \"-o\", \"LogLevel=ERROR\",\n \"-o\", \"SetEnv=LC_ALL=C.UTF-8\",\n `kvbox@${ip}`,\n ];\n}\n\nexport const NOT_A_HOST_HINT =\n \"kvbox-server is not installed, so this machine is not a kvbox host. \" +\n \"Set it up with install-server.sh, or point the CLI at a remote host (M2: kvbox host add).\";\n\n/**\n * Spawn `kvbox-server start` from PATH, detached, stdio to ~/.kvbox/server.log,\n * and resolve once its socket accepts a /health call (or reject after `timeoutMs`).\n */\nexport async function startLocalServer(p: Paths = paths(), timeoutMs = 5000): Promise<{ pid: number; socketPath: string }> {\n await mkdir(p.home, { recursive: true });\n const log = openSync(p.serverLog, \"a\");\n const child = spawn(\"kvbox-server\", [\"start\"], { detached: true, stdio: [\"ignore\", log, log] });\n\n const failed = new Promise<never>((_, reject) => {\n child.once(\"error\", (err: NodeJS.ErrnoException) => {\n reject(new KvboxError(err.code === \"ENOENT\" ? NOT_A_HOST_HINT : `could not start kvbox-server: ${err.message}`));\n });\n child.once(\"exit\", (code) => {\n reject(new KvboxError(`kvbox-server exited with code ${String(code)} before listening; see ${p.serverLog}`));\n });\n });\n\n const ready = (async () => {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n if (await stat(p.serverSock).then(() => true, () => false)) {\n const probe = new ServerClient({ kind: \"local\", socketPath: p.serverSock }, { autoStart: false });\n try {\n await probe.health();\n return;\n } catch {\n // socket exists but not serving yet\n } finally {\n await probe.close();\n }\n }\n await sleep(100);\n }\n throw new KvboxError(`kvbox-server did not start within ${timeoutMs / 1000}s; see ${p.serverLog}`);\n })();\n\n await Promise.race([ready, failed]);\n child.removeAllListeners(\"exit\");\n child.unref();\n return { pid: child.pid ?? -1, socketPath: p.serverSock };\n}\n","/**\n * Wire shapes of the kvbox HTTP API, shared by the client (src/cli) and the\n * server (src/server). Validated with zod on both sides because the client\n * may be talking to an older or newer server.\n */\nimport { z } from \"zod\";\nimport { SIZE_NAMES } from \"./sizes.js\";\n\nexport const SizeSchema = z.enum(SIZE_NAMES as [string, ...string[]]);\n\nexport const MachineSchema = z.object({\n name: z.string(),\n ip: z.string(),\n mac: z.string(),\n pid: z.number().int().nullable(),\n vcpu: z.number().int(),\n memMib: z.number().int(),\n createdAt: z.string(),\n source: z.string(), // \"base\" or \"snapshot:<name>\"\n status: z.enum([\"running\", \"stopped\"]),\n});\nexport type Machine = z.infer<typeof MachineSchema>;\n\nexport const CreateMachineRequest = z.object({\n name: z.string().optional(),\n size: SizeSchema.optional(),\n from: z.string().optional(),\n force: z.boolean().optional(),\n});\nexport type CreateMachineRequest = z.infer<typeof CreateMachineRequest>;\n\nexport const SnapshotSchema = z.object({\n name: z.string(),\n source: z.string(),\n vcpu: z.number().int(),\n memMib: z.number().int(),\n createdAt: z.string(),\n});\nexport type Snapshot = z.infer<typeof SnapshotSchema>;\n\nexport const CreateSnapshotRequest = z.object({\n box: z.string(),\n name: z.string(),\n});\nexport type CreateSnapshotRequest = z.infer<typeof CreateSnapshotRequest>;\n\nexport const CapacitySchema = z.object({\n hostMemMib: z.number().int(),\n hostCores: z.number().int(),\n reservedMemMib: z.number().int(),\n reservedVcpu: z.number().int(),\n running: z.number().int(),\n});\nexport type Capacity = z.infer<typeof CapacitySchema>;\n\nexport const HealthSchema = z.object({\n ok: z.literal(true),\n version: z.string(),\n pid: z.number().int(),\n});\nexport type Health = z.infer<typeof HealthSchema>;\n\nexport const ApiErrorSchema = z.object({\n error: z.string(),\n});\n","/** Fixed box sizes. Firecracker backs guest memory lazily, so a box only costs what the guest touches. */\nexport const SIZES = {\n s: { vcpu: 2, memMib: 4096 },\n m: { vcpu: 4, memMib: 8192 },\n l: { vcpu: 8, memMib: 16384 },\n} as const;\n\nexport type SizeName = keyof typeof SIZES;\nexport const SIZE_NAMES = Object.keys(SIZES) as SizeName[];\nexport const DEFAULT_SIZE: SizeName = \"s\";\n\nexport function isSizeName(value: string): value is SizeName {\n return value in SIZES;\n}\n","/**\n * Host state layout. The filesystem is the database and `~/.kvbox` is its root.\n * This module is dependency-free so both the CLI (socket path, hosts.json)\n * and core (everything else) can use it without crossing the layer boundary.\n *\n * `KVBOX_HOME` overrides the root, mainly for tests.\n */\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport function kvboxHome(): string {\n return process.env[\"KVBOX_HOME\"] ?? join(homedir(), \".kvbox\");\n}\n\nexport function paths(home: string = kvboxHome()) {\n return {\n home,\n config: join(home, \"config.json\"),\n serverSock: join(home, \"server.sock\"),\n serverPid: join(home, \"server.pid\"),\n serverLog: join(home, \"server.log\"),\n hosts: join(home, \"hosts.json\"),\n ssh: join(home, \"ssh\"),\n sshKey: join(home, \"ssh\", \"id_ed25519\"),\n bin: join(home, \"bin\"),\n firecracker: join(home, \"bin\", \"firecracker\"),\n assets: join(home, \"assets\"),\n kernel: join(home, \"assets\", \"vmlinux\"),\n rootfs: join(home, \"assets\", \"rootfs.ext4\"),\n machines: join(home, \"machines\"),\n snapshots: join(home, \"snapshots\"),\n machine: (name: string) => ({\n dir: join(home, \"machines\", name),\n meta: join(home, \"machines\", name, \"meta.json\"),\n disk: join(home, \"machines\", name, \"disk.ext4\"),\n sock: join(home, \"machines\", name, \"fc.sock\"),\n log: join(home, \"machines\", name, \"fc.log\"),\n }),\n snapshot: (name: string) => ({\n dir: join(home, \"snapshots\", name),\n meta: join(home, \"snapshots\", name, \"meta.json\"),\n vmstate: join(home, \"snapshots\", name, \"vmstate\"),\n mem: join(home, \"snapshots\", name, \"mem\"),\n disk: join(home, \"snapshots\", name, \"disk.ext4\"),\n }),\n } as const;\n}\n\nexport type Paths = ReturnType<typeof paths>;\n","import { Command } from \"commander\";\nimport { clientFor } from \"../../client.js\";\nimport { info, json, table } from \"../../output.js\";\n\n/** kvbox vm ls [--json] — --json output: Machine[] */\nexport const lsCommand = new Command(\"ls\")\n .description(\"list boxes with status and IP\")\n .option(\"--json\", \"print as JSON\")\n .action(async (opts: { json?: boolean }, cmd: Command) => {\n const client = clientFor(cmd.optsWithGlobals());\n try {\n const boxes = await client.listMachines();\n if (opts.json) {\n json(boxes);\n return;\n }\n if (boxes.length === 0) {\n info(\"no boxes yet; create one with: kvbox new\");\n return;\n }\n table(\n boxes.map((b) => ({\n name: b.name,\n status: b.status,\n ip: b.ip,\n size: `${b.vcpu} vCPU / ${b.memMib / 1024} GB`,\n source: b.source,\n created: b.createdAt.replace(\"T\", \" \").slice(0, 16),\n })),\n );\n } finally {\n await client.close();\n }\n });\n","import { Command, Option } from \"commander\";\nimport type { Machine } from \"@kvbox/shared/api\";\nimport { SIZE_NAMES } from \"@kvbox/shared/sizes\";\nimport { clientFor } from \"../../client.js\";\nimport { color, info, json } from \"../../output.js\";\n\n/**\n * kvbox vm new [name] [--size s|m|l] [--from <snapshot>] [--force] [--json]\n * --json output: the Machine object (see packages/shared/src/api.ts).\n */\nexport const newCommand = new Command(\"new\")\n .description(\"boot a new box from the base image or a snapshot\")\n .argument(\"[name]\", \"box name (box1, box2, … when omitted)\")\n .addOption(new Option(\"-s, --size <size>\", \"box size (default: server config, s)\").choices(SIZE_NAMES))\n .option(\"-f, --from <snapshot>\", \"start from a snapshot instead of the base image\")\n .option(\"--force\", \"skip the capacity guard\")\n .option(\"--json\", \"print the box as JSON\")\n .action(async (name: string | undefined, opts: { size?: string; from?: string; force?: boolean; json?: boolean }, cmd: Command) => {\n const client = clientFor(cmd.optsWithGlobals());\n try {\n const box = await client.createMachine({\n ...(name !== undefined ? { name } : {}),\n ...(opts.size !== undefined ? { size: opts.size } : {}),\n ...(opts.from !== undefined ? { from: opts.from } : {}),\n ...(opts.force ? { force: true } : {}),\n });\n if (opts.json) {\n json(box);\n return;\n }\n process.stdout.write(`${box.name}\\n`);\n info(`${color.green(\"✓\")} ${describe(box)}`);\n info(` connect with: kvbox connect ${box.name} (SSH is up a couple of seconds after boot)`);\n } finally {\n await client.close();\n }\n });\n\nexport function describe(box: Machine): string {\n return `box ${color.bold(box.name)} is ${box.status} at ${box.ip}, ${box.vcpu} vCPU / ${box.memMib / 1024} GB`;\n}\n","import { Command } from \"commander\";\nimport { clientFor } from \"../../client.js\";\nimport { color, confirm, info } from \"../../output.js\";\n\n/** kvbox vm rm <name> [-y] — destructive: asks y/N on a TTY, needs -y otherwise */\nexport const rmCommand = new Command(\"rm\")\n .description(\"destroy a box (its disk is deleted; snapshots are kept)\")\n .argument(\"<name>\", \"box name\")\n .option(\"-y, --yes\", \"do not ask for confirmation\")\n .action(async (name: string, opts: { yes?: boolean }, cmd: Command) => {\n if (!(await confirm(`Destroy box ${name} and its disk?`, opts.yes ?? false))) {\n info(\"cancelled\");\n process.exitCode = 1;\n return;\n }\n const client = clientFor(cmd.optsWithGlobals());\n try {\n await client.deleteMachine(name);\n info(`${color.green(\"✓\")} box ${name} destroyed`);\n } finally {\n await client.close();\n }\n });\n","import { Command } from \"commander\";\nimport { startCommand } from \"./start.js\";\nimport { statusCommand } from \"./status.js\";\nimport { stopCommand } from \"./stop.js\";\n\nexport const serverCommand = new Command(\"server\")\n .description(\"run or manage the kvbox server\")\n .addCommand(startCommand)\n .addCommand(statusCommand)\n .addCommand(stopCommand);\n","/**\n * kvbox server start\n * Starts the local `kvbox-server` (a separate package) detached, logging to\n * ~/.kvbox/server.log, and waits for its socket. The CLI never imports server code.\n */\nimport { Command } from \"commander\";\nimport { paths } from \"@kvbox/shared/paths\";\nimport { clientFor, startLocalServer } from \"../../client.js\";\nimport { info } from \"../../output.js\";\n\nexport const startCommand = new Command(\"start\")\n .description(\"start the local kvbox-server in the background\")\n .action(async (_opts: Record<string, never>, cmd: Command) => {\n const client = clientFor(cmd.optsWithGlobals(), { autoStart: false });\n try {\n const health = await client.health();\n info(`kvbox-server ${health.version} is already running (pid ${health.pid})`);\n return;\n } catch {\n // not running: start it\n } finally {\n await client.close();\n }\n const { pid, socketPath } = await startLocalServer(paths());\n info(`kvbox-server started (pid ${pid}), listening on ${socketPath}`);\n });\n","import { Command } from \"commander\";\nimport { clientFor } from \"../../client.js\";\nimport { info, json } from \"../../output.js\";\n\n/** kvbox server status [--json] — --json output: Health */\nexport const statusCommand = new Command(\"status\")\n .description(\"show whether the server is running\")\n .option(\"--json\", \"print as JSON\")\n .action(async (opts: { json?: boolean }, cmd: Command) => {\n const client = clientFor(cmd.optsWithGlobals());\n try {\n const health = await client.health();\n if (opts.json) json(health);\n else info(`kvbox-server ${health.version} running, pid ${health.pid}`);\n } finally {\n await client.close();\n }\n });\n","import { readFile } from \"node:fs/promises\";\nimport { Command } from \"commander\";\nimport { KvboxError } from \"@kvbox/shared/errors\";\nimport { paths } from \"@kvbox/shared/paths\";\nimport { info } from \"../../output.js\";\n\n/** kvbox server stop — SIGTERM the pid in server.pid. Boxes keep running. */\nexport const stopCommand = new Command(\"stop\")\n .description(\"stop the local server (running boxes are not affected)\")\n .action(async () => {\n const p = paths();\n let pid: number;\n try {\n pid = Number.parseInt(await readFile(p.serverPid, \"utf8\"), 10);\n } catch {\n throw new KvboxError(\"kvbox-server is not running (no pid file)\");\n }\n try {\n process.kill(pid, \"SIGTERM\");\n } catch {\n throw new KvboxError(`kvbox-server pid ${pid} is not running; remove ${p.serverPid}`);\n }\n info(`sent SIGTERM to kvbox-server (pid ${pid})`);\n });\n","import { Command } from \"commander\";\nimport { lsCommand } from \"./ls.js\";\nimport { rmCommand } from \"./rm.js\";\nimport { saveCommand } from \"./save.js\";\n\nexport const snapshotCommand = new Command(\"snapshot\")\n .alias(\"snap\")\n .description(\"manage snapshots\")\n .addCommand(saveCommand)\n .addCommand(lsCommand)\n .addCommand(rmCommand);\n","import { Command } from \"commander\";\nimport { notImplemented } from \"../../output.js\";\n\n/** kvbox snapshot ls [--json] — --json output: Snapshot[] */\nexport const lsCommand = new Command(\"ls\")\n .description(\"list snapshots\")\n .option(\"--json\", \"print as JSON\")\n .action(() => notImplemented(\"snapshot ls\"));\n","import { Command } from \"commander\";\nimport { notImplemented } from \"../../output.js\";\n\n/** kvbox snapshot rm <name> [-y] — destructive: asks y/N on a TTY, needs -y otherwise */\nexport const rmCommand = new Command(\"rm\")\n .description(\"delete a snapshot\")\n .argument(\"<name>\", \"snapshot name\")\n .option(\"-y, --yes\", \"do not ask for confirmation\")\n .action(() => notImplemented(\"snapshot rm\"));\n","import { Command } from \"commander\";\nimport { notImplemented } from \"../../output.js\";\n\n/** kvbox snapshot save <box> <name> [--json] — --json output: Snapshot */\nexport const saveCommand = new Command(\"save\")\n .description(\"freeze a running box (memory + disk) into a snapshot\")\n .argument(\"<box>\", \"box name\")\n .argument(\"<name>\", \"snapshot name\")\n .option(\"--json\", \"print the snapshot as JSON\")\n .action(() => notImplemented(\"snapshot save\"));\n","/** Entry point: parse argv, map KvboxError to an exit code. */\nimport { color } from \"./output.js\";\nimport { buildProgram } from \"./program.js\";\nimport { KvboxError } from \"@kvbox/shared/errors\";\n\ntry {\n await buildProgram().parseAsync(process.argv);\n} catch (err) {\n if (err instanceof KvboxError) {\n process.stderr.write(color.red(`error: ${err.message}`) + \"\\n\");\n process.exit(err.exitCode);\n }\n throw err;\n}\n"],"mappings":"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","names":["Command","require","Command","z","z","Command","Command","Command","Command","Command","Command","Command","Command","Command","Command","Command","Command","Command","Command","Command","Command","Command","lsCommand","Command","Command","rmCommand","Command","Command","Command","Command","lsCommand","rmCommand","Command"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ghalex/kvbox",
3
- "version": "0.0.1",
4
- "description": "Self-hosted Firecracker microVM manager with Docker-like ergonomics",
3
+ "version": "0.0.3",
4
+ "description": "kvbox CLI: manage Firecracker microVM hosts with Docker-like commands. Thin client; the host runs @ghalex/kvbox-server.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -15,7 +15,8 @@
15
15
  },
16
16
  "repository": {
17
17
  "type": "git",
18
- "url": "https://github.com/ghalex/kvbox"
18
+ "url": "https://github.com/ghalex/kvbox",
19
+ "directory": "packages/cli"
19
20
  },
20
21
  "homepage": "https://kvbox.sh",
21
22
  "keywords": [
@@ -28,30 +29,17 @@
28
29
  "engines": {
29
30
  "node": ">=22"
30
31
  },
31
- "packageManager": "pnpm@11.5.2",
32
32
  "scripts": {
33
33
  "dev": "tsx src/index.ts",
34
- "build": "tsup",
35
- "typecheck": "tsc --noEmit",
36
- "test": "vitest run",
37
- "test:watch": "vitest",
38
- "lint:deps": "depcruise src --config .dependency-cruiser.cjs",
39
- "check": "pnpm typecheck && pnpm lint:deps && pnpm test"
34
+ "build": "tsup"
40
35
  },
41
36
  "dependencies": {
42
- "@hono/node-server": "^2.1.5",
43
37
  "commander": "^15.0.0",
44
- "hono": "^4.13.13",
45
38
  "picocolors": "^1.1.1",
46
39
  "undici": "^8.12.3",
47
40
  "zod": "^4.6.7"
48
41
  },
49
42
  "devDependencies": {
50
- "@types/node": "^26.6.5",
51
- "dependency-cruiser": "^18.6.0",
52
- "tsup": "^9.0.0",
53
- "tsx": "^4.23.15",
54
- "typescript": "^6.0.3",
55
- "vitest": "^5.0.5"
43
+ "@kvbox/shared": "workspace:*"
56
44
  }
57
45
  }
package/README.md DELETED
@@ -1,76 +0,0 @@
1
- # kvbox
2
-
3
- Self-hosted Firecracker microVMs with Docker-like ergonomics. Every box is a
4
- real VM with its own kernel, boots in seconds, and a running box can be
5
- snapshotted (memory + disk) and cloned from that snapshot already warm.
6
-
7
- ```
8
- kvbox new # boot a fresh box (2 vCPU / 4 GB)
9
- kvbox connect <name> # shell into it over SSH
10
- kvbox snapshot save <box> <n> # freeze memory + disk
11
- kvbox new --from <n> # start a warm clone
12
- kvbox ls / kvbox rm <name>
13
- ```
14
-
15
- Linux host with KVM only. Status: early development, nothing boots yet.
16
-
17
- ## Install
18
-
19
- One line on a fresh Linux host with Node 22+ (it asks for sudo only where
20
- root is needed):
21
-
22
- ```
23
- curl -fsSL https://kvbox.sh/install.sh | bash
24
- ```
25
-
26
- Until that domain is live, use the raw GitHub URL of `install.sh` instead, or
27
- run `./install.sh` from a clone. The installer checks the host, installs a pinned
28
- Firecracker, installs the `kvbox` CLI from npm (`@ghalex/kvbox`), the base
29
- image and the SSH key `kvbox connect` uses, and adds you to the `kvm` group.
30
- Rerun it any time; it only changes what is missing. `KVBOX_YES=1` takes every
31
- default without asking.
32
-
33
- ## Layout
34
-
35
- One executable, two roles. `kvbox server start` is the daemon that owns
36
- Firecracker and `~/.kvbox`; every other command is a thin client talking to it
37
- over a Unix socket (HTTPS to remote hosts later).
38
-
39
- ```
40
- src/
41
- index.ts entry point; cli/program.ts builds the command tree
42
- cli/ commands (one file each), HTTP client, output
43
- server/ hono app + routes, started only by `server start`
44
- core/ Firecracker client, state dir, networking, capacity guard
45
- shared/ dependency-free helpers: paths, sizes, API schemas
46
- install.sh one-shot host setup
47
- scripts/ host networking (bridge + NAT)
48
- spikes/ throwaway experiments
49
- ```
50
-
51
- Dependency direction `cli -> (HTTP) -> server -> core` is enforced by
52
- dependency-cruiser.
53
-
54
- ## Develop
55
-
56
- Requires Node >= 22 and pnpm.
57
-
58
- ```
59
- pnpm install
60
- pnpm dev -- --help # run from source
61
- pnpm check # typecheck + dependency rules + tests
62
- pnpm build # dist/kvbox.js, the single file published to npm
63
- npm link # put this checkout's `kvbox` on PATH for development
64
- ```
65
-
66
- Set `KVBOX_HOME` to use a state dir other than `~/.kvbox`.
67
-
68
- The base image comes from [kvbox-images](https://github.com/ghalex/kvbox-images)
69
- as a release asset; `install.sh` pins it with `ROOTFS_VERSION`. To use a local
70
- build instead, point `KVBOX_IMAGES_DIR` at a checkout of that repo. Per-box
71
- settings (hostname, SSH key, IP) arrive on the kernel command line at boot, so
72
- one image serves every host and user.
73
-
74
- ## License
75
-
76
- MIT