@sylphx/cli 0.1.9 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1596 -413
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -24,13 +24,13 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  ));
25
25
 
26
26
  // src/index.ts
27
- var import_chalk17 = __toESM(require("chalk"));
28
- var import_commander16 = require("commander");
27
+ var import_chalk23 = __toESM(require("chalk"));
28
+ var import_commander22 = require("commander");
29
29
 
30
30
  // package.json
31
31
  var package_default = {
32
32
  name: "@sylphx/cli",
33
- version: "0.1.9",
33
+ version: "0.2.0",
34
34
  description: "Sylphx Platform CLI \u2014 deploy, manage logs, env vars, and more",
35
35
  type: "commonjs",
36
36
  bin: {
@@ -73,8 +73,7 @@ var package_default = {
73
73
  }
74
74
  };
75
75
 
76
- // src/commands/db.ts
77
- var import_node_readline = __toESM(require("readline"));
76
+ // src/commands/config-cmd.ts
78
77
  var import_chalk = __toESM(require("chalk"));
79
78
  var import_commander = require("commander");
80
79
  var import_ora = __toESM(require("ora"));
@@ -154,6 +153,7 @@ var config = {
154
153
  };
155
154
 
156
155
  // src/api.ts
156
+ var { version } = package_default;
157
157
  var BASE_URL = process.env.SYLPHX_API_URL ?? "https://api.sylphx.com";
158
158
  var API_BASE = `${BASE_URL}/v1`;
159
159
  var AUTH_BASE_URL = "https://sylphx.com";
@@ -178,7 +178,7 @@ async function request(method, path5, options) {
178
178
  const headers = {
179
179
  Authorization: `Bearer ${token}`,
180
180
  "Content-Type": "application/json",
181
- "User-Agent": "sylphx-cli/0.1.0"
181
+ "User-Agent": `sylphx-cli/${version}`
182
182
  };
183
183
  const res = await fetch(url, {
184
184
  method,
@@ -218,13 +218,12 @@ var api = {
218
218
  query: { envType }
219
219
  });
220
220
  },
221
- /** Rollback to previous deployment
222
- * NOTE: Endpoint needs backend verification; follows RESTful convention.
221
+ /** Rollback to previous deployment.
222
+ * Accepts envId (rollback env to previous deploy), deploymentRecordId, or buildRecordId.
223
+ * One of these is required.
223
224
  */
224
- async rollback(projectId, envType) {
225
- return request("POST", `/projects/${projectId}/rollback`, {
226
- body: { envType }
227
- });
225
+ async rollback(projectId, body) {
226
+ return request("POST", `/projects/${projectId}/rollback`, { body });
228
227
  },
229
228
  /** List env vars for a project environment */
230
229
  async listEnvVars(projectId, envType) {
@@ -252,30 +251,77 @@ var api = {
252
251
  query: { envType }
253
252
  });
254
253
  },
255
- /** List domains for a project environment */
256
- async listDomains(projectId, envType) {
257
- const params = new URLSearchParams({ envType });
258
- const res = await request(
259
- "GET",
260
- `/domains/projects/${projectId}/domains?${params}`
254
+ // ── Domain Management (Unified System) ──────────────────────────────────
255
+ /** List all apex domains for a project environment */
256
+ async listDomains(projectId, envType = "production") {
257
+ return request("GET", `/domains/projects/${projectId}/domains`, {
258
+ query: { envType }
259
+ });
260
+ },
261
+ /** Add a custom domain to a project */
262
+ async addDomain(projectId, domain, envType = "production") {
263
+ return request(
264
+ "POST",
265
+ `/domains/projects/${projectId}/domains`,
266
+ { query: { envType }, body: { domain } }
267
+ );
268
+ },
269
+ /** Remove a custom domain from a project */
270
+ async removeDomain(projectId, domain, envType = "production") {
271
+ return request(
272
+ "DELETE",
273
+ `/domains/projects/${projectId}/domains/${encodeURIComponent(domain)}`,
274
+ { query: { envType } }
261
275
  );
262
- return res.customDomains ?? [];
263
276
  },
264
- /** Add a domain for a project environment */
265
- async addDomain(projectId, domain, envType) {
266
- const params = new URLSearchParams({ envType });
267
- return request("POST", `/domains/projects/${projectId}/domains?${params}`, {
268
- body: { domain }
277
+ /** Register an apex domain for a project (full domain object with DNS records) */
278
+ async createDomain(projectId, apexDomain, envType = "production") {
279
+ return request("POST", `/domains/projects/${projectId}/domains`, {
280
+ body: { apexDomain, envType }
269
281
  });
270
282
  },
271
- /** Remove a domain from a project environment */
272
- async removeDomain(projectId, domain, envType) {
273
- const params = new URLSearchParams({ envType });
283
+ /** Add a hostname binding to an existing domain */
284
+ async addHostname(projectId, domainId, hostname, opts) {
285
+ const { envType = "production", ...bodyOpts } = opts ?? {};
286
+ return request(
287
+ "POST",
288
+ `/domains/projects/${projectId}/domains/${domainId}/hostnames`,
289
+ { query: { envType }, body: { hostname, ...bodyOpts } }
290
+ );
291
+ },
292
+ /** Remove a hostname binding */
293
+ async removeHostname(projectId, domainId, hostnameId) {
274
294
  return request(
275
295
  "DELETE",
276
- `/domains/projects/${projectId}/domains/${encodeURIComponent(domain)}?${params}`
296
+ `/domains/projects/${projectId}/domains/${domainId}/hostnames/${hostnameId}`
297
+ );
298
+ },
299
+ /** Trigger DNS check for a hostname */
300
+ async checkHostname(projectId, domainId, hostnameId) {
301
+ return request(
302
+ "POST",
303
+ `/domains/projects/${projectId}/domains/${domainId}/hostnames/${hostnameId}/check`
304
+ );
305
+ },
306
+ /** Enable email sending for a domain (generates DKIM keypair) */
307
+ async enableEmail(projectId, domainId, opts) {
308
+ return request(
309
+ "POST",
310
+ `/domains/projects/${projectId}/domains/${domainId}/email`,
311
+ { body: opts ?? {} }
312
+ );
313
+ },
314
+ /** Check DKIM DNS propagation and load into Stalwart */
315
+ async checkEmail(projectId, domainId) {
316
+ return request(
317
+ "POST",
318
+ `/domains/${projectId}/domains/${domainId}/email/check`
277
319
  );
278
320
  },
321
+ /** Disable email sending for a domain */
322
+ async disableEmail(projectId, domainId) {
323
+ return request("DELETE", `/domains/${projectId}/domains/${domainId}/email`);
324
+ },
279
325
  /** Get current user and orgs */
280
326
  async whoami() {
281
327
  return request("GET", "/user/me");
@@ -333,12 +379,224 @@ var api = {
333
379
  /** CLI auth: poll for token (uses main sylphx.com domain) */
334
380
  async pollCliAuth(code) {
335
381
  const res = await fetch(`${AUTH_BASE_URL}/api/auth/cli/poll?code=${encodeURIComponent(code)}`, {
336
- headers: { "User-Agent": "sylphx-cli/0.1.0" }
382
+ headers: { "User-Agent": `sylphx-cli/${version}` }
337
383
  });
338
384
  if (!res.ok) {
339
385
  throw new ApiError(res.status, "Failed to poll auth status");
340
386
  }
341
387
  return res.json();
388
+ },
389
+ // ── Services ─────────────────────────────────────────────────────────────
390
+ /** List services for a project */
391
+ async listServices(projectId) {
392
+ const res = await request(
393
+ "GET",
394
+ `/projects/${encodeURIComponent(projectId)}/services`
395
+ );
396
+ return Array.isArray(res) ? res : res.data ?? [];
397
+ },
398
+ /** Get a service by name */
399
+ async getService(projectId, name) {
400
+ return request(
401
+ "GET",
402
+ `/projects/${encodeURIComponent(projectId)}/services/${encodeURIComponent(name)}`
403
+ );
404
+ },
405
+ /**
406
+ * Deploy a specific service.
407
+ * Backend expects { environmentId?, force?, image? } — NOT envType.
408
+ * Resolves envType → environmentId automatically.
409
+ */
410
+ async deployService(projectId, name, envType) {
411
+ const environmentId = await this.resolveEnvId(projectId, envType);
412
+ return request(
413
+ "POST",
414
+ `/projects/${encodeURIComponent(projectId)}/services/${encodeURIComponent(name)}/deploy`,
415
+ { body: environmentId ? { environmentId } : {} }
416
+ );
417
+ },
418
+ /** Delete a service */
419
+ async deleteService(projectId, name) {
420
+ return request(
421
+ "DELETE",
422
+ `/projects/${encodeURIComponent(projectId)}/services/${encodeURIComponent(name)}`
423
+ );
424
+ },
425
+ // ── Volumes ───────────────────────────────────────────────────────────────
426
+ /** List volumes for a project */
427
+ async listVolumes(projectId) {
428
+ const res = await request(
429
+ "GET",
430
+ `/projects/${encodeURIComponent(projectId)}/volumes`
431
+ );
432
+ return Array.isArray(res) ? res : res.volumes ?? [];
433
+ },
434
+ /** Create a volume */
435
+ async createVolume(projectId, data) {
436
+ return request(
437
+ "POST",
438
+ `/projects/${encodeURIComponent(projectId)}/volumes`,
439
+ { body: data }
440
+ );
441
+ },
442
+ /** Delete a volume */
443
+ async deleteVolume(projectId, volId) {
444
+ return request(
445
+ "DELETE",
446
+ `/projects/${encodeURIComponent(projectId)}/volumes/${encodeURIComponent(volId)}`
447
+ );
448
+ },
449
+ // ── Storage (Blob) ────────────────────────────────────────────────────────
450
+ /** List storage resources for a project.
451
+ * Backend returns a plain array of binding objects (not wrapped in { data: [...] }).
452
+ * Optionally filter by envType client-side.
453
+ */
454
+ async listStorage(projectId, envType) {
455
+ const res = await request(
456
+ "GET",
457
+ `/projects/${encodeURIComponent(projectId)}/storage`
458
+ );
459
+ const list = Array.isArray(res) ? res : [];
460
+ return envType ? list.filter((r) => !r.envType || r.envType === envType) : list;
461
+ },
462
+ /** Provision blob storage for a project environment */
463
+ async createStorage(projectId, data) {
464
+ return request("POST", `/projects/${encodeURIComponent(projectId)}/storage`, {
465
+ body: data
466
+ });
467
+ },
468
+ /** Delete a storage resource */
469
+ async deleteStorage(projectId, storageId) {
470
+ return request(
471
+ "DELETE",
472
+ `/projects/${encodeURIComponent(projectId)}/storage/${encodeURIComponent(storageId)}`
473
+ );
474
+ },
475
+ // ── Config (remote key-value) ─────────────────────────────────────────────
476
+ /**
477
+ * List all config keys for a project.
478
+ * Backend uses ?env=<envId> (not ?envType). Resolves envType → envId automatically.
479
+ */
480
+ async listConfig(projectId, envType) {
481
+ const query = {};
482
+ if (envType) {
483
+ const envId = await this.resolveEnvId(projectId, envType);
484
+ if (envId) query.env = envId;
485
+ }
486
+ const res = await request(
487
+ "GET",
488
+ `/projects/${encodeURIComponent(projectId)}/config`,
489
+ { query }
490
+ );
491
+ return Array.isArray(res) ? res : res.data ?? [];
492
+ },
493
+ /**
494
+ * Get a single config key.
495
+ * Backend uses ?env=<envId> (not ?envType). Resolves envType → envId automatically.
496
+ */
497
+ async getConfig(projectId, key, envType) {
498
+ const query = {};
499
+ if (envType) {
500
+ const envId = await this.resolveEnvId(projectId, envType);
501
+ if (envId) query.env = envId;
502
+ }
503
+ return request(
504
+ "GET",
505
+ `/projects/${encodeURIComponent(projectId)}/config/${encodeURIComponent(key)}`,
506
+ { query }
507
+ );
508
+ },
509
+ /**
510
+ * Set a config key.
511
+ * Backend expects { key, value, environmentId } (not envType).
512
+ * Resolves envType → environmentId automatically when envType is provided.
513
+ */
514
+ async setConfig(projectId, key, value, envType) {
515
+ let environmentId = null;
516
+ if (envType) {
517
+ environmentId = await this.resolveEnvId(projectId, envType);
518
+ }
519
+ return request("POST", `/projects/${encodeURIComponent(projectId)}/config`, {
520
+ body: { key, value, environmentId }
521
+ });
522
+ },
523
+ /**
524
+ * Delete a config key.
525
+ * Backend uses ?env=<envId> (not ?envType). Resolves envType → envId automatically.
526
+ */
527
+ async deleteConfig(projectId, key, envType) {
528
+ const query = {};
529
+ if (envType) {
530
+ const envId = await this.resolveEnvId(projectId, envType);
531
+ if (envId) query.env = envId;
532
+ }
533
+ return request(
534
+ "DELETE",
535
+ `/projects/${encodeURIComponent(projectId)}/config/${encodeURIComponent(key)}`,
536
+ { query }
537
+ );
538
+ },
539
+ // ── Environments ──────────────────────────────────────────────────────────
540
+ /** List environments for a project */
541
+ async listEnvironments(projectId) {
542
+ const res = await request(
543
+ "GET",
544
+ `/projects/${encodeURIComponent(projectId)}/environments`
545
+ );
546
+ return Array.isArray(res) ? res : res.data ?? [];
547
+ },
548
+ /**
549
+ * Resolve an environment type string (e.g. "production") → its envId for the given project.
550
+ * Returns null if not found. Used internally by config commands.
551
+ */
552
+ async resolveEnvId(projectId, envType) {
553
+ const envs = await this.listEnvironments(projectId);
554
+ const match = envs.find((e) => (e.envType ?? "").toLowerCase() === envType.toLowerCase());
555
+ return match?.id ?? null;
556
+ },
557
+ /** Promote an environment (e.g. staging → production).
558
+ * Backend returns { promoted: number, services: [...], sourceEnvironment, targetEnvironment }
559
+ */
560
+ async promoteEnvironment(projectId, targetEnvId, sourceEnvId) {
561
+ return request(
562
+ "POST",
563
+ `/projects/${encodeURIComponent(projectId)}/environments/${encodeURIComponent(targetEnvId)}/promote`,
564
+ { body: { sourceEnvId } }
565
+ );
566
+ },
567
+ // ── Resource Bindings ─────────────────────────────────────────────────────
568
+ /** List bindings for a resource (which envs it's linked to).
569
+ * Backend returns { bindings: [...] }.
570
+ */
571
+ async listResourceBindings(resourceId) {
572
+ const res = await request(
573
+ "GET",
574
+ `/resources/${encodeURIComponent(resourceId)}/bindings`
575
+ );
576
+ return res.bindings ?? [];
577
+ },
578
+ /**
579
+ * Bind a resource to a project environment.
580
+ * Backend expects { appEnvironmentId, role? }.
581
+ * Accepts projectId + envType and resolves to appEnvironmentId automatically.
582
+ */
583
+ async bindResource(resourceId, data) {
584
+ const appEnvironmentId = await this.resolveEnvId(data.projectId, data.envType);
585
+ if (!appEnvironmentId) {
586
+ throw new Error(`Environment '${data.envType}' not found for project '${data.projectId}'`);
587
+ }
588
+ return request(
589
+ "POST",
590
+ `/resources/${encodeURIComponent(resourceId)}/bindings`,
591
+ { body: { appEnvironmentId, role: data.role ?? "primary" } }
592
+ );
593
+ },
594
+ /** Unbind a resource from a project environment */
595
+ async unbindResource(resourceId, bindingId) {
596
+ return request(
597
+ "DELETE",
598
+ `/resources/${encodeURIComponent(resourceId)}/bindings/${encodeURIComponent(bindingId)}`
599
+ );
342
600
  }
343
601
  };
344
602
  function createLogStream(projectId, envType) {
@@ -349,13 +607,105 @@ function createLogStream(projectId, envType) {
349
607
  };
350
608
  }
351
609
 
610
+ // src/commands/config-cmd.ts
611
+ function requireLinkedApp() {
612
+ const linked = config.getLinkedApp();
613
+ if (!linked) {
614
+ console.log(import_chalk.default.red("No app linked. Run `sylphx link` first."));
615
+ process.exit(1);
616
+ }
617
+ return linked;
618
+ }
619
+ function requireToken() {
620
+ const token = config.getToken();
621
+ if (!token) {
622
+ console.log(import_chalk.default.red("Not authenticated. Run `sylphx login` first."));
623
+ process.exit(1);
624
+ }
625
+ return token;
626
+ }
627
+ var listCmd = new import_commander.Command("list").description("List all remote config keys").option("--env <env>", "Environment (e.g. production, staging)").action(async (opts) => {
628
+ requireToken();
629
+ const linked = requireLinkedApp();
630
+ const spinner = (0, import_ora.default)("Fetching config...").start();
631
+ try {
632
+ const entries = await api.listConfig(linked.appId, opts.env);
633
+ spinner.stop();
634
+ if (entries.length === 0) {
635
+ console.log(import_chalk.default.dim(" No config keys set."));
636
+ return;
637
+ }
638
+ console.log();
639
+ const maxKey = Math.max(...entries.map((e) => e.key.length), 3);
640
+ for (const e of entries) {
641
+ const display = typeof e.value === "string" ? e.value : JSON.stringify(e.value);
642
+ console.log(` ${import_chalk.default.cyan(e.key.padEnd(maxKey))} ${import_chalk.default.white(display)}`);
643
+ }
644
+ console.log();
645
+ } catch (err) {
646
+ spinner.fail(import_chalk.default.red(err instanceof Error ? err.message : String(err)));
647
+ process.exit(1);
648
+ }
649
+ });
650
+ var getCmd = new import_commander.Command("get").description("Get a remote config value").argument("<key>", "Config key name").option("--env <env>", "Environment (e.g. production, staging)").action(async (key, opts) => {
651
+ requireToken();
652
+ const linked = requireLinkedApp();
653
+ const spinner = (0, import_ora.default)(`Fetching config key '${key}'...`).start();
654
+ try {
655
+ const entry = await api.getConfig(linked.appId, key, opts.env);
656
+ spinner.stop();
657
+ console.log(
658
+ typeof entry.value === "string" ? entry.value : JSON.stringify(entry.value, null, 2)
659
+ );
660
+ } catch (err) {
661
+ spinner.fail(import_chalk.default.red(err instanceof Error ? err.message : String(err)));
662
+ process.exit(1);
663
+ }
664
+ });
665
+ var setCmd = new import_commander.Command("set").description("Set a remote config key").argument("<assignment>", "KEY=VALUE assignment").option("--env <env>", "Environment (e.g. production, staging)").action(async (assignment, opts) => {
666
+ requireToken();
667
+ const linked = requireLinkedApp();
668
+ const eqIdx = assignment.indexOf("=");
669
+ if (eqIdx === -1) {
670
+ console.log(import_chalk.default.red(" Invalid format. Use KEY=VALUE."));
671
+ process.exit(1);
672
+ }
673
+ const key = assignment.slice(0, eqIdx);
674
+ const value = assignment.slice(eqIdx + 1);
675
+ const spinner = (0, import_ora.default)(`Setting config '${key}'...`).start();
676
+ try {
677
+ await api.setConfig(linked.appId, key, value, opts.env);
678
+ spinner.succeed(import_chalk.default.green(`\u2713 Config '${key}' set`));
679
+ } catch (err) {
680
+ spinner.fail(import_chalk.default.red(err instanceof Error ? err.message : String(err)));
681
+ process.exit(1);
682
+ }
683
+ });
684
+ var rmCmd = new import_commander.Command("rm").description("Delete a remote config key").argument("<key>", "Config key to delete").option("--env <env>", "Environment (e.g. production, staging)").action(async (key, opts) => {
685
+ requireToken();
686
+ const linked = requireLinkedApp();
687
+ const spinner = (0, import_ora.default)(`Deleting config '${key}'...`).start();
688
+ try {
689
+ await api.deleteConfig(linked.appId, key, opts.env);
690
+ spinner.succeed(import_chalk.default.green(`\u2713 Config '${key}' deleted`));
691
+ } catch (err) {
692
+ spinner.fail(import_chalk.default.red(err instanceof Error ? err.message : String(err)));
693
+ process.exit(1);
694
+ }
695
+ });
696
+ var configCommand = new import_commander.Command("config").description("Manage remote config (non-secret key-value store)").addCommand(listCmd).addCommand(getCmd).addCommand(setCmd).addCommand(rmCmd).action(() => configCommand.help());
697
+
352
698
  // src/commands/db.ts
699
+ var import_node_readline = __toESM(require("readline"));
700
+ var import_chalk2 = __toESM(require("chalk"));
701
+ var import_commander2 = require("commander");
702
+ var import_ora2 = __toESM(require("ora"));
353
703
  var POLL_INTERVAL_MS = 3e3;
354
704
  var POLL_TIMEOUT_MS = 2 * 60 * 1e3;
355
705
  function requireAuth() {
356
706
  const token = config.getToken();
357
707
  if (!token) {
358
- console.log(import_chalk.default.red("Not authenticated. Run `sylphx login` first."));
708
+ console.log(import_chalk2.default.red("Not authenticated. Run `sylphx login` first."));
359
709
  process.exit(1);
360
710
  }
361
711
  return token;
@@ -371,36 +721,36 @@ function prompt(question) {
371
721
  }
372
722
  function printDatabase(db) {
373
723
  console.log("");
374
- console.log(import_chalk.default.bold(` ${db.name}`));
375
- console.log(import_chalk.default.dim(` ${"\u2500".repeat(50)}`));
376
- console.log(` ID : ${import_chalk.default.white(db.id)}`);
724
+ console.log(import_chalk2.default.bold(` ${db.name}`));
725
+ console.log(import_chalk2.default.dim(` ${"\u2500".repeat(50)}`));
726
+ console.log(` ID : ${import_chalk2.default.white(db.id)}`);
377
727
  console.log(` Status : ${statusColour(db.status)}`);
378
- console.log(` Tier : ${import_chalk.default.white(db.tier)}`);
379
- if (db.env) console.log(` Env : ${import_chalk.default.white(db.env)}`);
380
- if (db.host) console.log(` Host : ${import_chalk.default.white(db.host)}`);
381
- if (db.port) console.log(` Port : ${import_chalk.default.white(String(db.port))}`);
382
- if (db.dbName) console.log(` DB Name : ${import_chalk.default.white(db.dbName)}`);
383
- if (db.dbUser) console.log(` DB User : ${import_chalk.default.white(db.dbUser)}`);
384
- if (db.storageGb) console.log(` Storage : ${import_chalk.default.white(`${db.storageGb} GB`)}`);
728
+ console.log(` Tier : ${import_chalk2.default.white(db.tier)}`);
729
+ if (db.env) console.log(` Env : ${import_chalk2.default.white(db.env)}`);
730
+ if (db.host) console.log(` Host : ${import_chalk2.default.white(db.host)}`);
731
+ if (db.port) console.log(` Port : ${import_chalk2.default.white(String(db.port))}`);
732
+ if (db.dbName) console.log(` DB Name : ${import_chalk2.default.white(db.dbName)}`);
733
+ if (db.dbUser) console.log(` DB User : ${import_chalk2.default.white(db.dbUser)}`);
734
+ if (db.storageGb) console.log(` Storage : ${import_chalk2.default.white(`${db.storageGb} GB`)}`);
385
735
  if (db.connectionString) {
386
736
  console.log(` Connection :`);
387
- console.log(` ${import_chalk.default.green(db.connectionString)}`);
737
+ console.log(` ${import_chalk2.default.green(db.connectionString)}`);
388
738
  }
389
- if (db.createdAt) console.log(import_chalk.default.dim(` Created : ${db.createdAt}`));
739
+ if (db.createdAt) console.log(import_chalk2.default.dim(` Created : ${db.createdAt}`));
390
740
  console.log("");
391
741
  }
392
742
  function statusColour(status) {
393
743
  switch (status) {
394
744
  case "ready":
395
- return import_chalk.default.green(status);
745
+ return import_chalk2.default.green(status);
396
746
  case "provisioning":
397
747
  case "branching":
398
- return import_chalk.default.yellow(status);
748
+ return import_chalk2.default.yellow(status);
399
749
  case "error":
400
750
  case "failed":
401
- return import_chalk.default.red(status);
751
+ return import_chalk2.default.red(status);
402
752
  default:
403
- return import_chalk.default.white(status);
753
+ return import_chalk2.default.white(status);
404
754
  }
405
755
  }
406
756
  async function pollUntilReady(id, spinner) {
@@ -417,152 +767,153 @@ async function pollUntilReady(id, spinner) {
417
767
  }
418
768
  throw new Error("Timed out waiting for database to become ready (2 min).");
419
769
  }
420
- var dbListCommand = new import_commander.Command("list").description("List all databases").action(async () => {
770
+ var dbListCommand = new import_commander2.Command("list").description("List all databases").action(async () => {
421
771
  requireAuth();
422
- const spinner = (0, import_ora.default)("Fetching databases...").start();
772
+ const spinner = (0, import_ora2.default)("Fetching databases...").start();
423
773
  try {
424
774
  const dbs = await api.listDatabases();
425
775
  spinner.stop();
426
776
  if (dbs.length === 0) {
427
- console.log(import_chalk.default.dim("\n No databases found.\n"));
777
+ console.log(import_chalk2.default.dim("\n No databases found.\n"));
428
778
  return;
429
779
  }
430
780
  const colName = Math.max(4, ...dbs.map((d) => d.name.length));
431
781
  const colStatus = Math.max(6, ...dbs.map((d) => d.status.length));
432
782
  const colTier = Math.max(4, ...dbs.map((d) => d.tier.length));
433
783
  console.log("");
434
- console.log(import_chalk.default.bold(" Databases"));
435
- console.log(import_chalk.default.dim(` ${"\u2500".repeat(colName + colStatus + colTier + 20)}`));
784
+ console.log(import_chalk2.default.bold(" Databases"));
785
+ console.log(import_chalk2.default.dim(` ${"\u2500".repeat(colName + colStatus + colTier + 20)}`));
436
786
  console.log(
437
- import_chalk.default.dim(
787
+ import_chalk2.default.dim(
438
788
  ` ${"NAME".padEnd(colName)} ${"STATUS".padEnd(colStatus)} ${"TIER".padEnd(colTier)} ID`
439
789
  )
440
790
  );
441
- console.log(import_chalk.default.dim(` ${"\u2500".repeat(colName + colStatus + colTier + 20)}`));
791
+ console.log(import_chalk2.default.dim(` ${"\u2500".repeat(colName + colStatus + colTier + 20)}`));
442
792
  for (const db of dbs) {
443
793
  console.log(
444
- ` ${import_chalk.default.cyan(db.name.padEnd(colName))} ${statusColour(db.status).padEnd(colStatus + 10)} ${db.tier.padEnd(colTier)} ${import_chalk.default.dim(db.id)}`
794
+ ` ${import_chalk2.default.cyan(db.name.padEnd(colName))} ${statusColour(db.status).padEnd(colStatus + 10)} ${db.tier.padEnd(colTier)} ${import_chalk2.default.dim(db.id)}`
445
795
  );
446
796
  }
447
797
  console.log("");
448
798
  } catch (err) {
449
- spinner.fail(import_chalk.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
799
+ spinner.fail(import_chalk2.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
450
800
  process.exit(1);
451
801
  }
452
802
  });
453
- var dbCreateCommand = new import_commander.Command("create").description("Provision a new database").argument("<name>", "Database name").option("--tier <tier>", "Database tier: standard or performance (default: standard)", "standard").option("--storage <gb>", "Storage in GB (default: 10)", "10").option("--env <env>", "Environment (default: production)", "production").action(async (name, opts) => {
803
+ var dbCreateCommand = new import_commander2.Command("create").description("Provision a new database").argument("<name>", "Database name").option("--tier <tier>", "Database tier: standard or performance (default: standard)", "standard").option("--storage <gb>", "Storage in GB (default: 10)", "10").option("--env <env>", "Environment (default: production)", "production").action(async (name, opts) => {
454
804
  requireAuth();
455
805
  const storageGb = Number.parseInt(opts.storage, 10);
456
806
  if (Number.isNaN(storageGb) || storageGb < 5) {
457
- console.log(import_chalk.default.red("Storage must be at least 5 GB."));
807
+ console.log(import_chalk2.default.red("Storage must be at least 5 GB."));
458
808
  process.exit(1);
459
809
  }
460
810
  const tier = opts.tier === "pro" ? "performance" : opts.tier;
461
- const spinner = (0, import_ora.default)(
462
- `Provisioning database ${import_chalk.default.bold(name)} (${tier}, ${storageGb} GB)...`
811
+ const spinner = (0, import_ora2.default)(
812
+ `Provisioning database ${import_chalk2.default.bold(name)} (${tier}, ${storageGb} GB)...`
463
813
  ).start();
464
814
  try {
465
815
  const db = await api.createDatabase({ name, env: opts.env, tier, storageGb });
466
816
  spinner.text = `Waiting for database to become ready...`;
467
817
  const ready = await pollUntilReady(db.id, spinner);
468
- spinner.succeed(import_chalk.default.green(`\u2713 Database ${import_chalk.default.bold(ready.name)} is ready`));
818
+ spinner.succeed(import_chalk2.default.green(`\u2713 Database ${import_chalk2.default.bold(ready.name)} is ready`));
469
819
  printDatabase(ready);
470
820
  } catch (err) {
471
- spinner.fail(import_chalk.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
821
+ spinner.fail(import_chalk2.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
472
822
  process.exit(1);
473
823
  }
474
824
  });
475
- var dbGetCommand = new import_commander.Command("get").description("Get database details including connection string").argument("<id>", "Database ID").action(async (id) => {
825
+ var dbGetCommand = new import_commander2.Command("get").description("Get database details including connection string").argument("<id>", "Database ID").action(async (id) => {
476
826
  requireAuth();
477
- const spinner = (0, import_ora.default)("Fetching database...").start();
827
+ const spinner = (0, import_ora2.default)("Fetching database...").start();
478
828
  try {
479
829
  const db = await api.getDatabase(id);
480
830
  spinner.stop();
481
831
  printDatabase(db);
482
832
  } catch (err) {
483
- spinner.fail(import_chalk.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
833
+ spinner.fail(import_chalk2.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
484
834
  process.exit(1);
485
835
  }
486
836
  });
487
- var dbDeleteCommand = new import_commander.Command("delete").description("Delete a database (irreversible)").argument("<id>", "Database ID").option("--force", "Skip confirmation prompt").action(async (id, opts) => {
837
+ var dbDeleteCommand = new import_commander2.Command("delete").description("Delete a database (irreversible)").argument("<id>", "Database ID").option("--force", "Skip confirmation prompt").action(async (id, opts) => {
488
838
  requireAuth();
489
839
  if (!opts.force) {
490
840
  console.log("");
491
- console.log(import_chalk.default.red(` \u26A0 You are about to PERMANENTLY delete database ${import_chalk.default.bold(id)}.`));
492
- console.log(import_chalk.default.red(" This deletes all data, backups, and PVCs. IRREVERSIBLE."));
841
+ console.log(import_chalk2.default.red(` \u26A0 You are about to PERMANENTLY delete database ${import_chalk2.default.bold(id)}.`));
842
+ console.log(import_chalk2.default.red(" This deletes all data, backups, and PVCs. IRREVERSIBLE."));
493
843
  console.log("");
494
844
  const answer = await prompt(" Type the database ID to confirm");
495
845
  if (answer !== id) {
496
- console.log(import_chalk.default.red("Confirmation did not match. Aborted."));
846
+ console.log(import_chalk2.default.red("Confirmation did not match. Aborted."));
497
847
  process.exit(1);
498
848
  }
499
849
  }
500
- const spinner = (0, import_ora.default)(`Deleting database ${import_chalk.default.bold(id)}...`).start();
850
+ const spinner = (0, import_ora2.default)(`Deleting database ${import_chalk2.default.bold(id)}...`).start();
501
851
  try {
502
852
  await api.deleteDatabase(id);
503
- spinner.succeed(import_chalk.default.green(`\u2713 Deleted database ${import_chalk.default.bold(id)}`));
853
+ spinner.succeed(import_chalk2.default.green(`\u2713 Deleted database ${import_chalk2.default.bold(id)}`));
504
854
  } catch (err) {
505
- spinner.fail(import_chalk.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
855
+ spinner.fail(import_chalk2.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
506
856
  process.exit(1);
507
857
  }
508
858
  });
509
- var dbBranchCommand = new import_commander.Command("branch").description("Branch a database for staging or preview").argument("<id>", "Source database ID").option("--name <name>", "Branch database name").option("--env <env>", "Target environment (default: staging)", "staging").action(async (id, opts) => {
859
+ var dbBranchCommand = new import_commander2.Command("branch").description("Branch a database for staging or preview").argument("<id>", "Source database ID").option("--name <name>", "Branch database name").option("--env <env>", "Target environment (default: staging)", "staging").action(async (id, opts) => {
510
860
  requireAuth();
511
861
  const branchName = opts.name ?? `${opts.env}-branch`;
512
- const spinner = (0, import_ora.default)(
513
- `Branching database ${import_chalk.default.bold(id)} \u2192 ${import_chalk.default.bold(branchName)}...`
862
+ const spinner = (0, import_ora2.default)(
863
+ `Branching database ${import_chalk2.default.bold(id)} \u2192 ${import_chalk2.default.bold(branchName)}...`
514
864
  ).start();
515
865
  try {
516
866
  const db = await api.branchDatabase(id, { name: branchName, env: opts.env, fast: true });
517
867
  spinner.text = `Waiting for branch to become ready...`;
518
868
  const ready = await pollUntilReady(db.id, spinner);
519
- spinner.succeed(import_chalk.default.green(`\u2713 Branch ${import_chalk.default.bold(ready.name)} is ready`));
869
+ spinner.succeed(import_chalk2.default.green(`\u2713 Branch ${import_chalk2.default.bold(ready.name)} is ready`));
520
870
  printDatabase(ready);
521
871
  } catch (err) {
522
- spinner.fail(import_chalk.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
872
+ spinner.fail(import_chalk2.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
523
873
  process.exit(1);
524
874
  }
525
875
  });
526
- var dbCommand = new import_commander.Command("db").description("Manage PostgreSQL databases").addCommand(dbListCommand).addCommand(dbCreateCommand).addCommand(dbGetCommand).addCommand(dbDeleteCommand).addCommand(dbBranchCommand);
876
+ var dbCommand = new import_commander2.Command("db").description("Manage PostgreSQL databases").addCommand(dbListCommand).addCommand(dbCreateCommand).addCommand(dbGetCommand).addCommand(dbDeleteCommand).addCommand(dbBranchCommand);
527
877
 
528
878
  // src/commands/deploy.ts
529
- var import_chalk3 = __toESM(require("chalk"));
530
- var import_commander2 = require("commander");
531
- var import_ora2 = __toESM(require("ora"));
879
+ var import_chalk4 = __toESM(require("chalk"));
880
+ var import_commander3 = require("commander");
881
+ var import_ora3 = __toESM(require("ora"));
532
882
 
533
883
  // src/utils/logs.ts
534
- var import_chalk2 = __toESM(require("chalk"));
884
+ var import_chalk3 = __toESM(require("chalk"));
885
+ var { version: version2 } = package_default;
535
886
  function formatLogEntry(raw) {
536
887
  let entry;
537
888
  try {
538
889
  entry = JSON.parse(raw);
539
890
  } catch {
540
- return ` ${import_chalk2.default.white(raw)}`;
891
+ return ` ${import_chalk3.default.white(raw)}`;
541
892
  }
542
893
  const message = entry.content ?? entry.message ?? entry.msg ?? raw;
543
894
  const level = (entry.level ?? "info").toLowerCase();
544
895
  const timestamp = entry.timestamp ?? entry.ts;
545
- const prefix = timestamp ? `${import_chalk2.default.dim(`[${new Date(timestamp).toLocaleTimeString()}]`)} ` : "";
896
+ const prefix = timestamp ? `${import_chalk3.default.dim(`[${new Date(timestamp).toLocaleTimeString()}]`)} ` : "";
546
897
  const status = entry.status?.toLowerCase();
547
898
  const effectiveLevel = status === "error" || status === "failed" ? "error" : status === "success" || status === "done" ? "success" : level;
548
899
  switch (effectiveLevel) {
549
900
  case "error":
550
- return ` ${prefix}${import_chalk2.default.red("\u2717")} ${import_chalk2.default.red(message)}`;
901
+ return ` ${prefix}${import_chalk3.default.red("\u2717")} ${import_chalk3.default.red(message)}`;
551
902
  case "warn":
552
- return ` ${prefix}${import_chalk2.default.yellow("\u26A0")} ${import_chalk2.default.yellow(message)}`;
903
+ return ` ${prefix}${import_chalk3.default.yellow("\u26A0")} ${import_chalk3.default.yellow(message)}`;
553
904
  case "success":
554
- return ` ${prefix}${import_chalk2.default.green("\u2713")} ${import_chalk2.default.green(message)}`;
905
+ return ` ${prefix}${import_chalk3.default.green("\u2713")} ${import_chalk3.default.green(message)}`;
555
906
  case "debug":
556
- return ` ${prefix}${import_chalk2.default.dim(message)}`;
907
+ return ` ${prefix}${import_chalk3.default.dim(message)}`;
557
908
  default:
558
- return ` ${prefix}${import_chalk2.default.white(message)}`;
909
+ return ` ${prefix}${import_chalk3.default.white(message)}`;
559
910
  }
560
911
  }
561
912
  function streamLogs(url, token, opts = {}) {
562
913
  return new Promise((resolve) => {
563
914
  const EventSource = require("eventsource");
564
915
  const headers = {
565
- "User-Agent": "sylphx-cli/0.1.0",
916
+ "User-Agent": `sylphx-cli/${version2}`,
566
917
  Accept: "text/event-stream"
567
918
  };
568
919
  if (token) {
@@ -615,7 +966,7 @@ function streamLogs(url, token, opts = {}) {
615
966
  es.addEventListener("error", (event) => {
616
967
  const errorEvent = event;
617
968
  if (errorEvent.message) {
618
- console.log(import_chalk2.default.red(` Stream error: ${errorEvent.message}`));
969
+ console.log(import_chalk3.default.red(` Stream error: ${errorEvent.message}`));
619
970
  }
620
971
  if (opts.exitOnDone) {
621
972
  finish(succeeded);
@@ -631,40 +982,40 @@ function streamLogs(url, token, opts = {}) {
631
982
  }
632
983
 
633
984
  // src/commands/deploy.ts
634
- var deployCommand = new import_commander2.Command("deploy").description("Trigger a deployment for the linked app").option("--env <env>", "Environment to deploy to (e.g. production, staging)").action(async (opts) => {
985
+ var deployCommand = new import_commander3.Command("deploy").description("Trigger a deployment for the linked app").option("--env <env>", "Environment to deploy to (e.g. production, staging)").action(async (opts) => {
635
986
  const token = config.getToken();
636
987
  if (!token) {
637
- console.log(import_chalk3.default.red("Not authenticated. Run `sylphx login` first."));
988
+ console.log(import_chalk4.default.red("Not authenticated. Run `sylphx login` first."));
638
989
  process.exit(1);
639
990
  }
640
991
  const linked = config.getLinkedApp();
641
992
  if (!linked) {
642
- console.log(import_chalk3.default.red("No app linked. Run `sylphx link` first."));
993
+ console.log(import_chalk4.default.red("No app linked. Run `sylphx link` first."));
643
994
  process.exit(1);
644
995
  }
645
996
  const env = opts.env ?? linked.defaultEnv;
646
997
  console.log("");
647
- console.log(import_chalk3.default.bold(` Deploying ${import_chalk3.default.cyan(linked.appId)} \u2192 ${import_chalk3.default.white(env)}`));
998
+ console.log(import_chalk4.default.bold(` Deploying ${import_chalk4.default.cyan(linked.appId)} \u2192 ${import_chalk4.default.white(env)}`));
648
999
  console.log("");
649
- const spinner = (0, import_ora2.default)("Triggering deployment...").start();
1000
+ const spinner = (0, import_ora3.default)("Triggering deployment...").start();
650
1001
  let deploymentId;
651
1002
  try {
652
1003
  const result = await api.triggerDeploy(linked.appId, env);
653
- spinner.succeed(import_chalk3.default.green(`Deployment triggered: ${result.status}`));
1004
+ spinner.succeed(import_chalk4.default.green(`Deployment triggered: ${result.status}`));
654
1005
  deploymentId = result.deploymentId;
655
1006
  if (deploymentId) {
656
- console.log(import_chalk3.default.dim(` Deployment ID: ${deploymentId}`));
1007
+ console.log(import_chalk4.default.dim(` Deployment ID: ${deploymentId}`));
657
1008
  }
658
1009
  if (result.environment) {
659
- console.log(import_chalk3.default.dim(` Environment: ${result.environment}`));
1010
+ console.log(import_chalk4.default.dim(` Environment: ${result.environment}`));
660
1011
  }
661
1012
  } catch (err) {
662
- spinner.fail(import_chalk3.default.red(`Deploy failed: ${err instanceof Error ? err.message : String(err)}`));
1013
+ spinner.fail(import_chalk4.default.red(`Deploy failed: ${err instanceof Error ? err.message : String(err)}`));
663
1014
  process.exit(1);
664
1015
  }
665
1016
  console.log("");
666
- console.log(import_chalk3.default.bold(" Build Logs"));
667
- console.log(import_chalk3.default.dim(` ${"\u2500".repeat(50)}`));
1017
+ console.log(import_chalk4.default.bold(" Build Logs"));
1018
+ console.log(import_chalk4.default.dim(` ${"\u2500".repeat(50)}`));
668
1019
  console.log("");
669
1020
  const logStream = createLogStream(linked.appId, env);
670
1021
  const success = await streamLogs(logStream.url, logStream.token, {
@@ -673,140 +1024,387 @@ var deployCommand = new import_commander2.Command("deploy").description("Trigger
673
1024
  });
674
1025
  if (!success) {
675
1026
  console.log("");
676
- console.log(import_chalk3.default.red(" Deployment failed."));
1027
+ console.log(import_chalk4.default.red(" Deployment failed."));
677
1028
  process.exit(1);
678
1029
  }
679
1030
  console.log("");
680
- console.log(import_chalk3.default.green.bold(" \u2713 Deployment successful!"));
1031
+ console.log(import_chalk4.default.green.bold(" \u2713 Deployment successful!"));
681
1032
  console.log("");
682
1033
  });
683
1034
 
684
1035
  // src/commands/domains.ts
685
- var import_chalk4 = __toESM(require("chalk"));
686
- var import_commander3 = require("commander");
687
- var import_ora3 = __toESM(require("ora"));
688
- function requireLinkedApp() {
1036
+ var import_chalk5 = __toESM(require("chalk"));
1037
+ var import_commander4 = require("commander");
1038
+ var import_ora4 = __toESM(require("ora"));
1039
+ function requireLinkedApp2() {
689
1040
  const token = config.getToken();
690
1041
  if (!token) {
691
- console.log(import_chalk4.default.red("Not authenticated. Run `sylphx login` first."));
1042
+ console.error(import_chalk5.default.red("Not authenticated. Run `sylphx login` first."));
692
1043
  process.exit(1);
693
1044
  }
694
1045
  const linked = config.getLinkedApp();
695
1046
  if (!linked) {
696
- console.log(import_chalk4.default.red("No app linked. Run `sylphx link` first."));
1047
+ console.error(import_chalk5.default.red("No project linked. Run `sylphx link` first."));
697
1048
  process.exit(1);
698
1049
  }
699
1050
  return linked;
700
1051
  }
701
- var domainsListCommand = new import_commander3.Command("list").description("List custom domains").option("--env <env>", "Environment (e.g. production, staging)").action(async (opts) => {
702
- const linked = requireLinkedApp();
703
- const env = opts.env ?? linked.defaultEnv;
704
- const spinner = (0, import_ora3.default)(`Fetching domains [${env}]...`).start();
1052
+ function extractApex(hostname) {
1053
+ const parts = hostname.split(".");
1054
+ return parts.length > 2 ? parts.slice(-2).join(".") : hostname;
1055
+ }
1056
+ function findDomainByApex(domains, apex) {
1057
+ return domains.find((d) => d.apexDomain === apex);
1058
+ }
1059
+ function findHostname(domain, hostname) {
1060
+ return domain.hostnames.find((h) => h.hostname === hostname);
1061
+ }
1062
+ function dnsStatusBadge(status) {
1063
+ switch (status) {
1064
+ case "active":
1065
+ case "verified":
1066
+ return import_chalk5.default.green("\u2713 active");
1067
+ case "verifying":
1068
+ return import_chalk5.default.yellow("\u27F3 verifying");
1069
+ case "pending":
1070
+ return import_chalk5.default.dim("\u25CB pending");
1071
+ case "error":
1072
+ case "failed":
1073
+ return import_chalk5.default.red("\u2717 failed");
1074
+ default:
1075
+ return import_chalk5.default.dim(status);
1076
+ }
1077
+ }
1078
+ function printDnsRecord(record) {
1079
+ const statusBadge = record.status ? ` ${dnsStatusBadge(record.status)}` : "";
1080
+ console.log(import_chalk5.default.dim(` ${record.type.padEnd(5)} ${import_chalk5.default.cyan(record.name)}`));
1081
+ console.log(import_chalk5.default.dim(` \u2192 ${record.value}${statusBadge}`));
1082
+ if (record.description) console.log(import_chalk5.default.dim(` ${import_chalk5.default.italic(record.description)}`));
1083
+ }
1084
+ function printHostname(h, indent = " ") {
1085
+ const primary = h.isPrimary ? import_chalk5.default.green(" [primary]") : "";
1086
+ const redirect = h.redirectTo ? import_chalk5.default.dim(` \u2192 ${h.redirectTo}`) : "";
1087
+ console.log(
1088
+ `${indent}${import_chalk5.default.bold(h.hostname)}${primary}${redirect} ${dnsStatusBadge(h.dnsStatus)}`
1089
+ );
1090
+ if (h.dnsStatus !== "active") {
1091
+ console.log(
1092
+ `${indent} ${import_chalk5.default.dim(h.instruction.type)} record: ${import_chalk5.default.cyan(h.instruction.name)} \u2192 ${h.instruction.value}`
1093
+ );
1094
+ }
1095
+ if (h.lastCheckError) {
1096
+ console.log(`${indent} ${import_chalk5.default.red(h.lastCheckError)}`);
1097
+ }
1098
+ }
1099
+ function printEmailBinding(email, indent = " ") {
1100
+ console.log(
1101
+ `${indent}Email: ${dnsStatusBadge(email.dnsStatus)}${email.stalwartLoaded ? import_chalk5.default.dim(" (DKIM loaded)") : ""}`
1102
+ );
1103
+ if (email.defaultFromEmail) {
1104
+ console.log(
1105
+ `${indent} From: ${email.defaultFromEmail}${email.defaultFromName ? ` <${email.defaultFromName}>` : ""}`
1106
+ );
1107
+ }
1108
+ if (email.dnsStatus !== "verified" && email.records.length > 0) {
1109
+ console.log(`${indent} ${import_chalk5.default.dim("DNS records to add:")}`);
1110
+ for (const rec of email.records) {
1111
+ printDnsRecord({ ...rec, status: rec.status ?? void 0 });
1112
+ }
1113
+ }
1114
+ }
1115
+ function printDomainResult(d) {
1116
+ console.log("");
1117
+ console.log(` ${import_chalk5.default.bold.white(d.apexDomain)}`);
1118
+ if (d.hostnames.length === 0) {
1119
+ console.log(import_chalk5.default.dim(" No hostname bindings"));
1120
+ } else {
1121
+ for (const h of d.hostnames) {
1122
+ printHostname(h, " ");
1123
+ }
1124
+ }
1125
+ if (d.email) {
1126
+ printEmailBinding(d.email, " ");
1127
+ }
1128
+ }
1129
+ var domainsListCommand = new import_commander4.Command("list").description("List all domains and their status").option("--env <env>", "Environment (production, staging, development)", "production").action(async (opts) => {
1130
+ const linked = requireLinkedApp2();
1131
+ const spinner = (0, import_ora4.default)("Fetching domains...").start();
705
1132
  try {
706
- const domains = await api.listDomains(linked.appId, env);
1133
+ const domains = await api.listDomains(linked.appId, opts.env);
707
1134
  spinner.stop();
708
1135
  if (domains.length === 0) {
709
- console.log(import_chalk4.default.dim("\n No custom domains configured.\n"));
1136
+ console.log(import_chalk5.default.dim("\n No domains configured. Run `sylphx domains add <hostname>`\n"));
710
1137
  return;
711
1138
  }
712
1139
  console.log("");
713
- console.log(import_chalk4.default.bold(` Custom Domains [${import_chalk4.default.white(env)}]`));
714
- console.log(import_chalk4.default.dim(` ${"\u2500".repeat(50)}`));
715
- console.log("");
1140
+ console.log(import_chalk5.default.bold(` Domains [${import_chalk5.default.white(opts.env)}]`));
1141
+ console.log(import_chalk5.default.dim(` ${"\u2500".repeat(50)}`));
716
1142
  for (const d of domains) {
717
- const status = d.status ? import_chalk4.default.dim(` [${d.status}]`) : "";
718
- const ssl = d.ssl ? import_chalk4.default.green(" \u{1F512}") : import_chalk4.default.yellow(" \u26A0 no SSL");
719
- console.log(` ${import_chalk4.default.cyan(d.domain)}${status}${ssl}`);
1143
+ printDomainResult(d);
720
1144
  }
721
1145
  console.log("");
722
1146
  } catch (err) {
723
- spinner.fail(import_chalk4.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
1147
+ spinner.fail(import_chalk5.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
724
1148
  process.exit(1);
725
1149
  }
726
1150
  });
727
- var domainsAddCommand = new import_commander3.Command("add").description("Add a custom domain").argument("<domain>", "Domain to add (e.g. example.com)").option("--env <env>", "Environment (e.g. production, staging)").action(async (domain, opts) => {
728
- const linked = requireLinkedApp();
729
- const env = opts.env ?? linked.defaultEnv;
730
- const spinner = (0, import_ora3.default)(`Adding domain ${domain} [${env}]...`).start();
1151
+ var domainsAddCommand = new import_commander4.Command("add").description("Add a hostname binding (auto-registers apex domain if needed)").argument("<hostname>", "Hostname to add (e.g. app.example.com or example.com)").option("--env <env>", "Environment", "production").option("--primary", "Set as primary domain", false).option("--redirect <url>", "Redirect to this URL instead of serving traffic").action(async (hostname, opts) => {
1152
+ const linked = requireLinkedApp2();
1153
+ const apex = extractApex(hostname);
1154
+ const spinner = (0, import_ora4.default)(`Setting up ${hostname}...`).start();
731
1155
  try {
732
- const result = await api.addDomain(linked.appId, domain, env);
733
- spinner.succeed(import_chalk4.default.green(`\u2713 Added domain ${import_chalk4.default.bold(result.domain ?? domain)}`));
734
- if (result.status) {
735
- console.log(import_chalk4.default.dim(` Status: ${result.status}`));
1156
+ const domains = await api.listDomains(linked.appId, opts.env);
1157
+ let domain = findDomainByApex(domains, apex);
1158
+ if (!domain) {
1159
+ spinner.text = `Registering apex domain ${apex}...`;
1160
+ domain = await api.createDomain(linked.appId, apex, opts.env);
736
1161
  }
1162
+ spinner.text = `Adding hostname binding ${hostname}...`;
1163
+ const h = await api.addHostname(linked.appId, domain.id, hostname, {
1164
+ isPrimary: opts.primary,
1165
+ redirectTo: opts.redirect,
1166
+ envType: opts.env
1167
+ });
1168
+ spinner.succeed(import_chalk5.default.green(`Added ${import_chalk5.default.bold(hostname)}`));
1169
+ console.log("");
1170
+ console.log(import_chalk5.default.bold(" DNS Record to add:"));
1171
+ console.log("");
1172
+ console.log(
1173
+ ` ${import_chalk5.default.dim(h.instruction.type.padEnd(5))} ${import_chalk5.default.cyan(h.instruction.name)}`
1174
+ );
1175
+ console.log(` ${import_chalk5.default.dim("value:")} ${h.instruction.value}`);
1176
+ console.log(` ${import_chalk5.default.dim("TTL:")} ${h.instruction.ttl}`);
1177
+ console.log("");
1178
+ console.log(import_chalk5.default.dim(` Status: ${dnsStatusBadge(h.dnsStatus)}`));
1179
+ console.log(import_chalk5.default.dim(` Run \`sylphx domains verify ${hostname}\` after updating DNS.`));
1180
+ console.log("");
737
1181
  } catch (err) {
738
- spinner.fail(import_chalk4.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
1182
+ spinner.fail(import_chalk5.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
739
1183
  process.exit(1);
740
1184
  }
741
1185
  });
742
- var domainsRmCommand = new import_commander3.Command("rm").description("Remove a custom domain").argument("<domain>", "Domain to remove (e.g. example.com)").option("--env <env>", "Environment (e.g. production, staging)").action(async (domain, opts) => {
743
- const linked = requireLinkedApp();
744
- const env = opts.env ?? linked.defaultEnv;
745
- const spinner = (0, import_ora3.default)(`Removing domain ${domain} [${env}]...`).start();
1186
+ var domainsRmCommand = new import_commander4.Command("rm").description("Remove a hostname binding").argument("<hostname>", "Hostname to remove").option("--env <env>", "Environment", "production").action(async (hostname, opts) => {
1187
+ const linked = requireLinkedApp2();
1188
+ const apex = extractApex(hostname);
1189
+ const spinner = (0, import_ora4.default)(`Removing ${hostname}...`).start();
746
1190
  try {
747
- await api.removeDomain(linked.appId, domain, env);
748
- spinner.succeed(import_chalk4.default.green(`\u2713 Removed domain ${import_chalk4.default.bold(domain)}`));
1191
+ const domains = await api.listDomains(linked.appId, opts.env);
1192
+ const domain = findDomainByApex(domains, apex);
1193
+ if (!domain) {
1194
+ spinner.fail(import_chalk5.default.red(`Domain ${apex} not found`));
1195
+ process.exit(1);
1196
+ }
1197
+ const h = findHostname(domain, hostname);
1198
+ if (!h) {
1199
+ spinner.fail(import_chalk5.default.red(`Hostname ${hostname} not found`));
1200
+ process.exit(1);
1201
+ }
1202
+ await api.removeHostname(linked.appId, domain.id, h.id);
1203
+ spinner.succeed(import_chalk5.default.green(`Removed ${import_chalk5.default.bold(hostname)}`));
749
1204
  } catch (err) {
750
- spinner.fail(import_chalk4.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
1205
+ spinner.fail(import_chalk5.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
751
1206
  process.exit(1);
752
1207
  }
753
1208
  });
754
- var domainsCommand = new import_commander3.Command("domains").description("Manage custom domains").addCommand(domainsListCommand).addCommand(domainsAddCommand).addCommand(domainsRmCommand);
755
-
756
- // src/commands/env.ts
757
- var import_node_fs = __toESM(require("fs"));
758
- var import_node_path2 = __toESM(require("path"));
759
- var import_chalk5 = __toESM(require("chalk"));
760
- var import_commander4 = require("commander");
761
- var import_ora4 = __toESM(require("ora"));
762
- function requireLinkedApp2() {
1209
+ var domainsVerifyCommand = new import_commander4.Command("verify").description("Check DNS propagation for a hostname").argument("<hostname>", "Hostname to check").option("--env <env>", "Environment", "production").action(async (hostname, opts) => {
1210
+ const linked = requireLinkedApp2();
1211
+ const apex = extractApex(hostname);
1212
+ const spinner = (0, import_ora4.default)(`Checking DNS for ${hostname}...`).start();
1213
+ try {
1214
+ const domains = await api.listDomains(linked.appId, opts.env);
1215
+ const domain = findDomainByApex(domains, apex);
1216
+ if (!domain) {
1217
+ spinner.fail(
1218
+ import_chalk5.default.red(`Domain ${apex} not found. Run \`sylphx domains add ${hostname}\` first.`)
1219
+ );
1220
+ process.exit(1);
1221
+ }
1222
+ const h = findHostname(domain, hostname);
1223
+ if (!h) {
1224
+ spinner.fail(
1225
+ import_chalk5.default.red(
1226
+ `Hostname ${hostname} not registered. Run \`sylphx domains add ${hostname}\` first.`
1227
+ )
1228
+ );
1229
+ process.exit(1);
1230
+ }
1231
+ const updated = await api.checkHostname(linked.appId, domain.id, h.id);
1232
+ spinner.stop();
1233
+ if (updated.dnsStatus === "active") {
1234
+ console.log(import_chalk5.default.green(`
1235
+ \u2713 ${hostname} is active
1236
+ `));
1237
+ } else {
1238
+ console.log(import_chalk5.default.yellow(`
1239
+ \u26A0 ${hostname} \u2014 ${dnsStatusBadge(updated.dnsStatus)}`));
1240
+ if (updated.lastCheckError) console.log(import_chalk5.default.dim(` ${updated.lastCheckError}`));
1241
+ console.log("");
1242
+ console.log(import_chalk5.default.bold(" DNS record required:"));
1243
+ console.log(
1244
+ ` ${updated.instruction.type} ${import_chalk5.default.cyan(updated.instruction.name)} \u2192 ${updated.instruction.value}`
1245
+ );
1246
+ console.log("");
1247
+ }
1248
+ } catch (err) {
1249
+ spinner.fail(import_chalk5.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
1250
+ process.exit(1);
1251
+ }
1252
+ });
1253
+ var emailEnableCommand = new import_commander4.Command("enable").description("Enable DKIM email sending for a domain").argument("<apex-domain>", "Apex domain to enable email for (e.g. example.com)").option("--from <email>", "Default from address (e.g. support@example.com)").option("--name <name>", 'Default sender display name (e.g. "Acme Support")').option("--env <env>", "Environment", "production").action(async (apexDomain, opts) => {
1254
+ const linked = requireLinkedApp2();
1255
+ const spinner = (0, import_ora4.default)(`Enabling email for ${apexDomain}...`).start();
1256
+ try {
1257
+ const domains = await api.listDomains(linked.appId, opts.env);
1258
+ let domain = findDomainByApex(domains, apexDomain);
1259
+ if (!domain) {
1260
+ spinner.text = `Registering ${apexDomain}...`;
1261
+ domain = await api.createDomain(linked.appId, apexDomain, opts.env);
1262
+ }
1263
+ if (domain.email) {
1264
+ spinner.warn(
1265
+ import_chalk5.default.yellow(
1266
+ `Email already enabled for ${apexDomain}. Use \`sylphx domains email verify\` to check status.`
1267
+ )
1268
+ );
1269
+ return;
1270
+ }
1271
+ const binding = await api.enableEmail(linked.appId, domain.id, {
1272
+ defaultFromEmail: opts.from,
1273
+ defaultFromName: opts.name
1274
+ });
1275
+ spinner.succeed(import_chalk5.default.green(`Email enabled for ${import_chalk5.default.bold(apexDomain)}`));
1276
+ console.log("");
1277
+ console.log(import_chalk5.default.bold(" DNS Records to add:"));
1278
+ console.log("");
1279
+ for (const rec of binding.records) {
1280
+ console.log(` ${import_chalk5.default.dim(rec.type.padEnd(5))} ${import_chalk5.default.cyan(rec.name)}`);
1281
+ console.log(` ${import_chalk5.default.dim("value:")} ${rec.value}`);
1282
+ if (rec.priority !== void 0) console.log(` ${import_chalk5.default.dim("priority:")} ${rec.priority}`);
1283
+ console.log(` ${import_chalk5.default.dim("TTL:")} ${rec.ttl}`);
1284
+ if (rec.description) console.log(` ${import_chalk5.default.dim(rec.description)}`);
1285
+ console.log("");
1286
+ }
1287
+ console.log(
1288
+ import_chalk5.default.dim(` Run \`sylphx domains email verify ${apexDomain}\` after updating DNS.`)
1289
+ );
1290
+ console.log("");
1291
+ } catch (err) {
1292
+ spinner.fail(import_chalk5.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
1293
+ process.exit(1);
1294
+ }
1295
+ });
1296
+ var emailVerifyCommand = new import_commander4.Command("verify").description("Check DKIM DNS propagation").argument("<apex-domain>", "Apex domain to verify").option("--env <env>", "Environment", "production").action(async (apexDomain, opts) => {
1297
+ const linked = requireLinkedApp2();
1298
+ const spinner = (0, import_ora4.default)(`Checking DKIM DNS for ${apexDomain}...`).start();
1299
+ try {
1300
+ const domains = await api.listDomains(linked.appId, opts.env);
1301
+ const domain = findDomainByApex(domains, apexDomain);
1302
+ if (!domain?.email) {
1303
+ spinner.fail(
1304
+ import_chalk5.default.red(
1305
+ `Email not enabled for ${apexDomain}. Run \`sylphx domains email enable ${apexDomain}\` first.`
1306
+ )
1307
+ );
1308
+ process.exit(1);
1309
+ }
1310
+ const binding = await api.checkEmail(linked.appId, domain.id);
1311
+ spinner.stop();
1312
+ if (binding.dnsStatus === "verified") {
1313
+ console.log(import_chalk5.default.green(`
1314
+ \u2713 ${apexDomain} email verified \u2014 DKIM active
1315
+ `));
1316
+ } else {
1317
+ console.log(import_chalk5.default.yellow(`
1318
+ ${apexDomain} \u2014 ${dnsStatusBadge(binding.dnsStatus)}`));
1319
+ if (!binding.stalwartLoaded)
1320
+ console.log(import_chalk5.default.dim(" (DKIM not yet loaded into mail server)"));
1321
+ if (binding.verificationError) console.log(import_chalk5.default.red(` ${binding.verificationError}`));
1322
+ console.log("");
1323
+ console.log(import_chalk5.default.bold(" DNS records still pending:"));
1324
+ for (const rec of binding.records.filter((r) => r.status !== "verified")) {
1325
+ printDnsRecord({ ...rec, status: rec.status ?? void 0 });
1326
+ }
1327
+ console.log("");
1328
+ }
1329
+ } catch (err) {
1330
+ spinner.fail(import_chalk5.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
1331
+ process.exit(1);
1332
+ }
1333
+ });
1334
+ var emailDisableCommand = new import_commander4.Command("disable").description("Disable email sending for a domain").argument("<apex-domain>", "Apex domain").option("--env <env>", "Environment", "production").action(async (apexDomain, opts) => {
1335
+ const linked = requireLinkedApp2();
1336
+ const spinner = (0, import_ora4.default)(`Disabling email for ${apexDomain}...`).start();
1337
+ try {
1338
+ const domains = await api.listDomains(linked.appId, opts.env);
1339
+ const domain = findDomainByApex(domains, apexDomain);
1340
+ if (!domain?.email) {
1341
+ spinner.fail(import_chalk5.default.red(`Email not enabled for ${apexDomain}`));
1342
+ process.exit(1);
1343
+ }
1344
+ await api.disableEmail(linked.appId, domain.id);
1345
+ spinner.succeed(import_chalk5.default.green(`Email disabled for ${import_chalk5.default.bold(apexDomain)}`));
1346
+ } catch (err) {
1347
+ spinner.fail(import_chalk5.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
1348
+ process.exit(1);
1349
+ }
1350
+ });
1351
+ var domainsEmailCommand = new import_commander4.Command("email").description("Manage email sending for a domain").addCommand(emailEnableCommand).addCommand(emailVerifyCommand).addCommand(emailDisableCommand);
1352
+ var domainsCommand = new import_commander4.Command("domains").description("Manage custom domains").addCommand(domainsListCommand).addCommand(domainsAddCommand).addCommand(domainsRmCommand).addCommand(domainsVerifyCommand).addCommand(domainsEmailCommand);
1353
+
1354
+ // src/commands/env.ts
1355
+ var import_node_fs = __toESM(require("fs"));
1356
+ var import_node_path2 = __toESM(require("path"));
1357
+ var import_chalk6 = __toESM(require("chalk"));
1358
+ var import_commander5 = require("commander");
1359
+ var import_ora5 = __toESM(require("ora"));
1360
+ function requireLinkedApp3() {
763
1361
  const token = config.getToken();
764
1362
  if (!token) {
765
- console.log(import_chalk5.default.red("Not authenticated. Run `sylphx login` first."));
1363
+ console.log(import_chalk6.default.red("Not authenticated. Run `sylphx login` first."));
766
1364
  process.exit(1);
767
1365
  }
768
1366
  const linked = config.getLinkedApp();
769
1367
  if (!linked) {
770
- console.log(import_chalk5.default.red("No app linked. Run `sylphx link` first."));
1368
+ console.log(import_chalk6.default.red("No app linked. Run `sylphx link` first."));
771
1369
  process.exit(1);
772
1370
  }
773
1371
  return linked;
774
1372
  }
775
- var envListCommand = new import_commander4.Command("list").description("List environment variables").option("--env <env>", "Environment (e.g. production, staging)").action(async (opts) => {
776
- const linked = requireLinkedApp2();
1373
+ var envListCommand = new import_commander5.Command("list").description("List environment variables").option("--env <env>", "Environment (e.g. production, staging)").action(async (opts) => {
1374
+ const linked = requireLinkedApp3();
777
1375
  const env = opts.env ?? linked.defaultEnv;
778
- const spinner = (0, import_ora4.default)(`Fetching env vars [${env}]...`).start();
1376
+ const spinner = (0, import_ora5.default)(`Fetching env vars [${env}]...`).start();
779
1377
  try {
780
1378
  const vars = await api.listEnvVars(linked.appId, env);
781
1379
  spinner.stop();
782
1380
  if (vars.length === 0) {
783
- console.log(import_chalk5.default.dim("\n No environment variables set.\n"));
1381
+ console.log(import_chalk6.default.dim("\n No environment variables set.\n"));
784
1382
  return;
785
1383
  }
786
1384
  console.log("");
787
- console.log(import_chalk5.default.bold(` Environment Variables [${import_chalk5.default.white(env)}]`));
788
- console.log(import_chalk5.default.dim(` ${"\u2500".repeat(50)}`));
1385
+ console.log(import_chalk6.default.bold(` Environment Variables [${import_chalk6.default.white(env)}]`));
1386
+ console.log(import_chalk6.default.dim(` ${"\u2500".repeat(50)}`));
789
1387
  console.log("");
790
1388
  const maxKeyLen = Math.max(...vars.map((v) => v.key.length));
791
1389
  for (const v of vars) {
792
- const key = import_chalk5.default.cyan(v.key.padEnd(maxKeyLen));
1390
+ const key = import_chalk6.default.cyan(v.key.padEnd(maxKeyLen));
793
1391
  const isSecret = v.isSecret ?? v.secret;
794
- const value = isSecret ? import_chalk5.default.dim("****** (secret)") : import_chalk5.default.white(v.value);
1392
+ const value = isSecret ? import_chalk6.default.dim("****** (secret)") : import_chalk6.default.white(v.value);
795
1393
  console.log(` ${key} ${value}`);
796
1394
  }
797
1395
  console.log("");
798
1396
  } catch (err) {
799
- spinner.fail(import_chalk5.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
1397
+ spinner.fail(import_chalk6.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
800
1398
  process.exit(1);
801
1399
  }
802
1400
  });
803
- var envSetCommand = new import_commander4.Command("set").description("Set an environment variable (KEY=VALUE)").argument("<assignment>", "Key=Value assignment, e.g. DATABASE_URL=postgres://...").option("--env <env>", "Environment (e.g. production, staging)").option("--secret", "Mark as secret").action(async (assignment, opts) => {
804
- const linked = requireLinkedApp2();
1401
+ var envSetCommand = new import_commander5.Command("set").description("Set an environment variable (KEY=VALUE)").argument("<assignment>", "Key=Value assignment, e.g. DATABASE_URL=postgres://...").option("--env <env>", "Environment (e.g. production, staging)").option("--secret", "Mark as secret").action(async (assignment, opts) => {
1402
+ const linked = requireLinkedApp3();
805
1403
  const env = opts.env ?? linked.defaultEnv;
806
1404
  const eqIdx = assignment.indexOf("=");
807
1405
  if (eqIdx < 1) {
808
1406
  console.log(
809
- import_chalk5.default.red(
1407
+ import_chalk6.default.red(
810
1408
  "Invalid format. Use KEY=VALUE (e.g. sylphx env set DATABASE_URL=postgres://...)"
811
1409
  )
812
1410
  );
@@ -815,40 +1413,40 @@ var envSetCommand = new import_commander4.Command("set").description("Set an env
815
1413
  const key = assignment.slice(0, eqIdx).trim();
816
1414
  const value = assignment.slice(eqIdx + 1);
817
1415
  if (!key) {
818
- console.log(import_chalk5.default.red("Key cannot be empty."));
1416
+ console.log(import_chalk6.default.red("Key cannot be empty."));
819
1417
  process.exit(1);
820
1418
  }
821
- const spinner = (0, import_ora4.default)(`Setting ${key} [${env}]...`).start();
1419
+ const spinner = (0, import_ora5.default)(`Setting ${key} [${env}]...`).start();
822
1420
  try {
823
1421
  await api.setEnvVar(linked.appId, key, value, env, opts.secret);
824
- spinner.succeed(import_chalk5.default.green(`\u2713 Set ${import_chalk5.default.bold(key)} in ${env}`));
1422
+ spinner.succeed(import_chalk6.default.green(`\u2713 Set ${import_chalk6.default.bold(key)} in ${env}`));
825
1423
  } catch (err) {
826
- spinner.fail(import_chalk5.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
1424
+ spinner.fail(import_chalk6.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
827
1425
  process.exit(1);
828
1426
  }
829
1427
  });
830
- var envRmCommand = new import_commander4.Command("rm").description("Remove an environment variable").argument("<key>", "Variable name to remove").option("--env <env>", "Environment (e.g. production, staging)").action(async (key, opts) => {
831
- const linked = requireLinkedApp2();
1428
+ var envRmCommand = new import_commander5.Command("rm").description("Remove an environment variable").argument("<key>", "Variable name to remove").option("--env <env>", "Environment (e.g. production, staging)").action(async (key, opts) => {
1429
+ const linked = requireLinkedApp3();
832
1430
  const env = opts.env ?? linked.defaultEnv;
833
- const spinner = (0, import_ora4.default)(`Removing ${key} [${env}]...`).start();
1431
+ const spinner = (0, import_ora5.default)(`Removing ${key} [${env}]...`).start();
834
1432
  try {
835
1433
  await api.deleteEnvVar(linked.appId, key, env);
836
- spinner.succeed(import_chalk5.default.green(`\u2713 Removed ${import_chalk5.default.bold(key)} from ${env}`));
1434
+ spinner.succeed(import_chalk6.default.green(`\u2713 Removed ${import_chalk6.default.bold(key)} from ${env}`));
837
1435
  } catch (err) {
838
- spinner.fail(import_chalk5.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
1436
+ spinner.fail(import_chalk6.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
839
1437
  process.exit(1);
840
1438
  }
841
1439
  });
842
- var envPullCommand = new import_commander4.Command("pull").description("Download env vars from the platform to a local .env file").option("--env <env>", "Environment (e.g. production, staging)").option("--file <file>", ".env file path", ".env").action(async (opts) => {
843
- const linked = requireLinkedApp2();
1440
+ var envPullCommand = new import_commander5.Command("pull").description("Download env vars from the platform to a local .env file").option("--env <env>", "Environment (e.g. production, staging)").option("--file <file>", ".env file path", ".env").action(async (opts) => {
1441
+ const linked = requireLinkedApp3();
844
1442
  const env = opts.env ?? linked.defaultEnv;
845
1443
  const filePath = import_node_path2.default.resolve(opts.file);
846
- const spinner = (0, import_ora4.default)(`Pulling env vars [${env}]...`).start();
1444
+ const spinner = (0, import_ora5.default)(`Pulling env vars [${env}]...`).start();
847
1445
  try {
848
1446
  const vars = await api.listEnvVars(linked.appId, env);
849
1447
  spinner.stop();
850
1448
  if (vars.length === 0) {
851
- console.log(import_chalk5.default.dim("\n No environment variables to pull.\n"));
1449
+ console.log(import_chalk6.default.dim("\n No environment variables to pull.\n"));
852
1450
  return;
853
1451
  }
854
1452
  const lines = [
@@ -871,19 +1469,19 @@ var envPullCommand = new import_commander4.Command("pull").description("Download
871
1469
  }
872
1470
  lines.push("");
873
1471
  import_node_fs.default.writeFileSync(filePath, lines.join("\n"), "utf-8");
874
- console.log(import_chalk5.default.green(`\u2713 Wrote ${vars.length} variable(s) to ${import_chalk5.default.bold(opts.file)}`));
875
- console.log(import_chalk5.default.dim(` Secrets are shown as ${import_chalk5.default.white("KEY=")} (value omitted).`));
1472
+ console.log(import_chalk6.default.green(`\u2713 Wrote ${vars.length} variable(s) to ${import_chalk6.default.bold(opts.file)}`));
1473
+ console.log(import_chalk6.default.dim(` Secrets are shown as ${import_chalk6.default.white("KEY=")} (value omitted).`));
876
1474
  } catch (err) {
877
- spinner.fail(import_chalk5.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
1475
+ spinner.fail(import_chalk6.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
878
1476
  process.exit(1);
879
1477
  }
880
1478
  });
881
- var envPushCommand = new import_commander4.Command("push").description("Push a local .env file to the platform").option("--env <env>", "Environment (e.g. production, staging)").option("--file <file>", ".env file path", ".env").action(async (opts) => {
882
- const linked = requireLinkedApp2();
1479
+ var envPushCommand = new import_commander5.Command("push").description("Push a local .env file to the platform").option("--env <env>", "Environment (e.g. production, staging)").option("--file <file>", ".env file path", ".env").action(async (opts) => {
1480
+ const linked = requireLinkedApp3();
883
1481
  const env = opts.env ?? linked.defaultEnv;
884
1482
  const filePath = import_node_path2.default.resolve(opts.file);
885
1483
  if (!import_node_fs.default.existsSync(filePath)) {
886
- console.log(import_chalk5.default.red(`File not found: ${opts.file}`));
1484
+ console.log(import_chalk6.default.red(`File not found: ${opts.file}`));
887
1485
  process.exit(1);
888
1486
  }
889
1487
  const content = import_node_fs.default.readFileSync(filePath, "utf-8");
@@ -903,29 +1501,29 @@ var envPushCommand = new import_commander4.Command("push").description("Push a l
903
1501
  vars.push({ key, value, secret: false });
904
1502
  }
905
1503
  if (vars.length === 0) {
906
- console.log(import_chalk5.default.yellow(" No variables to push (all lines empty or comments)."));
1504
+ console.log(import_chalk6.default.yellow(" No variables to push (all lines empty or comments)."));
907
1505
  return;
908
1506
  }
909
- const spinner = (0, import_ora4.default)(`Pushing ${vars.length} variable(s) to [${env}]...`).start();
1507
+ const spinner = (0, import_ora5.default)(`Pushing ${vars.length} variable(s) to [${env}]...`).start();
910
1508
  try {
911
1509
  await api.setEnvVars(linked.appId, vars, env);
912
- spinner.succeed(import_chalk5.default.green(`\u2713 Pushed ${vars.length} variable(s) to ${import_chalk5.default.bold(env)}`));
913
- console.log(import_chalk5.default.dim(` Run ${import_chalk5.default.cyan("sylphx deploy")} to apply the new variables.`));
1510
+ spinner.succeed(import_chalk6.default.green(`\u2713 Pushed ${vars.length} variable(s) to ${import_chalk6.default.bold(env)}`));
1511
+ console.log(import_chalk6.default.dim(` Run ${import_chalk6.default.cyan("sylphx deploy")} to apply the new variables.`));
914
1512
  } catch (err) {
915
- spinner.fail(import_chalk5.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
1513
+ spinner.fail(import_chalk6.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
916
1514
  process.exit(1);
917
1515
  }
918
1516
  });
919
- var envCommand = new import_commander4.Command("env").description("Manage environment variables").addCommand(envListCommand).addCommand(envSetCommand).addCommand(envRmCommand).addCommand(envPullCommand).addCommand(envPushCommand);
1517
+ var envCommand = new import_commander5.Command("env").description("Manage environment variables").addCommand(envListCommand).addCommand(envSetCommand).addCommand(envRmCommand).addCommand(envPullCommand).addCommand(envPushCommand);
920
1518
 
921
1519
  // src/commands/init.ts
922
1520
  var import_node_child_process = require("child_process");
923
1521
  var import_node_fs2 = __toESM(require("fs"));
924
1522
  var import_node_path3 = __toESM(require("path"));
925
1523
  var import_node_readline2 = __toESM(require("readline"));
926
- var import_chalk6 = __toESM(require("chalk"));
927
- var import_commander5 = require("commander");
928
- var import_ora5 = __toESM(require("ora"));
1524
+ var import_chalk7 = __toESM(require("chalk"));
1525
+ var import_commander6 = require("commander");
1526
+ var import_ora6 = __toESM(require("ora"));
929
1527
  function prompt2(question, defaultValue) {
930
1528
  return new Promise((resolve) => {
931
1529
  const rl = import_node_readline2.default.createInterface({ input: process.stdin, output: process.stdout });
@@ -960,10 +1558,10 @@ function detectGitRemote() {
960
1558
  return void 0;
961
1559
  }
962
1560
  }
963
- var initCommand = new import_commander5.Command("init").description("Create and link a new Sylphx project in one step").argument("[name]", "Project name (detected from package.json if omitted)").option("--git", "Auto-detect git remote URL and set as gitRepository").option("--port <port>", "Application port").action(async (nameArg, opts) => {
1561
+ var initCommand = new import_commander6.Command("init").description("Create and link a new Sylphx project in one step").argument("[name]", "Project name (detected from package.json if omitted)").option("--git", "Auto-detect git remote URL and set as gitRepository").option("--port <port>", "Application port").action(async (nameArg, opts) => {
964
1562
  const token = config.getToken();
965
1563
  if (!token) {
966
- console.log(import_chalk6.default.red("Not authenticated. Run `sylphx login` first."));
1564
+ console.log(import_chalk7.default.red("Not authenticated. Run `sylphx login` first."));
967
1565
  process.exit(1);
968
1566
  }
969
1567
  const pkg = readPackageJson();
@@ -971,51 +1569,51 @@ var initCommand = new import_commander5.Command("init").description("Create and
971
1569
  let projectName;
972
1570
  if (detectedName) {
973
1571
  projectName = detectedName;
974
- console.log(import_chalk6.default.dim(` Project name: ${import_chalk6.default.white(projectName)}`));
1572
+ console.log(import_chalk7.default.dim(` Project name: ${import_chalk7.default.white(projectName)}`));
975
1573
  } else {
976
1574
  projectName = await prompt2(" Project name");
977
1575
  if (!projectName) {
978
- console.log(import_chalk6.default.red("Project name is required."));
1576
+ console.log(import_chalk7.default.red("Project name is required."));
979
1577
  process.exit(1);
980
1578
  }
981
1579
  }
982
1580
  const slug = slugify(projectName);
983
1581
  let orgId = config.getDefaultOrg();
984
1582
  if (!orgId) {
985
- const spinner2 = (0, import_ora5.default)("Fetching your organizations...").start();
1583
+ const spinner2 = (0, import_ora6.default)("Fetching your organizations...").start();
986
1584
  let orgs = [];
987
1585
  try {
988
1586
  const me = await api.whoami();
989
1587
  orgs = me.orgs;
990
1588
  spinner2.stop();
991
1589
  } catch (err) {
992
- spinner2.fail(import_chalk6.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
1590
+ spinner2.fail(import_chalk7.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
993
1591
  process.exit(1);
994
1592
  }
995
1593
  if (orgs.length === 0) {
996
- console.log(import_chalk6.default.red("No organizations found. Create one at https://app.sylphx.com."));
1594
+ console.log(import_chalk7.default.red("No organizations found. Create one at https://app.sylphx.com."));
997
1595
  process.exit(1);
998
1596
  }
999
1597
  if (orgs.length === 1 && orgs[0]) {
1000
1598
  orgId = orgs[0].slug;
1001
- console.log(import_chalk6.default.dim(` Using org: ${import_chalk6.default.white(orgId)}`));
1599
+ console.log(import_chalk7.default.dim(` Using org: ${import_chalk7.default.white(orgId)}`));
1002
1600
  } else {
1003
- console.log(import_chalk6.default.bold("\n Your organizations:\n"));
1601
+ console.log(import_chalk7.default.bold("\n Your organizations:\n"));
1004
1602
  orgs.forEach((org, i) => {
1005
- console.log(` ${import_chalk6.default.cyan(String(i + 1))}. ${org.slug} ${import_chalk6.default.dim(`(${org.name})`)}`);
1603
+ console.log(` ${import_chalk7.default.cyan(String(i + 1))}. ${org.slug} ${import_chalk7.default.dim(`(${org.name})`)}`);
1006
1604
  });
1007
1605
  console.log("");
1008
1606
  const choice = await prompt2(" Select org (number)");
1009
1607
  const idx = Number.parseInt(choice, 10) - 1;
1010
1608
  if (Number.isNaN(idx) || idx < 0 || idx >= orgs.length || !orgs[idx]) {
1011
- console.log(import_chalk6.default.red("Invalid selection."));
1609
+ console.log(import_chalk7.default.red("Invalid selection."));
1012
1610
  process.exit(1);
1013
1611
  }
1014
1612
  orgId = orgs[idx]?.slug;
1015
1613
  }
1016
1614
  }
1017
1615
  if (!orgId) {
1018
- console.log(import_chalk6.default.red("Could not determine org."));
1616
+ console.log(import_chalk7.default.red("Could not determine org."));
1019
1617
  process.exit(1);
1020
1618
  }
1021
1619
  let gitRepository;
@@ -1026,9 +1624,9 @@ var initCommand = new import_commander5.Command("init").description("Create and
1026
1624
  if (sshMatch?.[1]) gitRepository = sshMatch[1];
1027
1625
  const httpsMatch = gitRepository.match(/github\.com\/(.+?)(?:\.git)?$/);
1028
1626
  if (httpsMatch?.[1]) gitRepository = httpsMatch[1];
1029
- console.log(import_chalk6.default.dim(` Git repo: ${import_chalk6.default.white(gitRepository)}`));
1627
+ console.log(import_chalk7.default.dim(` Git repo: ${import_chalk7.default.white(gitRepository)}`));
1030
1628
  } else {
1031
- console.log(import_chalk6.default.yellow(" \u26A0 No git remote found (--git flag ignored)."));
1629
+ console.log(import_chalk7.default.yellow(" \u26A0 No git remote found (--git flag ignored)."));
1032
1630
  }
1033
1631
  }
1034
1632
  let appPort;
@@ -1036,10 +1634,10 @@ var initCommand = new import_commander5.Command("init").description("Create and
1036
1634
  appPort = Number.parseInt(opts.port, 10);
1037
1635
  } else if (pkg) {
1038
1636
  appPort = detectPortFromPackage(pkg);
1039
- if (appPort) console.log(import_chalk6.default.dim(` Detected port: ${import_chalk6.default.white(String(appPort))}`));
1637
+ if (appPort) console.log(import_chalk7.default.dim(` Detected port: ${import_chalk7.default.white(String(appPort))}`));
1040
1638
  }
1041
1639
  console.log("");
1042
- const spinner = (0, import_ora5.default)(`Creating project ${import_chalk6.default.bold(projectName)}...`).start();
1640
+ const spinner = (0, import_ora6.default)(`Creating project ${import_chalk7.default.bold(projectName)}...`).start();
1043
1641
  try {
1044
1642
  const project = await api.createProject({
1045
1643
  name: projectName,
@@ -1049,17 +1647,17 @@ var initCommand = new import_commander5.Command("init").description("Create and
1049
1647
  });
1050
1648
  config.linkApp({ appId: project.id, orgId, defaultEnv: "production" });
1051
1649
  config.setDefaultOrg(orgId);
1052
- spinner.succeed(import_chalk6.default.green(`\u2713 Created and linked project ${import_chalk6.default.bold(project.name)}`));
1650
+ spinner.succeed(import_chalk7.default.green(`\u2713 Created and linked project ${import_chalk7.default.bold(project.name)}`));
1053
1651
  console.log("");
1054
- console.log(import_chalk6.default.dim(` Project ID : ${project.id}`));
1055
- console.log(import_chalk6.default.dim(` Slug : ${project.slug}`));
1056
- console.log(import_chalk6.default.dim(` Org : ${orgId}`));
1057
- console.log(import_chalk6.default.dim(` Directory : ${process.cwd()}`));
1652
+ console.log(import_chalk7.default.dim(` Project ID : ${project.id}`));
1653
+ console.log(import_chalk7.default.dim(` Slug : ${project.slug}`));
1654
+ console.log(import_chalk7.default.dim(` Org : ${orgId}`));
1655
+ console.log(import_chalk7.default.dim(` Directory : ${process.cwd()}`));
1058
1656
  console.log("");
1059
- console.log(import_chalk6.default.dim(` ${import_chalk6.default.cyan("sylphx deploy")} \u2014 deploy to production when ready`));
1657
+ console.log(import_chalk7.default.dim(` ${import_chalk7.default.cyan("sylphx deploy")} \u2014 deploy to production when ready`));
1060
1658
  console.log("");
1061
1659
  } catch (err) {
1062
- spinner.fail(import_chalk6.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
1660
+ spinner.fail(import_chalk7.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
1063
1661
  process.exit(1);
1064
1662
  }
1065
1663
  });
@@ -1068,9 +1666,9 @@ var initCommand = new import_commander5.Command("init").description("Create and
1068
1666
  var import_node_fs3 = __toESM(require("fs"));
1069
1667
  var import_node_path4 = __toESM(require("path"));
1070
1668
  var import_node_readline3 = __toESM(require("readline"));
1071
- var import_chalk7 = __toESM(require("chalk"));
1072
- var import_commander6 = require("commander");
1073
- var import_ora6 = __toESM(require("ora"));
1669
+ var import_chalk8 = __toESM(require("chalk"));
1670
+ var import_commander7 = require("commander");
1671
+ var import_ora7 = __toESM(require("ora"));
1074
1672
  function readPackageName() {
1075
1673
  try {
1076
1674
  const pkgPath = import_node_path4.default.join(process.cwd(), "package.json");
@@ -1092,86 +1690,86 @@ function prompt3(question) {
1092
1690
  });
1093
1691
  });
1094
1692
  }
1095
- var linkCommand = new import_commander6.Command("link").description("Link current directory to a Sylphx app").option("--org <org>", "Organization slug").action(async (opts) => {
1693
+ var linkCommand = new import_commander7.Command("link").description("Link current directory to a Sylphx app").option("--org <org>", "Organization slug").action(async (opts) => {
1096
1694
  const token = config.getToken();
1097
1695
  if (!token) {
1098
- console.log(import_chalk7.default.red("Not authenticated. Run `sylphx login` first."));
1696
+ console.log(import_chalk8.default.red("Not authenticated. Run `sylphx login` first."));
1099
1697
  process.exit(1);
1100
1698
  }
1101
1699
  const pkgName = readPackageName();
1102
1700
  if (pkgName) {
1103
- console.log(import_chalk7.default.dim(` Detected package: ${import_chalk7.default.white(pkgName)}`));
1701
+ console.log(import_chalk8.default.dim(` Detected package: ${import_chalk8.default.white(pkgName)}`));
1104
1702
  }
1105
1703
  let orgId = opts.org ?? config.getDefaultOrg();
1106
1704
  if (!orgId) {
1107
- const spinner = (0, import_ora6.default)("Fetching your organizations...").start();
1705
+ const spinner = (0, import_ora7.default)("Fetching your organizations...").start();
1108
1706
  let orgs = [];
1109
1707
  try {
1110
1708
  const me = await api.whoami();
1111
1709
  orgs = me.orgs;
1112
1710
  spinner.stop();
1113
1711
  } catch (err) {
1114
- spinner.fail(import_chalk7.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
1712
+ spinner.fail(import_chalk8.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
1115
1713
  process.exit(1);
1116
1714
  }
1117
1715
  if (orgs.length === 0) {
1118
- console.log(import_chalk7.default.red("No organizations found."));
1716
+ console.log(import_chalk8.default.red("No organizations found."));
1119
1717
  process.exit(1);
1120
1718
  }
1121
1719
  if (orgs.length === 1 && orgs[0]) {
1122
1720
  orgId = orgs[0].slug;
1123
- console.log(import_chalk7.default.dim(` Using org: ${import_chalk7.default.white(orgId)}`));
1721
+ console.log(import_chalk8.default.dim(` Using org: ${import_chalk8.default.white(orgId)}`));
1124
1722
  } else {
1125
- console.log(import_chalk7.default.bold("\n Your organizations:\n"));
1723
+ console.log(import_chalk8.default.bold("\n Your organizations:\n"));
1126
1724
  orgs.forEach((org, i) => {
1127
- console.log(` ${import_chalk7.default.cyan(String(i + 1))}. ${org.slug} ${import_chalk7.default.dim(`(${org.name})`)}`);
1725
+ console.log(` ${import_chalk8.default.cyan(String(i + 1))}. ${org.slug} ${import_chalk8.default.dim(`(${org.name})`)}`);
1128
1726
  });
1129
1727
  console.log("");
1130
1728
  const choice = await prompt3(" Select org (number): ");
1131
1729
  const idx = Number.parseInt(choice, 10) - 1;
1132
1730
  if (Number.isNaN(idx) || idx < 0 || idx >= orgs.length || !orgs[idx]) {
1133
- console.log(import_chalk7.default.red("Invalid selection."));
1731
+ console.log(import_chalk8.default.red("Invalid selection."));
1134
1732
  process.exit(1);
1135
1733
  }
1136
1734
  orgId = orgs[idx]?.slug;
1137
1735
  }
1138
1736
  }
1139
- const spinner2 = (0, import_ora6.default)("Fetching projects...").start();
1737
+ const spinner2 = (0, import_ora7.default)("Fetching projects...").start();
1140
1738
  let apps = [];
1141
1739
  try {
1142
1740
  apps = await api.listProjects();
1143
1741
  spinner2.stop();
1144
1742
  } catch (err) {
1145
- spinner2.fail(import_chalk7.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
1743
+ spinner2.fail(import_chalk8.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
1146
1744
  process.exit(1);
1147
1745
  }
1148
1746
  if (apps.length === 0) {
1149
- console.log(import_chalk7.default.yellow("No apps found for this org."));
1747
+ console.log(import_chalk8.default.yellow("No apps found for this org."));
1150
1748
  process.exit(1);
1151
1749
  }
1152
- console.log(import_chalk7.default.bold("\n Available apps:\n"));
1750
+ console.log(import_chalk8.default.bold("\n Available apps:\n"));
1153
1751
  apps.forEach((app, i) => {
1154
- console.log(` ${import_chalk7.default.cyan(String(i + 1))}. ${app.slug} ${import_chalk7.default.dim(`(${app.name})`)}`);
1752
+ console.log(` ${import_chalk8.default.cyan(String(i + 1))}. ${app.slug} ${import_chalk8.default.dim(`(${app.name})`)}`);
1155
1753
  });
1156
1754
  console.log("");
1157
1755
  const appChoice = await prompt3(" Select app (number): ");
1158
1756
  const appIdx = Number.parseInt(appChoice, 10) - 1;
1159
1757
  if (Number.isNaN(appIdx) || appIdx < 0 || appIdx >= apps.length || !apps[appIdx]) {
1160
- console.log(import_chalk7.default.red("Invalid selection."));
1758
+ console.log(import_chalk8.default.red("Invalid selection."));
1161
1759
  process.exit(1);
1162
1760
  }
1163
1761
  const selectedApp = apps[appIdx];
1164
1762
  if (!selectedApp) {
1165
- console.log(import_chalk7.default.red("Invalid selection."));
1763
+ console.log(import_chalk8.default.red("Invalid selection."));
1166
1764
  process.exit(1);
1167
1765
  }
1168
1766
  let defaultEnv = "production";
1169
1767
  const envs = selectedApp.environments ?? [];
1170
1768
  if (envs.length > 1) {
1171
- console.log(import_chalk7.default.bold("\n Available environments:\n"));
1769
+ console.log(import_chalk8.default.bold("\n Available environments:\n"));
1172
1770
  envs.forEach((env, i) => {
1173
1771
  console.log(
1174
- ` ${import_chalk7.default.cyan(String(i + 1))}. ${env.envType ?? env.slug ?? env.name} ${import_chalk7.default.dim(`(${env.name})`)}`
1772
+ ` ${import_chalk8.default.cyan(String(i + 1))}. ${env.envType ?? env.slug ?? env.name} ${import_chalk8.default.dim(`(${env.name})`)}`
1175
1773
  );
1176
1774
  });
1177
1775
  console.log("");
@@ -1194,18 +1792,19 @@ var linkCommand = new import_commander6.Command("link").description("Link curren
1194
1792
  config.setDefaultOrg(orgId);
1195
1793
  }
1196
1794
  console.log("");
1197
- console.log(import_chalk7.default.green(`\u2713 Linked to ${import_chalk7.default.bold(selectedApp.slug)}`));
1795
+ console.log(import_chalk8.default.green(`\u2713 Linked to ${import_chalk8.default.bold(selectedApp.slug)}`));
1198
1796
  console.log(
1199
- import_chalk7.default.dim(` App ID: ${selectedApp.id} | Env: ${defaultEnv} | Dir: ${process.cwd()}`)
1797
+ import_chalk8.default.dim(` App ID: ${selectedApp.id} | Env: ${defaultEnv} | Dir: ${process.cwd()}`)
1200
1798
  );
1201
1799
  console.log("");
1202
1800
  });
1203
1801
 
1204
1802
  // src/commands/login.ts
1205
1803
  var import_node_crypto = __toESM(require("crypto"));
1206
- var import_chalk8 = __toESM(require("chalk"));
1207
- var import_commander7 = require("commander");
1208
- var import_ora7 = __toESM(require("ora"));
1804
+ var import_chalk9 = __toESM(require("chalk"));
1805
+ var import_commander8 = require("commander");
1806
+ var import_ora8 = __toESM(require("ora"));
1807
+ var { version: version3 } = package_default;
1209
1808
  var BASE_URL2 = process.env.SYLPHX_API_URL ?? "https://sylphx.com";
1210
1809
  var POLL_INTERVAL_MS2 = 2e3;
1211
1810
  var POLL_TIMEOUT_MS2 = 5 * 60 * 1e3;
@@ -1214,7 +1813,7 @@ async function initCode(code) {
1214
1813
  method: "POST",
1215
1814
  headers: {
1216
1815
  "Content-Type": "application/json",
1217
- "User-Agent": "sylphx-cli/0.1.0"
1816
+ "User-Agent": `sylphx-cli/${version3}`
1218
1817
  },
1219
1818
  body: JSON.stringify({ code })
1220
1819
  });
@@ -1222,22 +1821,17 @@ async function initCode(code) {
1222
1821
  throw new Error(`Failed to initialize auth code (HTTP ${res.status})`);
1223
1822
  }
1224
1823
  }
1225
- var loginCommand = new import_commander7.Command("login").description("Authenticate with the Sylphx platform").option(
1226
- "--token <token>",
1227
- "Authenticate with a pre-existing API token (for CI/CD)"
1228
- ).action(async (opts) => {
1824
+ var loginCommand = new import_commander8.Command("login").description("Authenticate with the Sylphx platform").option("--token <token>", "Authenticate with a pre-existing API token (for CI/CD)").action(async (opts) => {
1229
1825
  if (opts.token) {
1230
- const spinner2 = (0, import_ora7.default)("Validating token...").start();
1826
+ const spinner2 = (0, import_ora8.default)("Validating token...").start();
1231
1827
  try {
1232
1828
  config.setToken(opts.token);
1233
1829
  const me = await api.whoami();
1234
1830
  config.setToken(opts.token);
1235
- spinner2.succeed(
1236
- import_chalk8.default.green(`Authenticated as ${import_chalk8.default.bold(me.user.email)}`)
1237
- );
1831
+ spinner2.succeed(import_chalk9.default.green(`Authenticated as ${import_chalk9.default.bold(me.user.email)}`));
1238
1832
  } catch (err) {
1239
1833
  config.clearToken();
1240
- spinner2.fail(import_chalk8.default.red("Invalid token"));
1834
+ spinner2.fail(import_chalk9.default.red("Invalid token"));
1241
1835
  process.exit(1);
1242
1836
  }
1243
1837
  return;
@@ -1245,18 +1839,18 @@ var loginCommand = new import_commander7.Command("login").description("Authentic
1245
1839
  const code = import_node_crypto.default.randomBytes(16).toString("hex").slice(0, 20).toUpperCase();
1246
1840
  const authUrl = `${BASE_URL2}/cli-auth?code=${code}`;
1247
1841
  console.log("");
1248
- console.log(import_chalk8.default.bold(" Sylphx CLI Authentication"));
1842
+ console.log(import_chalk9.default.bold(" Sylphx CLI Authentication"));
1249
1843
  console.log("");
1250
1844
  console.log(" Opening browser to:");
1251
- console.log(` ${import_chalk8.default.cyan(authUrl)}`);
1845
+ console.log(` ${import_chalk9.default.cyan(authUrl)}`);
1252
1846
  console.log("");
1253
- console.log(` One-time code: ${import_chalk8.default.yellow.bold(code)}`);
1847
+ console.log(` One-time code: ${import_chalk9.default.yellow.bold(code)}`);
1254
1848
  console.log("");
1255
1849
  try {
1256
1850
  await initCode(code);
1257
1851
  } catch (err) {
1258
1852
  console.log(
1259
- import_chalk8.default.yellow(
1853
+ import_chalk9.default.yellow(
1260
1854
  ` Warning: Could not register code: ${err instanceof Error ? err.message : String(err)}`
1261
1855
  )
1262
1856
  );
@@ -1265,13 +1859,9 @@ var loginCommand = new import_commander7.Command("login").description("Authentic
1265
1859
  const { default: open } = await import("open");
1266
1860
  await open(authUrl);
1267
1861
  } catch {
1268
- console.log(
1269
- import_chalk8.default.dim(
1270
- " Could not open browser automatically. Please visit the URL above."
1271
- )
1272
- );
1862
+ console.log(import_chalk9.default.dim(" Could not open browser automatically. Please visit the URL above."));
1273
1863
  }
1274
- const spinner = (0, import_ora7.default)("Waiting for browser authorization...").start();
1864
+ const spinner = (0, import_ora8.default)("Waiting for browser authorization...").start();
1275
1865
  const deadline = Date.now() + POLL_TIMEOUT_MS2;
1276
1866
  while (Date.now() < deadline) {
1277
1867
  await sleep(POLL_INTERVAL_MS2);
@@ -1281,28 +1871,24 @@ var loginCommand = new import_commander7.Command("login").description("Authentic
1281
1871
  config.setToken(result.token);
1282
1872
  try {
1283
1873
  const me = await api.whoami();
1284
- spinner.succeed(
1285
- import_chalk8.default.green(`Authenticated as ${import_chalk8.default.bold(me.user.email)}`)
1286
- );
1874
+ spinner.succeed(import_chalk9.default.green(`Authenticated as ${import_chalk9.default.bold(me.user.email)}`));
1287
1875
  } catch {
1288
- spinner.succeed(import_chalk8.default.green("Authenticated successfully"));
1876
+ spinner.succeed(import_chalk9.default.green("Authenticated successfully"));
1289
1877
  }
1290
1878
  console.log("");
1291
- console.log(
1292
- ` Token stored at: ${import_chalk8.default.dim(config.getConfigPath())}`
1293
- );
1879
+ console.log(` Token stored at: ${import_chalk9.default.dim(config.getConfigPath())}`);
1294
1880
  console.log("");
1295
1881
  return;
1296
1882
  }
1297
1883
  } catch (err) {
1298
1884
  const msg = err instanceof Error ? err.message : String(err);
1299
1885
  if (msg.includes("404") || msg.includes("expired")) {
1300
- spinner.fail(import_chalk8.default.red("Code expired or invalid. Please try again."));
1886
+ spinner.fail(import_chalk9.default.red("Code expired or invalid. Please try again."));
1301
1887
  process.exit(1);
1302
1888
  }
1303
1889
  }
1304
1890
  }
1305
- spinner.fail(import_chalk8.default.red("Authentication timed out. Please try again."));
1891
+ spinner.fail(import_chalk9.default.red("Authentication timed out. Please try again."));
1306
1892
  process.exit(1);
1307
1893
  });
1308
1894
  function sleep(ms) {
@@ -1310,43 +1896,43 @@ function sleep(ms) {
1310
1896
  }
1311
1897
 
1312
1898
  // src/commands/logout.ts
1313
- var import_chalk9 = __toESM(require("chalk"));
1314
- var import_commander8 = require("commander");
1315
- var logoutCommand = new import_commander8.Command("logout").description("Clear stored credentials").action(() => {
1899
+ var import_chalk10 = __toESM(require("chalk"));
1900
+ var import_commander9 = require("commander");
1901
+ var logoutCommand = new import_commander9.Command("logout").description("Clear stored credentials").action(() => {
1316
1902
  const token = config.getToken();
1317
1903
  if (!token) {
1318
- console.log(import_chalk9.default.yellow("You are not currently logged in."));
1904
+ console.log(import_chalk10.default.yellow("You are not currently logged in."));
1319
1905
  return;
1320
1906
  }
1321
1907
  config.clearToken();
1322
- console.log(import_chalk9.default.green("\u2713 Logged out successfully."));
1908
+ console.log(import_chalk10.default.green("\u2713 Logged out successfully."));
1323
1909
  console.log(
1324
- import_chalk9.default.dim(` Credentials cleared from ${config.getConfigPath()}`)
1910
+ import_chalk10.default.dim(` Credentials cleared from ${config.getConfigPath()}`)
1325
1911
  );
1326
1912
  });
1327
1913
 
1328
1914
  // src/commands/logs.ts
1329
- var import_chalk10 = __toESM(require("chalk"));
1330
- var import_commander9 = require("commander");
1331
- var logsCommand = new import_commander9.Command("logs").description("Stream build/runtime logs").option("--env <env>", "Environment (e.g. production, staging)").option("-f, --follow", "Follow log output (stream continuously)").action(async (opts) => {
1915
+ var import_chalk11 = __toESM(require("chalk"));
1916
+ var import_commander10 = require("commander");
1917
+ var logsCommand = new import_commander10.Command("logs").description("Stream build/runtime logs").option("--env <env>", "Environment (e.g. production, staging)").option("-f, --follow", "Follow log output (stream continuously)").action(async (opts) => {
1332
1918
  const token = config.getToken();
1333
1919
  if (!token) {
1334
- console.log(import_chalk10.default.red("Not authenticated. Run `sylphx login` first."));
1920
+ console.log(import_chalk11.default.red("Not authenticated. Run `sylphx login` first."));
1335
1921
  process.exit(1);
1336
1922
  }
1337
1923
  const linked = config.getLinkedApp();
1338
1924
  if (!linked) {
1339
- console.log(import_chalk10.default.red("No app linked. Run `sylphx link` first."));
1925
+ console.log(import_chalk11.default.red("No app linked. Run `sylphx link` first."));
1340
1926
  process.exit(1);
1341
1927
  }
1342
1928
  const env = opts.env ?? linked.defaultEnv;
1343
1929
  console.log("");
1344
1930
  console.log(
1345
- import_chalk10.default.bold(
1346
- ` Logs for ${import_chalk10.default.cyan(linked.appId)} [${import_chalk10.default.white(env)}]${opts.follow ? import_chalk10.default.dim(" (following...)") : ""}`
1931
+ import_chalk11.default.bold(
1932
+ ` Logs for ${import_chalk11.default.cyan(linked.appId)} [${import_chalk11.default.white(env)}]${opts.follow ? import_chalk11.default.dim(" (following...)") : ""}`
1347
1933
  )
1348
1934
  );
1349
- console.log(import_chalk10.default.dim(` ${"\u2500".repeat(50)}`));
1935
+ console.log(import_chalk11.default.dim(` ${"\u2500".repeat(50)}`));
1350
1936
  console.log("");
1351
1937
  const logStream = createLogStream(linked.appId, env);
1352
1938
  await streamLogs(logStream.url, logStream.token, {
@@ -1359,21 +1945,21 @@ var logsCommand = new import_commander9.Command("logs").description("Stream buil
1359
1945
  });
1360
1946
 
1361
1947
  // src/commands/open.ts
1362
- var import_chalk11 = __toESM(require("chalk"));
1363
- var import_commander10 = require("commander");
1364
- var import_ora8 = __toESM(require("ora"));
1365
- var openCommand = new import_commander10.Command("open").description("Open the app in your browser").option("--env <env>", "Environment (e.g. production, staging)").action(async (opts) => {
1948
+ var import_chalk12 = __toESM(require("chalk"));
1949
+ var import_commander11 = require("commander");
1950
+ var import_ora9 = __toESM(require("ora"));
1951
+ var openCommand = new import_commander11.Command("open").description("Open the app in your browser").option("--env <env>", "Environment (e.g. production, staging)").action(async (opts) => {
1366
1952
  const token = config.getToken();
1367
1953
  if (!token) {
1368
- console.log(import_chalk11.default.red("Not authenticated. Run `sylphx login` first."));
1954
+ console.log(import_chalk12.default.red("Not authenticated. Run `sylphx login` first."));
1369
1955
  process.exit(1);
1370
1956
  }
1371
1957
  const linked = config.getLinkedApp();
1372
1958
  if (!linked) {
1373
- console.log(import_chalk11.default.red("No app linked. Run `sylphx link` first."));
1959
+ console.log(import_chalk12.default.red("No app linked. Run `sylphx link` first."));
1374
1960
  process.exit(1);
1375
1961
  }
1376
- const spinner = (0, import_ora8.default)("Fetching app URL...").start();
1962
+ const spinner = (0, import_ora9.default)("Fetching app URL...").start();
1377
1963
  let url;
1378
1964
  try {
1379
1965
  const status = await api.getDeploymentStatus(linked.appId);
@@ -1383,7 +1969,7 @@ var openCommand = new import_commander10.Command("open").description("Open the a
1383
1969
  const resolvedUrl = status.url ?? envMatch?.url ?? null;
1384
1970
  if (!resolvedUrl) {
1385
1971
  url = `https://sylphx.com/console/apps/${linked.appId}`;
1386
- console.log(import_chalk11.default.yellow(" No URL found, opening console instead."));
1972
+ console.log(import_chalk12.default.yellow(" No URL found, opening console instead."));
1387
1973
  } else {
1388
1974
  url = resolvedUrl;
1389
1975
  }
@@ -1391,28 +1977,28 @@ var openCommand = new import_commander10.Command("open").description("Open the a
1391
1977
  spinner.stop();
1392
1978
  url = `https://sylphx.com/console/apps/${linked.appId}`;
1393
1979
  }
1394
- console.log(` Opening ${import_chalk11.default.cyan(url)}`);
1980
+ console.log(` Opening ${import_chalk12.default.cyan(url)}`);
1395
1981
  try {
1396
1982
  const { default: open } = await import("open");
1397
1983
  await open(url);
1398
1984
  } catch (err) {
1399
1985
  console.log(
1400
- import_chalk11.default.red(`Failed to open browser: ${err instanceof Error ? err.message : String(err)}`)
1986
+ import_chalk12.default.red(`Failed to open browser: ${err instanceof Error ? err.message : String(err)}`)
1401
1987
  );
1402
- console.log(import_chalk11.default.dim(` URL: ${url}`));
1988
+ console.log(import_chalk12.default.dim(` URL: ${url}`));
1403
1989
  process.exit(1);
1404
1990
  }
1405
1991
  });
1406
1992
 
1407
1993
  // src/commands/projects.ts
1408
1994
  var import_node_readline4 = __toESM(require("readline"));
1409
- var import_chalk12 = __toESM(require("chalk"));
1410
- var import_commander11 = require("commander");
1411
- var import_ora9 = __toESM(require("ora"));
1995
+ var import_chalk13 = __toESM(require("chalk"));
1996
+ var import_commander12 = require("commander");
1997
+ var import_ora10 = __toESM(require("ora"));
1412
1998
  function requireAuth2() {
1413
1999
  const token = config.getToken();
1414
2000
  if (!token) {
1415
- console.log(import_chalk12.default.red("Not authenticated. Run `sylphx login` first."));
2001
+ console.log(import_chalk13.default.red("Not authenticated. Run `sylphx login` first."));
1416
2002
  process.exit(1);
1417
2003
  }
1418
2004
  return token;
@@ -1429,41 +2015,41 @@ function prompt4(question) {
1429
2015
  });
1430
2016
  });
1431
2017
  }
1432
- var projectsListCommand = new import_commander11.Command("list").description("List all projects").action(async () => {
2018
+ var projectsListCommand = new import_commander12.Command("list").description("List all projects").action(async () => {
1433
2019
  requireAuth2();
1434
- const spinner = (0, import_ora9.default)("Fetching projects...").start();
2020
+ const spinner = (0, import_ora10.default)("Fetching projects...").start();
1435
2021
  try {
1436
2022
  const projects = await api.listProjects();
1437
2023
  spinner.stop();
1438
2024
  if (projects.length === 0) {
1439
- console.log(import_chalk12.default.dim("\n No projects found.\n"));
2025
+ console.log(import_chalk13.default.dim("\n No projects found.\n"));
1440
2026
  return;
1441
2027
  }
1442
2028
  const colSlug = Math.max(4, ...projects.map((p) => p.slug.length));
1443
2029
  const colName = Math.max(4, ...projects.map((p) => p.name.length));
1444
2030
  console.log("");
1445
- console.log(import_chalk12.default.bold(" Projects"));
1446
- console.log(import_chalk12.default.dim(` ${"\u2500".repeat(colSlug + colName + 30)}`));
2031
+ console.log(import_chalk13.default.bold(" Projects"));
2032
+ console.log(import_chalk13.default.dim(` ${"\u2500".repeat(colSlug + colName + 30)}`));
1447
2033
  console.log(
1448
- import_chalk12.default.dim(` ${"SLUG".padEnd(colSlug)} ${"NAME".padEnd(colName)} ${"ENVIRONMENTS"}`)
2034
+ import_chalk13.default.dim(` ${"SLUG".padEnd(colSlug)} ${"NAME".padEnd(colName)} ${"ENVIRONMENTS"}`)
1449
2035
  );
1450
- console.log(import_chalk12.default.dim(` ${"\u2500".repeat(colSlug + colName + 30)}`));
2036
+ console.log(import_chalk13.default.dim(` ${"\u2500".repeat(colSlug + colName + 30)}`));
1451
2037
  for (const p of projects) {
1452
- const envNames = p.environments && p.environments.length > 0 ? p.environments.map((e) => e.name || e.envType || e.id).join(", ") : import_chalk12.default.dim("\u2014");
2038
+ const envNames = p.environments && p.environments.length > 0 ? p.environments.map((e) => e.name || e.envType || e.id).join(", ") : import_chalk13.default.dim("\u2014");
1453
2039
  console.log(
1454
- ` ${import_chalk12.default.cyan(p.slug.padEnd(colSlug))} ${p.name.padEnd(colName)} ${envNames}`
2040
+ ` ${import_chalk13.default.cyan(p.slug.padEnd(colSlug))} ${p.name.padEnd(colName)} ${envNames}`
1455
2041
  );
1456
2042
  }
1457
2043
  console.log("");
1458
2044
  } catch (err) {
1459
- spinner.fail(import_chalk12.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
2045
+ spinner.fail(import_chalk13.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
1460
2046
  process.exit(1);
1461
2047
  }
1462
2048
  });
1463
- var projectsCreateCommand = new import_commander11.Command("create").description("Create a new project (without linking the current directory)").argument("<name>", "Project name").option("--git <repo>", "Git repository (e.g. owner/repo)").option("--port <port>", "Application port").action(async (name, opts) => {
2049
+ var projectsCreateCommand = new import_commander12.Command("create").description("Create a new project (without linking the current directory)").argument("<name>", "Project name").option("--git <repo>", "Git repository (e.g. owner/repo)").option("--port <port>", "Application port").action(async (name, opts) => {
1464
2050
  requireAuth2();
1465
2051
  const slug = slugify2(name);
1466
- const spinner = (0, import_ora9.default)(`Creating project ${import_chalk12.default.bold(name)}...`).start();
2052
+ const spinner = (0, import_ora10.default)(`Creating project ${import_chalk13.default.bold(name)}...`).start();
1467
2053
  try {
1468
2054
  const project = await api.createProject({
1469
2055
  name,
@@ -1471,75 +2057,296 @@ var projectsCreateCommand = new import_commander11.Command("create").description
1471
2057
  ...opts.git ? { gitRepository: opts.git } : {},
1472
2058
  ...opts.port ? { port: Number.parseInt(opts.port, 10) } : {}
1473
2059
  });
1474
- spinner.succeed(import_chalk12.default.green(`\u2713 Created project ${import_chalk12.default.bold(project.name)}`));
2060
+ spinner.succeed(import_chalk13.default.green(`\u2713 Created project ${import_chalk13.default.bold(project.name)}`));
1475
2061
  console.log("");
1476
- console.log(import_chalk12.default.dim(` ID : ${project.id}`));
1477
- console.log(import_chalk12.default.dim(` Slug : ${project.slug}`));
2062
+ console.log(import_chalk13.default.dim(` ID : ${project.id}`));
2063
+ console.log(import_chalk13.default.dim(` Slug : ${project.slug}`));
1478
2064
  console.log("");
1479
2065
  console.log(
1480
- import_chalk12.default.dim(` Run ${import_chalk12.default.cyan("sylphx link")} to link this project to a directory.`)
2066
+ import_chalk13.default.dim(` Run ${import_chalk13.default.cyan("sylphx link")} to link this project to a directory.`)
1481
2067
  );
1482
2068
  console.log("");
1483
2069
  } catch (err) {
1484
- spinner.fail(import_chalk12.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
2070
+ spinner.fail(import_chalk13.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
1485
2071
  process.exit(1);
1486
2072
  }
1487
2073
  });
1488
- var projectsDeleteCommand = new import_commander11.Command("delete").description("Delete a project").argument("<id>", "Project ID or slug").option("--force", "Skip confirmation prompt").action(async (id, opts) => {
2074
+ var projectsDeleteCommand = new import_commander12.Command("delete").description("Delete a project").argument("<id>", "Project ID or slug").option("--force", "Skip confirmation prompt").action(async (id, opts) => {
1489
2075
  requireAuth2();
1490
2076
  if (!opts.force) {
1491
2077
  console.log("");
1492
2078
  console.log(
1493
- import_chalk12.default.yellow(` \u26A0 You are about to permanently delete project ${import_chalk12.default.bold(id)}.`)
2079
+ import_chalk13.default.yellow(` \u26A0 You are about to permanently delete project ${import_chalk13.default.bold(id)}.`)
1494
2080
  );
1495
- console.log(import_chalk12.default.yellow(" This action cannot be undone."));
2081
+ console.log(import_chalk13.default.yellow(" This action cannot be undone."));
1496
2082
  console.log("");
1497
2083
  const answer = await prompt4(" Type the project ID/slug to confirm");
1498
2084
  if (answer !== id) {
1499
- console.log(import_chalk12.default.red("Confirmation did not match. Aborted."));
2085
+ console.log(import_chalk13.default.red("Confirmation did not match. Aborted."));
1500
2086
  process.exit(1);
1501
2087
  }
1502
2088
  }
1503
- const spinner = (0, import_ora9.default)(`Deleting project ${import_chalk12.default.bold(id)}...`).start();
2089
+ const spinner = (0, import_ora10.default)(`Deleting project ${import_chalk13.default.bold(id)}...`).start();
1504
2090
  try {
1505
2091
  await api.deleteProject(id);
1506
- spinner.succeed(import_chalk12.default.green(`\u2713 Deleted project ${import_chalk12.default.bold(id)}`));
2092
+ spinner.succeed(import_chalk13.default.green(`\u2713 Deleted project ${import_chalk13.default.bold(id)}`));
1507
2093
  } catch (err) {
1508
- spinner.fail(import_chalk12.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
2094
+ spinner.fail(import_chalk13.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
1509
2095
  process.exit(1);
1510
2096
  }
1511
2097
  });
1512
- var projectsCommand = new import_commander11.Command("projects").description("Manage Sylphx projects").addCommand(projectsListCommand).addCommand(projectsCreateCommand).addCommand(projectsDeleteCommand);
2098
+ var projectsCommand = new import_commander12.Command("projects").description("Manage Sylphx projects").addCommand(projectsListCommand).addCommand(projectsCreateCommand).addCommand(projectsDeleteCommand);
1513
2099
 
1514
- // src/commands/rollback.ts
1515
- var import_chalk13 = __toESM(require("chalk"));
1516
- var import_commander12 = require("commander");
1517
- var import_ora10 = __toESM(require("ora"));
1518
- var rollbackCommand = new import_commander12.Command("rollback").description("Rollback to the previous deployment").option("--env <env>", "Environment (e.g. production, staging)").option("--force", "Skip confirmation prompt").action(async (opts) => {
2100
+ // src/commands/promote.ts
2101
+ var import_chalk14 = __toESM(require("chalk"));
2102
+ var import_commander13 = require("commander");
2103
+ var import_ora11 = __toESM(require("ora"));
2104
+ var promoteCommand = new import_commander13.Command("promote").description("Promote an environment (e.g. staging \u2192 production)").option("--from <env>", "Source environment (default: staging)", "staging").option("--to <env>", "Target environment (default: production)", "production").option("--force", "Skip confirmation prompt").action(async (opts) => {
1519
2105
  const token = config.getToken();
1520
2106
  if (!token) {
1521
- console.log(import_chalk13.default.red("Not authenticated. Run `sylphx login` first."));
2107
+ console.log(import_chalk14.default.red("Not authenticated. Run `sylphx login` first."));
1522
2108
  process.exit(1);
1523
2109
  }
1524
2110
  const linked = config.getLinkedApp();
1525
2111
  if (!linked) {
1526
- console.log(import_chalk13.default.red("No app linked. Run `sylphx link` first."));
2112
+ console.log(import_chalk14.default.red("No app linked. Run `sylphx link` first."));
2113
+ process.exit(1);
2114
+ }
2115
+ if (opts.from === opts.to) {
2116
+ console.log(import_chalk14.default.red("Source and target environments must be different."));
1527
2117
  process.exit(1);
1528
2118
  }
1529
- const env = opts.env ?? linked.defaultEnv;
1530
2119
  if (!opts.force) {
1531
- const readline5 = await import("readline");
1532
- const rl = readline5.createInterface({
1533
- input: process.stdin,
1534
- output: process.stdout
2120
+ const readline8 = await import("readline");
2121
+ const rl = readline8.createInterface({ input: process.stdin, output: process.stdout });
2122
+ await new Promise((resolve) => {
2123
+ rl.question(
2124
+ import_chalk14.default.yellow(
2125
+ ` Promote ${import_chalk14.default.bold(linked.appId)}: ${import_chalk14.default.cyan(opts.from)} \u2192 ${import_chalk14.default.green(opts.to)}? (y/N) `
2126
+ ),
2127
+ (answer) => {
2128
+ rl.close();
2129
+ if (answer.toLowerCase() !== "y") {
2130
+ console.log(import_chalk14.default.dim(" Cancelled."));
2131
+ process.exit(0);
2132
+ }
2133
+ resolve();
2134
+ }
2135
+ );
1535
2136
  });
1536
- await new Promise((resolve, reject) => {
2137
+ }
2138
+ const spinner = (0, import_ora11.default)(`Resolving environments...`).start();
2139
+ try {
2140
+ const envs = await api.listEnvironments(linked.appId);
2141
+ const sourceEnv = envs.find(
2142
+ (e) => (e.envType ?? e.name ?? "").toLowerCase() === opts.from.toLowerCase()
2143
+ );
2144
+ const targetEnv = envs.find(
2145
+ (e) => (e.envType ?? e.name ?? "").toLowerCase() === opts.to.toLowerCase()
2146
+ );
2147
+ if (!sourceEnv) {
2148
+ spinner.fail(import_chalk14.default.red(`Source environment '${opts.from}' not found.`));
2149
+ console.log(import_chalk14.default.dim(` Available: ${envs.map((e) => e.envType ?? e.name).join(", ")}`));
2150
+ process.exit(1);
2151
+ }
2152
+ if (!targetEnv) {
2153
+ spinner.fail(import_chalk14.default.red(`Target environment '${opts.to}' not found.`));
2154
+ console.log(import_chalk14.default.dim(` Available: ${envs.map((e) => e.envType ?? e.name).join(", ")}`));
2155
+ process.exit(1);
2156
+ }
2157
+ spinner.text = `Promoting ${opts.from} \u2192 ${opts.to}...`;
2158
+ const result = await api.promoteEnvironment(linked.appId, targetEnv.id, sourceEnv.id);
2159
+ if (result.promoted === 0) {
2160
+ spinner.warn(
2161
+ import_chalk14.default.yellow(
2162
+ `No services promoted from ${import_chalk14.default.bold(opts.from)} \u2014 nothing to push (source may have no deployed image yet)`
2163
+ )
2164
+ );
2165
+ } else {
2166
+ spinner.succeed(
2167
+ import_chalk14.default.green(
2168
+ `\u2713 Promoted ${import_chalk14.default.bold(opts.from)} \u2192 ${import_chalk14.default.bold(opts.to)} (${result.promoted} service${result.promoted !== 1 ? "s" : ""})`
2169
+ )
2170
+ );
2171
+ console.log(
2172
+ import_chalk14.default.dim(`
2173
+ Deployments queued. Run ${import_chalk14.default.cyan("sylphx status")} to monitor.`)
2174
+ );
2175
+ }
2176
+ } catch (err) {
2177
+ spinner.fail(import_chalk14.default.red(`Promote failed: ${err instanceof Error ? err.message : String(err)}`));
2178
+ process.exit(1);
2179
+ }
2180
+ });
2181
+
2182
+ // src/commands/resources.ts
2183
+ var import_chalk15 = __toESM(require("chalk"));
2184
+ var import_commander14 = require("commander");
2185
+ var import_ora12 = __toESM(require("ora"));
2186
+ function requireLinkedApp4() {
2187
+ const linked = config.getLinkedApp();
2188
+ if (!linked) {
2189
+ console.log(import_chalk15.default.red("No app linked. Run `sylphx link` first."));
2190
+ process.exit(1);
2191
+ }
2192
+ return linked;
2193
+ }
2194
+ function requireToken2() {
2195
+ const token = config.getToken();
2196
+ if (!token) {
2197
+ console.log(import_chalk15.default.red("Not authenticated. Run `sylphx login` first."));
2198
+ process.exit(1);
2199
+ }
2200
+ return token;
2201
+ }
2202
+ var listCmd2 = new import_commander14.Command("list").description("List managed resources (databases, volumes, storage)").option("--kind <kind>", "Filter by kind: database, volume, storage").action(async (opts) => {
2203
+ requireToken2();
2204
+ const spinner = (0, import_ora12.default)("Fetching resources...").start();
2205
+ try {
2206
+ const resources = await api.listDatabases();
2207
+ spinner.stop();
2208
+ if (resources.length === 0) {
2209
+ console.log(import_chalk15.default.dim(` No resources found.`));
2210
+ return;
2211
+ }
2212
+ console.log();
2213
+ console.log(
2214
+ ` ${import_chalk15.default.bold("NAME".padEnd(24))} ${"KIND".padEnd(12)} ${"STATUS".padEnd(14)} ID`
2215
+ );
2216
+ console.log(` ${"\u2500".repeat(72)}`);
2217
+ for (const r of resources) {
2218
+ const statusColor2 = r.status === "ready" ? import_chalk15.default.green((r.status ?? "unknown").padEnd(14)) : import_chalk15.default.yellow((r.status ?? "unknown").padEnd(14));
2219
+ console.log(
2220
+ ` ${import_chalk15.default.cyan(r.name.padEnd(24))} ${"database".padEnd(12)} ${statusColor2} ${import_chalk15.default.dim(r.id)}`
2221
+ );
2222
+ }
2223
+ console.log();
2224
+ } catch (err) {
2225
+ spinner.fail(import_chalk15.default.red(err instanceof Error ? err.message : String(err)));
2226
+ process.exit(1);
2227
+ }
2228
+ });
2229
+ var getCmd2 = new import_commander14.Command("get").description("Get details of a resource").argument("<id>", "Resource ID").action(async (id) => {
2230
+ requireToken2();
2231
+ const spinner = (0, import_ora12.default)(`Fetching resource ${id}...`).start();
2232
+ try {
2233
+ const r = await api.getDatabase(id);
2234
+ spinner.stop();
2235
+ console.log();
2236
+ console.log(` ${import_chalk15.default.bold("Name:")} ${import_chalk15.default.cyan(r.name)}`);
2237
+ console.log(` ${import_chalk15.default.bold("ID:")} ${r.id}`);
2238
+ console.log(
2239
+ ` ${import_chalk15.default.bold("Status:")} ${r.status === "ready" ? import_chalk15.default.green(r.status) : import_chalk15.default.yellow(r.status ?? "unknown")}`
2240
+ );
2241
+ if (r.tier) console.log(` ${import_chalk15.default.bold("Tier:")} ${r.tier}`);
2242
+ if (r.host) console.log(` ${import_chalk15.default.bold("Host:")} ${r.host}${r.port ? `:${r.port}` : ""}`);
2243
+ if (r.connectionString) {
2244
+ console.log(` ${import_chalk15.default.bold("DSN:")} ${import_chalk15.default.dim(r.connectionString)}`);
2245
+ }
2246
+ if (r.env) console.log(` ${import_chalk15.default.bold("Env:")} ${r.env}`);
2247
+ console.log();
2248
+ } catch (err) {
2249
+ spinner.fail(import_chalk15.default.red(err instanceof Error ? err.message : String(err)));
2250
+ process.exit(1);
2251
+ }
2252
+ });
2253
+ var bindingsCmd = new import_commander14.Command("bindings").description("List project bindings for a resource").argument("<id>", "Resource ID").action(async (id) => {
2254
+ requireToken2();
2255
+ const spinner = (0, import_ora12.default)(`Fetching bindings for ${id}...`).start();
2256
+ try {
2257
+ const bindings = await api.listResourceBindings(id);
2258
+ spinner.stop();
2259
+ if (bindings.length === 0) {
2260
+ console.log(import_chalk15.default.dim(" No bindings. Use `sylphx resources bind <id>` to link."));
2261
+ return;
2262
+ }
2263
+ console.log();
2264
+ console.log(
2265
+ ` ${import_chalk15.default.bold("BINDING ID".padEnd(26))} ${"PROJECT".padEnd(24)} ${"ENV".padEnd(12)} ROLE`
2266
+ );
2267
+ console.log(` ${"\u2500".repeat(80)}`);
2268
+ for (const b of bindings) {
2269
+ const project = b.projectName ?? b.projectSlug ?? "\u2014";
2270
+ const env = b.envName ?? "\u2014";
2271
+ console.log(
2272
+ ` ${import_chalk15.default.dim(b.id.padEnd(26))} ${project.padEnd(24)} ${env.padEnd(12)} ${b.role}`
2273
+ );
2274
+ }
2275
+ console.log();
2276
+ } catch (err) {
2277
+ spinner.fail(import_chalk15.default.red(err instanceof Error ? err.message : String(err)));
2278
+ process.exit(1);
2279
+ }
2280
+ });
2281
+ var bindCmd = new import_commander14.Command("bind").description("Bind a resource to this project environment").argument("<id>", "Resource ID (e.g. res_xxx)").option("--env <env>", "Environment (default: production)", "production").option(
2282
+ "--role <role>",
2283
+ "Binding role: primary, replica, analytics, backup (default: primary)",
2284
+ "primary"
2285
+ ).action(async (id, opts) => {
2286
+ requireToken2();
2287
+ const linked = requireLinkedApp4();
2288
+ const spinner = (0, import_ora12.default)(`Binding resource ${id} to ${linked.appId} [${opts.env}]...`).start();
2289
+ try {
2290
+ const binding = await api.bindResource(id, {
2291
+ projectId: linked.appId,
2292
+ envType: opts.env,
2293
+ role: opts.role
2294
+ });
2295
+ spinner.succeed(import_chalk15.default.green(`\u2713 Resource bound (${binding.role})`));
2296
+ console.log();
2297
+ console.log(` ${import_chalk15.default.dim("Binding ID:")} ${binding.id}`);
2298
+ console.log(` ${import_chalk15.default.dim("Env:")} ${binding.envName ?? opts.env}`);
2299
+ console.log(
2300
+ import_chalk15.default.dim(
2301
+ `
2302
+ Run ${import_chalk15.default.cyan("sylphx deploy")} to apply the connection vars to your deployment.`
2303
+ )
2304
+ );
2305
+ } catch (err) {
2306
+ spinner.fail(import_chalk15.default.red(err instanceof Error ? err.message : String(err)));
2307
+ process.exit(1);
2308
+ }
2309
+ });
2310
+ var unbindCmd = new import_commander14.Command("unbind").description("Remove a resource binding").argument("<id>", "Resource ID").argument("<binding-id>", "Binding ID (from `sylphx resources bindings <id>`)").action(async (id, bindingId) => {
2311
+ requireToken2();
2312
+ const spinner = (0, import_ora12.default)(`Removing binding ${bindingId}...`).start();
2313
+ try {
2314
+ await api.unbindResource(id, bindingId);
2315
+ spinner.succeed(import_chalk15.default.green(`\u2713 Binding removed`));
2316
+ } catch (err) {
2317
+ spinner.fail(import_chalk15.default.red(err instanceof Error ? err.message : String(err)));
2318
+ process.exit(1);
2319
+ }
2320
+ });
2321
+ var resourcesCommand = new import_commander14.Command("resources").description("Manage resources and project bindings").addCommand(listCmd2).addCommand(getCmd2).addCommand(bindingsCmd).addCommand(bindCmd).addCommand(unbindCmd).action(() => resourcesCommand.help());
2322
+
2323
+ // src/commands/rollback.ts
2324
+ var import_chalk16 = __toESM(require("chalk"));
2325
+ var import_commander15 = require("commander");
2326
+ var import_ora13 = __toESM(require("ora"));
2327
+ var rollbackCommand = new import_commander15.Command("rollback").description("Rollback to the previous deployment").option("--env <env>", "Environment to rollback (e.g. production, staging)").option("--deployment <id>", "Rollback to a specific deployment ID").option("--force", "Skip confirmation prompt").action(async (opts) => {
2328
+ const token = config.getToken();
2329
+ if (!token) {
2330
+ console.log(import_chalk16.default.red("Not authenticated. Run `sylphx login` first."));
2331
+ process.exit(1);
2332
+ }
2333
+ const linked = config.getLinkedApp();
2334
+ if (!linked) {
2335
+ console.log(import_chalk16.default.red("No app linked. Run `sylphx link` first."));
2336
+ process.exit(1);
2337
+ }
2338
+ const envType = opts.env ?? linked.defaultEnv ?? "production";
2339
+ if (!opts.force) {
2340
+ const readline8 = await import("readline");
2341
+ const rl = readline8.createInterface({ input: process.stdin, output: process.stdout });
2342
+ await new Promise((resolve) => {
2343
+ const label = opts.deployment ? `deployment ${import_chalk16.default.bold(opts.deployment)}` : `previous deployment`;
1537
2344
  rl.question(
1538
- import_chalk13.default.yellow(` Rollback ${import_chalk13.default.bold(linked.appId)} [${env}]? (y/N) `),
2345
+ import_chalk16.default.yellow(` Rollback ${import_chalk16.default.bold(linked.appId)} [${envType}] to ${label}? (y/N) `),
1539
2346
  (answer) => {
1540
2347
  rl.close();
1541
2348
  if (answer.toLowerCase() !== "y") {
1542
- console.log(import_chalk13.default.dim(" Cancelled."));
2349
+ console.log(import_chalk16.default.dim(" Cancelled."));
1543
2350
  process.exit(0);
1544
2351
  }
1545
2352
  resolve();
@@ -1547,152 +2354,523 @@ var rollbackCommand = new import_commander12.Command("rollback").description("Ro
1547
2354
  );
1548
2355
  });
1549
2356
  }
1550
- const spinner = (0, import_ora10.default)(`Rolling back ${linked.appId} [${env}]...`).start();
2357
+ const spinner = (0, import_ora13.default)(`Rolling back ${linked.appId} [${envType}]...`).start();
1551
2358
  try {
1552
- const result = await api.rollback(linked.appId, env);
1553
- spinner.succeed(import_chalk13.default.green(`\u2713 Rollback initiated: ${result.status}`));
2359
+ let rollbackBody;
2360
+ if (opts.deployment) {
2361
+ rollbackBody = { deploymentRecordId: opts.deployment };
2362
+ } else {
2363
+ const status = await api.getDeploymentStatus(linked.appId);
2364
+ const envEntry = status.environments?.find(
2365
+ (e) => (e.envType ?? "").toLowerCase() === envType.toLowerCase()
2366
+ );
2367
+ if (!envEntry?.envId) {
2368
+ spinner.fail(
2369
+ import_chalk16.default.red(
2370
+ `Could not find environment '${envType}'. Run \`sylphx status\` to see available environments.`
2371
+ )
2372
+ );
2373
+ process.exit(1);
2374
+ }
2375
+ rollbackBody = { envId: envEntry.envId };
2376
+ }
2377
+ const result = await api.rollback(linked.appId, rollbackBody);
2378
+ spinner.succeed(import_chalk16.default.green(`\u2713 Rollback initiated`));
1554
2379
  if (result.deploymentId) {
1555
- console.log(import_chalk13.default.dim(` Deployment ID: ${result.deploymentId}`));
2380
+ console.log(import_chalk16.default.dim(` Deployment ID: ${result.deploymentId}`));
2381
+ }
2382
+ if (result.status) {
2383
+ console.log(import_chalk16.default.dim(` Status: ${result.status}`));
1556
2384
  }
1557
2385
  if (result.message) {
1558
- console.log(import_chalk13.default.dim(` ${result.message}`));
2386
+ console.log(import_chalk16.default.dim(` ${result.message}`));
1559
2387
  }
2388
+ console.log(import_chalk16.default.dim(`
2389
+ Run ${import_chalk16.default.cyan("sylphx status")} to monitor progress.`));
1560
2390
  } catch (err) {
1561
2391
  spinner.fail(
1562
- import_chalk13.default.red(`Rollback failed: ${err instanceof Error ? err.message : String(err)}`)
2392
+ import_chalk16.default.red(`Rollback failed: ${err instanceof Error ? err.message : String(err)}`)
1563
2393
  );
1564
2394
  process.exit(1);
1565
2395
  }
1566
2396
  });
1567
2397
 
2398
+ // src/commands/services.ts
2399
+ var import_node_readline5 = __toESM(require("readline"));
2400
+ var import_chalk17 = __toESM(require("chalk"));
2401
+ var import_commander16 = require("commander");
2402
+ var import_ora14 = __toESM(require("ora"));
2403
+ function requireLinkedApp5() {
2404
+ const linked = config.getLinkedApp();
2405
+ if (!linked) {
2406
+ console.log(import_chalk17.default.red("No app linked. Run `sylphx link` first."));
2407
+ process.exit(1);
2408
+ }
2409
+ return linked;
2410
+ }
2411
+ function requireToken3() {
2412
+ const token = config.getToken();
2413
+ if (!token) {
2414
+ console.log(import_chalk17.default.red("Not authenticated. Run `sylphx login` first."));
2415
+ process.exit(1);
2416
+ }
2417
+ return token;
2418
+ }
2419
+ var listCmd3 = new import_commander16.Command("list").description("List all services in this project").action(async () => {
2420
+ requireToken3();
2421
+ const linked = requireLinkedApp5();
2422
+ const spinner = (0, import_ora14.default)("Fetching services...").start();
2423
+ try {
2424
+ const services = await api.listServices(linked.appId);
2425
+ spinner.stop();
2426
+ if (services.length === 0) {
2427
+ console.log(import_chalk17.default.dim(" No services defined."));
2428
+ return;
2429
+ }
2430
+ console.log();
2431
+ console.log(` ${import_chalk17.default.bold("NAME".padEnd(24))} ${"REPO".padEnd(36)} ENVS`);
2432
+ console.log(` ${"\u2500".repeat(70)}`);
2433
+ for (const svc of services) {
2434
+ const repo = svc.githubRepo ?? import_chalk17.default.dim("\u2014");
2435
+ const envs = svc.environmentCount ?? 0;
2436
+ console.log(` ${import_chalk17.default.cyan(svc.name.padEnd(24))} ${repo.padEnd(36)} ${envs}`);
2437
+ }
2438
+ console.log();
2439
+ } catch (err) {
2440
+ spinner.fail(import_chalk17.default.red(err instanceof Error ? err.message : String(err)));
2441
+ process.exit(1);
2442
+ }
2443
+ });
2444
+ var getCmd3 = new import_commander16.Command("get").description("Show details of a service").argument("<name>", "Service name").action(async (name) => {
2445
+ requireToken3();
2446
+ const linked = requireLinkedApp5();
2447
+ const spinner = (0, import_ora14.default)(`Fetching service '${name}'...`).start();
2448
+ try {
2449
+ const svc = await api.getService(linked.appId, name);
2450
+ spinner.stop();
2451
+ console.log();
2452
+ console.log(` ${import_chalk17.default.bold("Name:")} ${import_chalk17.default.cyan(svc.name)}`);
2453
+ console.log(` ${import_chalk17.default.bold("ID:")} ${svc.id}`);
2454
+ if (svc.githubRepo) console.log(` ${import_chalk17.default.bold("Repo:")} ${svc.githubRepo}`);
2455
+ if (svc.githubBranch) console.log(` ${import_chalk17.default.bold("Branch:")} ${svc.githubBranch}`);
2456
+ if (svc.dockerfilePath)
2457
+ console.log(` ${import_chalk17.default.bold("Dockerfile:")} ${svc.dockerfilePath}`);
2458
+ if (svc.port) console.log(` ${import_chalk17.default.bold("Port:")} ${svc.port}`);
2459
+ if (svc.publicNetworking !== void 0 && svc.publicNetworking !== null)
2460
+ console.log(
2461
+ ` ${import_chalk17.default.bold("Public:")} ${svc.publicNetworking ? import_chalk17.default.green("yes") : import_chalk17.default.dim("no")}`
2462
+ );
2463
+ console.log();
2464
+ } catch (err) {
2465
+ spinner.fail(import_chalk17.default.red(err instanceof Error ? err.message : String(err)));
2466
+ process.exit(1);
2467
+ }
2468
+ });
2469
+ var deployCmd = new import_commander16.Command("deploy").description("Deploy a specific service").argument("<name>", "Service name").option("--env <env>", "Environment (default: production)", "production").action(async (name, opts) => {
2470
+ requireToken3();
2471
+ const linked = requireLinkedApp5();
2472
+ const spinner = (0, import_ora14.default)(`Deploying service '${name}' [${opts.env}]...`).start();
2473
+ try {
2474
+ const result = await api.deployService(linked.appId, name, opts.env);
2475
+ spinner.succeed(import_chalk17.default.green(`\u2713 Deploy triggered for service '${name}'`));
2476
+ if (result.deploymentId) {
2477
+ console.log(import_chalk17.default.dim(` Deployment ID: ${result.deploymentId}`));
2478
+ }
2479
+ console.log(import_chalk17.default.dim(`
2480
+ Run ${import_chalk17.default.cyan("sylphx status")} to monitor.`));
2481
+ } catch (err) {
2482
+ spinner.fail(import_chalk17.default.red(err instanceof Error ? err.message : String(err)));
2483
+ process.exit(1);
2484
+ }
2485
+ });
2486
+ var rmCmd2 = new import_commander16.Command("rm").description("Delete a service").argument("<name>", "Service name").option("--force", "Skip confirmation prompt").action(async (name, opts) => {
2487
+ requireToken3();
2488
+ const linked = requireLinkedApp5();
2489
+ if (!opts.force) {
2490
+ const rl = import_node_readline5.default.createInterface({ input: process.stdin, output: process.stdout });
2491
+ await new Promise((resolve) => {
2492
+ rl.question(import_chalk17.default.yellow(` Delete service ${import_chalk17.default.bold(name)}? (y/N) `), (answer) => {
2493
+ rl.close();
2494
+ if (answer.toLowerCase() !== "y") {
2495
+ console.log(import_chalk17.default.dim(" Cancelled."));
2496
+ process.exit(0);
2497
+ }
2498
+ resolve();
2499
+ });
2500
+ });
2501
+ }
2502
+ const spinner = (0, import_ora14.default)(`Deleting service '${name}'...`).start();
2503
+ try {
2504
+ await api.deleteService(linked.appId, name);
2505
+ spinner.succeed(import_chalk17.default.green(`\u2713 Service '${name}' deleted`));
2506
+ } catch (err) {
2507
+ spinner.fail(import_chalk17.default.red(err instanceof Error ? err.message : String(err)));
2508
+ process.exit(1);
2509
+ }
2510
+ });
2511
+ var servicesCommand = new import_commander16.Command("services").description("Manage project services").addCommand(listCmd3).addCommand(getCmd3).addCommand(deployCmd).addCommand(rmCmd2).action(() => servicesCommand.help());
2512
+
1568
2513
  // src/commands/status.ts
1569
- var import_chalk14 = __toESM(require("chalk"));
1570
- var import_commander13 = require("commander");
1571
- var import_ora11 = __toESM(require("ora"));
2514
+ var import_chalk18 = __toESM(require("chalk"));
2515
+ var import_commander17 = require("commander");
2516
+ var import_ora15 = __toESM(require("ora"));
1572
2517
  function statusColor(status) {
1573
2518
  const s = status.toLowerCase();
1574
2519
  if (s === "running" || s === "active" || s === "healthy" || s === "success") {
1575
- return import_chalk14.default.green(status);
2520
+ return import_chalk18.default.green(status);
1576
2521
  }
1577
2522
  if (s === "deploying" || s === "building" || s === "pending") {
1578
- return import_chalk14.default.yellow(status);
2523
+ return import_chalk18.default.yellow(status);
1579
2524
  }
1580
2525
  if (s === "error" || s === "failed" || s === "stopped") {
1581
- return import_chalk14.default.red(status);
2526
+ return import_chalk18.default.red(status);
1582
2527
  }
1583
- return import_chalk14.default.white(status);
2528
+ return import_chalk18.default.white(status);
1584
2529
  }
1585
- var statusCommand = new import_commander13.Command("status").description("Show deployment status").option("--env <env>", "Environment (e.g. production, staging)").action(async (opts) => {
2530
+ var statusCommand = new import_commander17.Command("status").description("Show deployment status").option("--env <env>", "Environment (e.g. production, staging)").action(async (opts) => {
1586
2531
  const token = config.getToken();
1587
2532
  if (!token) {
1588
- console.log(import_chalk14.default.red("Not authenticated. Run `sylphx login` first."));
2533
+ console.log(import_chalk18.default.red("Not authenticated. Run `sylphx login` first."));
1589
2534
  process.exit(1);
1590
2535
  }
1591
2536
  const linked = config.getLinkedApp();
1592
2537
  if (!linked) {
1593
- console.log(import_chalk14.default.red("No app linked. Run `sylphx link` first."));
2538
+ console.log(import_chalk18.default.red("No app linked. Run `sylphx link` first."));
1594
2539
  process.exit(1);
1595
2540
  }
1596
2541
  const env = opts.env ?? linked.defaultEnv;
1597
- const spinner = (0, import_ora11.default)(`Fetching status [${env}]...`).start();
2542
+ const spinner = (0, import_ora15.default)(`Fetching status [${env}]...`).start();
1598
2543
  try {
1599
2544
  const status = await api.getDeploymentStatus(linked.appId);
1600
2545
  spinner.stop();
1601
2546
  console.log("");
1602
- console.log(import_chalk14.default.bold(" Deployment Status"));
1603
- console.log(import_chalk14.default.dim(` ${"\u2500".repeat(40)}`));
2547
+ console.log(import_chalk18.default.bold(" Deployment Status"));
2548
+ console.log(import_chalk18.default.dim(` ${"\u2500".repeat(40)}`));
1604
2549
  console.log("");
1605
- console.log(` App: ${import_chalk14.default.cyan(linked.appId)}`);
1606
- console.log(` Environment: ${import_chalk14.default.white(env)}`);
2550
+ console.log(` App: ${import_chalk18.default.cyan(linked.appId)}`);
2551
+ console.log(` Environment: ${import_chalk18.default.white(env)}`);
1607
2552
  console.log(` Status: ${statusColor(status.status)}`);
1608
2553
  if (status.deployedAt) {
1609
2554
  const date = new Date(status.deployedAt);
1610
- console.log(` Deployed: ${import_chalk14.default.white(date.toLocaleString())}`);
2555
+ console.log(` Deployed: ${import_chalk18.default.white(date.toLocaleString())}`);
1611
2556
  }
1612
2557
  const envMatch = status.environments?.find((e) => e.envType === env) ?? status.environments?.find((e) => e.envType === "production") ?? status.environments?.[0];
1613
2558
  const displayUrl = status.url ?? envMatch?.url ?? null;
1614
2559
  if (displayUrl) {
1615
- console.log(` URL: ${import_chalk14.default.cyan(displayUrl)}`);
2560
+ console.log(` URL: ${import_chalk18.default.cyan(displayUrl)}`);
1616
2561
  }
1617
2562
  if (envMatch?.lastDeployedAt) {
1618
2563
  console.log(
1619
- ` Deployed: ${import_chalk14.default.white(new Date(envMatch.lastDeployedAt).toLocaleString())}`
2564
+ ` Deployed: ${import_chalk18.default.white(new Date(envMatch.lastDeployedAt).toLocaleString())}`
1620
2565
  );
1621
2566
  }
1622
2567
  if (status.replicas) {
1623
2568
  const { ready, desired } = status.replicas;
1624
- const replicaColor = ready === desired ? import_chalk14.default.green : import_chalk14.default.yellow;
2569
+ const replicaColor = ready === desired ? import_chalk18.default.green : import_chalk18.default.yellow;
1625
2570
  console.log(` Replicas: ${replicaColor(`${ready}/${desired}`)}`);
1626
2571
  }
1627
2572
  console.log("");
1628
2573
  } catch (err) {
1629
- spinner.fail(import_chalk14.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
2574
+ spinner.fail(import_chalk18.default.red(`Failed: ${err instanceof Error ? err.message : String(err)}`));
2575
+ process.exit(1);
2576
+ }
2577
+ });
2578
+
2579
+ // src/commands/storage.ts
2580
+ var import_node_readline6 = __toESM(require("readline"));
2581
+ var import_chalk19 = __toESM(require("chalk"));
2582
+ var import_commander18 = require("commander");
2583
+ var import_ora16 = __toESM(require("ora"));
2584
+ function requireLinkedApp6() {
2585
+ const linked = config.getLinkedApp();
2586
+ if (!linked) {
2587
+ console.log(import_chalk19.default.red("No app linked. Run `sylphx link` first."));
2588
+ process.exit(1);
2589
+ }
2590
+ return linked;
2591
+ }
2592
+ function requireToken4() {
2593
+ const token = config.getToken();
2594
+ if (!token) {
2595
+ console.log(import_chalk19.default.red("Not authenticated. Run `sylphx login` first."));
2596
+ process.exit(1);
2597
+ }
2598
+ return token;
2599
+ }
2600
+ var listCmd4 = new import_commander18.Command("list").description("List storage resources for this project").option("--env <env>", "Environment (default: production)", "production").action(async (opts) => {
2601
+ requireToken4();
2602
+ const linked = requireLinkedApp6();
2603
+ const spinner = (0, import_ora16.default)("Fetching storage resources...").start();
2604
+ try {
2605
+ const resources = await api.listStorage(linked.appId, opts.env);
2606
+ spinner.stop();
2607
+ if (resources.length === 0) {
2608
+ console.log(import_chalk19.default.dim(" No storage resources provisioned."));
2609
+ console.log(
2610
+ import_chalk19.default.dim(`
2611
+ Run ${import_chalk19.default.cyan("sylphx storage create")} to provision storage.`)
2612
+ );
2613
+ return;
2614
+ }
2615
+ console.log();
2616
+ for (const r of resources) {
2617
+ const label = [r.role ?? "primary", r.envType].filter(Boolean).join(" \xB7 ");
2618
+ console.log(` ${import_chalk19.default.cyan(label)} ${import_chalk19.default.dim(r.id)}`);
2619
+ if (r.publicUrl) {
2620
+ console.log(` ${import_chalk19.default.dim("URL:")} ${import_chalk19.default.cyan(r.publicUrl)}`);
2621
+ }
2622
+ if (r.bucket) {
2623
+ console.log(` ${import_chalk19.default.dim("Bucket:")} ${r.bucket}`);
2624
+ }
2625
+ }
2626
+ console.log();
2627
+ } catch (err) {
2628
+ spinner.fail(import_chalk19.default.red(err instanceof Error ? err.message : String(err)));
1630
2629
  process.exit(1);
1631
2630
  }
1632
2631
  });
2632
+ var createCmd = new import_commander18.Command("create").description("Provision blob storage for this project").argument("[name]", "Storage resource name (default: primary)", "primary").option("--env <env>", "Environment (default: production)", "production").action(async (name, opts) => {
2633
+ requireToken4();
2634
+ const linked = requireLinkedApp6();
2635
+ const spinner = (0, import_ora16.default)(`Provisioning storage '${name}' [${opts.env}]...`).start();
2636
+ try {
2637
+ const resource = await api.createStorage(linked.appId, { name, envType: opts.env });
2638
+ spinner.succeed(import_chalk19.default.green(`\u2713 Storage '${name}' provisioned`));
2639
+ console.log();
2640
+ console.log(` ${import_chalk19.default.dim("ID:")} ${resource.id}`);
2641
+ if (resource.publicUrl) {
2642
+ console.log(` ${import_chalk19.default.dim("Public URL:")} ${import_chalk19.default.cyan(resource.publicUrl)}`);
2643
+ }
2644
+ if (resource.envVars && Object.keys(resource.envVars).length > 0) {
2645
+ console.log();
2646
+ console.log(import_chalk19.default.bold(" Env vars to add to your project:"));
2647
+ for (const [k, v] of Object.entries(resource.envVars)) {
2648
+ console.log(` ${import_chalk19.default.cyan(k)}=${v}`);
2649
+ }
2650
+ console.log();
2651
+ console.log(
2652
+ import_chalk19.default.dim(
2653
+ ` Run ${import_chalk19.default.cyan("sylphx env set KEY=VALUE")} to add these to your project.`
2654
+ )
2655
+ );
2656
+ }
2657
+ } catch (err) {
2658
+ spinner.fail(import_chalk19.default.red(err instanceof Error ? err.message : String(err)));
2659
+ process.exit(1);
2660
+ }
2661
+ });
2662
+ var rmCmd3 = new import_commander18.Command("rm").description("Delete a storage resource").argument("<id>", "Storage resource ID").option("--force", "Skip confirmation prompt").action(async (id, opts) => {
2663
+ requireToken4();
2664
+ const linked = requireLinkedApp6();
2665
+ if (!opts.force) {
2666
+ const rl = import_node_readline6.default.createInterface({ input: process.stdin, output: process.stdout });
2667
+ await new Promise((resolve) => {
2668
+ rl.question(
2669
+ import_chalk19.default.yellow(` \u26A0\uFE0F Delete storage ${import_chalk19.default.bold(id)}? This is irreversible. (y/N) `),
2670
+ (answer) => {
2671
+ rl.close();
2672
+ if (answer.toLowerCase() !== "y") {
2673
+ console.log(import_chalk19.default.dim(" Cancelled."));
2674
+ process.exit(0);
2675
+ }
2676
+ resolve();
2677
+ }
2678
+ );
2679
+ });
2680
+ }
2681
+ const spinner = (0, import_ora16.default)(`Deleting storage ${id}...`).start();
2682
+ try {
2683
+ await api.deleteStorage(linked.appId, id);
2684
+ spinner.succeed(import_chalk19.default.green(`\u2713 Storage ${id} deleted`));
2685
+ } catch (err) {
2686
+ spinner.fail(import_chalk19.default.red(err instanceof Error ? err.message : String(err)));
2687
+ process.exit(1);
2688
+ }
2689
+ });
2690
+ var storageCommand = new import_commander18.Command("storage").description("Manage blob storage resources").addCommand(listCmd4).addCommand(createCmd).addCommand(rmCmd3).action(() => storageCommand.help());
1633
2691
 
1634
2692
  // src/commands/unlink.ts
1635
- var import_chalk15 = __toESM(require("chalk"));
1636
- var import_commander14 = require("commander");
1637
- var unlinkCommand = new import_commander14.Command("unlink").description("Unlink current directory from its linked app").action(() => {
2693
+ var import_chalk20 = __toESM(require("chalk"));
2694
+ var import_commander19 = require("commander");
2695
+ var unlinkCommand = new import_commander19.Command("unlink").description("Unlink current directory from its linked app").action(() => {
1638
2696
  const linked = config.getLinkedApp();
1639
2697
  if (!linked) {
1640
- console.log(import_chalk15.default.yellow(" This directory is not linked to any app."));
2698
+ console.log(import_chalk20.default.yellow(" This directory is not linked to any app."));
1641
2699
  process.exit(0);
1642
2700
  }
1643
2701
  const appId = linked.appId;
1644
2702
  config.unlinkApp();
1645
2703
  console.log("");
1646
- console.log(import_chalk15.default.green(`\u2713 Unlinked from ${import_chalk15.default.bold(appId)}`));
1647
- console.log(import_chalk15.default.dim(` Directory: ${process.cwd()}`));
2704
+ console.log(import_chalk20.default.green(`\u2713 Unlinked from ${import_chalk20.default.bold(appId)}`));
2705
+ console.log(import_chalk20.default.dim(` Directory: ${process.cwd()}`));
1648
2706
  console.log("");
1649
2707
  });
1650
2708
 
2709
+ // src/commands/volumes.ts
2710
+ var import_node_readline7 = __toESM(require("readline"));
2711
+ var import_chalk21 = __toESM(require("chalk"));
2712
+ var import_commander20 = require("commander");
2713
+ var import_ora17 = __toESM(require("ora"));
2714
+ function requireLinkedApp7() {
2715
+ const linked = config.getLinkedApp();
2716
+ if (!linked) {
2717
+ console.log(import_chalk21.default.red("No app linked. Run `sylphx link` first."));
2718
+ process.exit(1);
2719
+ }
2720
+ return linked;
2721
+ }
2722
+ function requireToken5() {
2723
+ const token = config.getToken();
2724
+ if (!token) {
2725
+ console.log(import_chalk21.default.red("Not authenticated. Run `sylphx login` first."));
2726
+ process.exit(1);
2727
+ }
2728
+ return token;
2729
+ }
2730
+ var listCmd5 = new import_commander20.Command("list").description("List persistent volumes for this project").action(async () => {
2731
+ requireToken5();
2732
+ const linked = requireLinkedApp7();
2733
+ const spinner = (0, import_ora17.default)("Fetching volumes...").start();
2734
+ try {
2735
+ const volumes = await api.listVolumes(linked.appId);
2736
+ spinner.stop();
2737
+ if (volumes.length === 0) {
2738
+ console.log(import_chalk21.default.dim(" No volumes provisioned."));
2739
+ console.log(
2740
+ import_chalk21.default.dim(`
2741
+ Run ${import_chalk21.default.cyan("sylphx volumes create <name>")} to provision.`)
2742
+ );
2743
+ return;
2744
+ }
2745
+ console.log();
2746
+ console.log(
2747
+ ` ${import_chalk21.default.bold("NAME".padEnd(24))} ${"SIZE".padEnd(8)} ${"STATUS".padEnd(14)} ID`
2748
+ );
2749
+ console.log(` ${"\u2500".repeat(70)}`);
2750
+ for (const v of volumes) {
2751
+ const statusColor2 = v.status === "ready" ? import_chalk21.default.green((v.status ?? "unknown").padEnd(14)) : import_chalk21.default.yellow((v.status ?? "unknown").padEnd(14));
2752
+ const size = v.sizeGb ? `${v.sizeGb}Gi`.padEnd(8) : "\u2014".padEnd(8);
2753
+ console.log(
2754
+ ` ${import_chalk21.default.cyan(v.name.padEnd(24))} ${size} ${statusColor2} ${import_chalk21.default.dim(v.id)}`
2755
+ );
2756
+ }
2757
+ console.log();
2758
+ } catch (err) {
2759
+ spinner.fail(import_chalk21.default.red(err instanceof Error ? err.message : String(err)));
2760
+ process.exit(1);
2761
+ }
2762
+ });
2763
+ var createCmd2 = new import_commander20.Command("create").description("Provision a persistent volume").argument("<name>", "Volume name").option("--size <gb>", "Size in GiB (default: 10)", "10").option(
2764
+ "--access-mode <mode>",
2765
+ "ReadWriteOnce or ReadWriteMany (default: ReadWriteOnce)",
2766
+ "ReadWriteOnce"
2767
+ ).option("--mount <path>", "Default mount path (optional)").action(async (name, opts) => {
2768
+ requireToken5();
2769
+ const linked = requireLinkedApp7();
2770
+ const sizeGb = Number.parseInt(opts.size, 10);
2771
+ if (Number.isNaN(sizeGb) || sizeGb < 1) {
2772
+ console.log(import_chalk21.default.red(" --size must be a number >= 1"));
2773
+ process.exit(1);
2774
+ }
2775
+ if (!["ReadWriteOnce", "ReadWriteMany"].includes(opts.accessMode)) {
2776
+ console.log(import_chalk21.default.red(" --access-mode must be ReadWriteOnce or ReadWriteMany"));
2777
+ process.exit(1);
2778
+ }
2779
+ const spinner = (0, import_ora17.default)(`Provisioning volume '${name}' (${sizeGb}Gi)...`).start();
2780
+ try {
2781
+ const result = await api.createVolume(linked.appId, {
2782
+ name,
2783
+ sizeGb,
2784
+ accessMode: opts.accessMode,
2785
+ mountPath: opts.mount
2786
+ });
2787
+ const vol = result.volume;
2788
+ spinner.succeed(import_chalk21.default.green(`\u2713 Volume '${vol.name}' provisioned`));
2789
+ console.log();
2790
+ console.log(` ${import_chalk21.default.dim("ID:")} ${vol.id}`);
2791
+ console.log(` ${import_chalk21.default.dim("Size:")} ${vol.sizeGb}Gi`);
2792
+ console.log(` ${import_chalk21.default.dim("Access:")} ${vol.accessMode ?? "ReadWriteOnce"}`);
2793
+ console.log();
2794
+ } catch (err) {
2795
+ spinner.fail(import_chalk21.default.red(err instanceof Error ? err.message : String(err)));
2796
+ process.exit(1);
2797
+ }
2798
+ });
2799
+ var rmCmd4 = new import_commander20.Command("rm").description("Delete a persistent volume").argument("<id>", "Volume ID").option("--force", "Skip confirmation prompt").action(async (id, opts) => {
2800
+ requireToken5();
2801
+ const linked = requireLinkedApp7();
2802
+ if (!opts.force) {
2803
+ const rl = import_node_readline7.default.createInterface({ input: process.stdin, output: process.stdout });
2804
+ await new Promise((resolve) => {
2805
+ rl.question(
2806
+ import_chalk21.default.yellow(` \u26A0\uFE0F Delete volume ${import_chalk21.default.bold(id)}? This will destroy all data. (y/N) `),
2807
+ (answer) => {
2808
+ rl.close();
2809
+ if (answer.toLowerCase() !== "y") {
2810
+ console.log(import_chalk21.default.dim(" Cancelled."));
2811
+ process.exit(0);
2812
+ }
2813
+ resolve();
2814
+ }
2815
+ );
2816
+ });
2817
+ }
2818
+ const spinner = (0, import_ora17.default)(`Deleting volume ${id}...`).start();
2819
+ try {
2820
+ await api.deleteVolume(linked.appId, id);
2821
+ spinner.succeed(import_chalk21.default.green(`\u2713 Volume ${id} deleted`));
2822
+ } catch (err) {
2823
+ spinner.fail(import_chalk21.default.red(err instanceof Error ? err.message : String(err)));
2824
+ process.exit(1);
2825
+ }
2826
+ });
2827
+ var volumesCommand = new import_commander20.Command("volumes").description("Manage persistent volumes").addCommand(listCmd5).addCommand(createCmd2).addCommand(rmCmd4).action(() => volumesCommand.help());
2828
+
1651
2829
  // src/commands/whoami.ts
1652
- var import_chalk16 = __toESM(require("chalk"));
1653
- var import_commander15 = require("commander");
1654
- var import_ora12 = __toESM(require("ora"));
1655
- var whoamiCommand = new import_commander15.Command("whoami").description("Show current user, org, and linked app").action(async () => {
2830
+ var import_chalk22 = __toESM(require("chalk"));
2831
+ var import_commander21 = require("commander");
2832
+ var import_ora18 = __toESM(require("ora"));
2833
+ var whoamiCommand = new import_commander21.Command("whoami").description("Show current user, org, and linked app").action(async () => {
1656
2834
  const token = config.getToken();
1657
2835
  if (!token) {
1658
- console.log(import_chalk16.default.red("Not authenticated. Run `sylphx login` first."));
2836
+ console.log(import_chalk22.default.red("Not authenticated. Run `sylphx login` first."));
1659
2837
  process.exit(1);
1660
2838
  }
1661
- const spinner = (0, import_ora12.default)("Fetching account info...").start();
2839
+ const spinner = (0, import_ora18.default)("Fetching account info...").start();
1662
2840
  try {
1663
2841
  const me = await api.whoami();
1664
2842
  spinner.stop();
1665
2843
  console.log("");
1666
- console.log(import_chalk16.default.bold(" Account"));
1667
- console.log(` User: ${import_chalk16.default.cyan(me.user.email)}`);
1668
- console.log(` Name: ${import_chalk16.default.white(me.user.name)}`);
1669
- console.log(` ID: ${import_chalk16.default.dim(me.user.id)}`);
2844
+ console.log(import_chalk22.default.bold(" Account"));
2845
+ console.log(` User: ${import_chalk22.default.cyan(me.user.email)}`);
2846
+ console.log(` Name: ${import_chalk22.default.white(me.user.name)}`);
2847
+ console.log(` ID: ${import_chalk22.default.dim(me.user.id)}`);
1670
2848
  if (me.orgs.length > 0) {
1671
2849
  console.log("");
1672
- console.log(import_chalk16.default.bold(" Organizations"));
2850
+ console.log(import_chalk22.default.bold(" Organizations"));
1673
2851
  for (const org of me.orgs) {
1674
2852
  console.log(
1675
- ` ${import_chalk16.default.cyan(org.slug)} ${import_chalk16.default.dim(`(${org.name})`)}`
2853
+ ` ${import_chalk22.default.cyan(org.slug)} ${import_chalk22.default.dim(`(${org.name})`)}`
1676
2854
  );
1677
2855
  }
1678
2856
  }
1679
2857
  const linkedApp = config.getLinkedApp();
1680
2858
  if (linkedApp) {
1681
2859
  console.log("");
1682
- console.log(import_chalk16.default.bold(" Linked App"));
1683
- console.log(` App: ${import_chalk16.default.cyan(linkedApp.appId)}`);
1684
- console.log(` Org: ${import_chalk16.default.white(linkedApp.orgId)}`);
1685
- console.log(` Env: ${import_chalk16.default.white(linkedApp.defaultEnv)}`);
2860
+ console.log(import_chalk22.default.bold(" Linked App"));
2861
+ console.log(` App: ${import_chalk22.default.cyan(linkedApp.appId)}`);
2862
+ console.log(` Org: ${import_chalk22.default.white(linkedApp.orgId)}`);
2863
+ console.log(` Env: ${import_chalk22.default.white(linkedApp.defaultEnv)}`);
1686
2864
  } else {
1687
2865
  console.log("");
1688
2866
  console.log(
1689
- import_chalk16.default.dim(" No app linked. Run `sylphx link` to link one.")
2867
+ import_chalk22.default.dim(" No app linked. Run `sylphx link` to link one.")
1690
2868
  );
1691
2869
  }
1692
2870
  console.log("");
1693
2871
  } catch (err) {
1694
2872
  spinner.fail(
1695
- import_chalk16.default.red(
2873
+ import_chalk22.default.red(
1696
2874
  `Failed: ${err instanceof Error ? err.message : String(err)}`
1697
2875
  )
1698
2876
  );
@@ -1701,44 +2879,49 @@ var whoamiCommand = new import_commander15.Command("whoami").description("Show c
1701
2879
  });
1702
2880
 
1703
2881
  // src/index.ts
1704
- var { version } = package_default;
1705
- var program = new import_commander16.Command();
1706
- program.name("sylphx").description(`${import_chalk17.default.bold("Sylphx Platform CLI")} \u2014 deploy and manage your applications`).version(version, "-v, --version", "Print version").helpOption("-h, --help", "Show help").addHelpText(
2882
+ var { version: version4 } = package_default;
2883
+ var program = new import_commander22.Command();
2884
+ program.name("sylphx").description(`${import_chalk23.default.bold("Sylphx Platform CLI")} \u2014 deploy and manage your applications`).version(version4, "-v, --version", "Print version").helpOption("-h, --help", "Show help").addHelpText(
1707
2885
  "after",
1708
2886
  `
1709
- ${import_chalk17.default.bold("Examples:")}
1710
- ${import_chalk17.default.cyan("sylphx login")} Authenticate with Sylphx
1711
- ${import_chalk17.default.cyan("sylphx init my-app")} Create and link a new project
1712
- ${import_chalk17.default.cyan("sylphx link")} Link current directory to an app
1713
- ${import_chalk17.default.cyan("sylphx unlink")} Unlink current directory from its app
1714
- ${import_chalk17.default.cyan("sylphx deploy")} Deploy to production
1715
- ${import_chalk17.default.cyan("sylphx deploy --env staging")} Deploy to staging
1716
- ${import_chalk17.default.cyan("sylphx logs -f")} Stream live logs
1717
- ${import_chalk17.default.cyan("sylphx env list")} List environment variables
1718
- ${import_chalk17.default.cyan("sylphx env set PORT=3000")} Set an env var
1719
- ${import_chalk17.default.cyan("sylphx env pull --file .env")} Pull env vars to a local .env file
1720
- ${import_chalk17.default.cyan("sylphx env push --file .env")} Push .env to the platform
1721
- ${import_chalk17.default.cyan("sylphx status")} Check deployment status
1722
- ${import_chalk17.default.cyan("sylphx projects list")} List all projects
1723
- ${import_chalk17.default.cyan("sylphx db create my-db")} Provision a database
1724
- ${import_chalk17.default.cyan("sylphx db list")} List databases
2887
+ ${import_chalk23.default.bold("Examples:")}
2888
+ ${import_chalk23.default.cyan("sylphx login")} Authenticate with Sylphx
2889
+ ${import_chalk23.default.cyan("sylphx init my-app")} Create and link a new project
2890
+ ${import_chalk23.default.cyan("sylphx link")} Link current directory to an app
2891
+ ${import_chalk23.default.cyan("sylphx deploy")} Deploy to production
2892
+ ${import_chalk23.default.cyan("sylphx deploy --env staging")} Deploy to staging
2893
+ ${import_chalk23.default.cyan("sylphx promote --from staging")} Promote staging build \u2192 production
2894
+ ${import_chalk23.default.cyan("sylphx rollback")} Rollback to previous deployment
2895
+ ${import_chalk23.default.cyan("sylphx logs -f")} Stream live logs
2896
+ ${import_chalk23.default.cyan("sylphx status")} Check deployment status
2897
+ ${import_chalk23.default.cyan("sylphx env list")} List environment variables
2898
+ ${import_chalk23.default.cyan("sylphx env set PORT=3000")} Set an env var
2899
+ ${import_chalk23.default.cyan("sylphx config list")} List remote config keys
2900
+ ${import_chalk23.default.cyan("sylphx config set KEY=value")} Set a remote config key
2901
+ ${import_chalk23.default.cyan("sylphx db create my-db")} Provision a PostgreSQL database
2902
+ ${import_chalk23.default.cyan("sylphx storage create")} Provision blob storage
2903
+ ${import_chalk23.default.cyan("sylphx volumes create data --size 20")} Provision a persistent volume
2904
+ ${import_chalk23.default.cyan("sylphx services list")} List project services
2905
+ ${import_chalk23.default.cyan("sylphx services deploy web")} Deploy a specific service
2906
+ ${import_chalk23.default.cyan("sylphx resources bind <id>")} Bind a resource to this project
2907
+ ${import_chalk23.default.cyan("sylphx projects list")} List all projects
1725
2908
 
1726
- ${import_chalk17.default.bold("Documentation:")}
1727
- ${import_chalk17.default.underline("https://docs.sylphx.com/cli")}
2909
+ ${import_chalk23.default.bold("Documentation:")}
2910
+ ${import_chalk23.default.underline("https://docs.sylphx.com/cli")}
1728
2911
  `
1729
2912
  );
1730
- program.addCommand(loginCommand).addCommand(logoutCommand).addCommand(whoamiCommand).addCommand(initCommand).addCommand(linkCommand).addCommand(unlinkCommand).addCommand(deployCommand).addCommand(logsCommand).addCommand(envCommand).addCommand(domainsCommand).addCommand(rollbackCommand).addCommand(openCommand).addCommand(statusCommand).addCommand(projectsCommand).addCommand(dbCommand);
2913
+ program.addCommand(loginCommand).addCommand(logoutCommand).addCommand(whoamiCommand).addCommand(initCommand).addCommand(linkCommand).addCommand(unlinkCommand).addCommand(projectsCommand).addCommand(deployCommand).addCommand(promoteCommand).addCommand(rollbackCommand).addCommand(statusCommand).addCommand(logsCommand).addCommand(envCommand).addCommand(configCommand).addCommand(dbCommand).addCommand(storageCommand).addCommand(volumesCommand).addCommand(resourcesCommand).addCommand(servicesCommand).addCommand(domainsCommand).addCommand(openCommand);
1731
2914
  program.on("command:*", (operands) => {
1732
- console.error(import_chalk17.default.red(`
1733
- Unknown command: ${import_chalk17.default.bold(operands[0] ?? "")}
2915
+ console.error(import_chalk23.default.red(`
2916
+ Unknown command: ${import_chalk23.default.bold(operands[0] ?? "")}
1734
2917
  `));
1735
- console.log(` Run ${import_chalk17.default.cyan("sylphx --help")} for usage.
2918
+ console.log(` Run ${import_chalk23.default.cyan("sylphx --help")} for usage.
1736
2919
  `);
1737
2920
  process.exit(1);
1738
2921
  });
1739
2922
  program.parseAsync(process.argv).catch((err) => {
1740
2923
  const msg = err instanceof Error ? err.message : String(err);
1741
- console.error(import_chalk17.default.red(`
2924
+ console.error(import_chalk23.default.red(`
1742
2925
  Error: ${msg}
1743
2926
  `));
1744
2927
  process.exit(1);