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