@miosa/cli 1.1.14 → 1.1.18

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 (51) hide show
  1. package/README.md +47 -0
  2. package/dist/api-validation.d.ts +53 -0
  3. package/dist/api-validation.d.ts.map +1 -0
  4. package/dist/api-validation.js +200 -0
  5. package/dist/api-validation.js.map +1 -0
  6. package/dist/bin/miosa.js +2 -0
  7. package/dist/bin/miosa.js.map +1 -1
  8. package/dist/client.d.ts +20 -1
  9. package/dist/client.d.ts.map +1 -1
  10. package/dist/client.js +141 -43
  11. package/dist/client.js.map +1 -1
  12. package/dist/commands/databases.d.ts.map +1 -1
  13. package/dist/commands/databases.js +11 -34
  14. package/dist/commands/databases.js.map +1 -1
  15. package/dist/commands/db.js +7 -7
  16. package/dist/commands/db.js.map +1 -1
  17. package/dist/commands/deploy.d.ts.map +1 -1
  18. package/dist/commands/deploy.js +227 -77
  19. package/dist/commands/deploy.js.map +1 -1
  20. package/dist/commands/devices.d.ts.map +1 -1
  21. package/dist/commands/devices.js +8 -9
  22. package/dist/commands/devices.js.map +1 -1
  23. package/dist/commands/forge.d.ts +3 -0
  24. package/dist/commands/forge.d.ts.map +1 -0
  25. package/dist/commands/forge.js +254 -0
  26. package/dist/commands/forge.js.map +1 -0
  27. package/dist/commands/login.js +1 -1
  28. package/dist/commands/logout.js +3 -3
  29. package/dist/commands/logout.js.map +1 -1
  30. package/dist/commands/templates.d.ts.map +1 -1
  31. package/dist/commands/templates.js +540 -91
  32. package/dist/commands/templates.js.map +1 -1
  33. package/dist/commands/util.d.ts +6 -1
  34. package/dist/commands/util.d.ts.map +1 -1
  35. package/dist/commands/util.js +47 -27
  36. package/dist/commands/util.js.map +1 -1
  37. package/dist/commands/watch.d.ts.map +1 -1
  38. package/dist/commands/watch.js +5 -1
  39. package/dist/commands/watch.js.map +1 -1
  40. package/dist/endpoint.d.ts +37 -0
  41. package/dist/endpoint.d.ts.map +1 -0
  42. package/dist/endpoint.js +123 -0
  43. package/dist/endpoint.js.map +1 -0
  44. package/dist/errors.d.ts +18 -2
  45. package/dist/errors.d.ts.map +1 -1
  46. package/dist/errors.js +37 -4
  47. package/dist/errors.js.map +1 -1
  48. package/dist/types.d.ts +8 -0
  49. package/dist/types.d.ts.map +1 -1
  50. package/dist/types.js.map +1 -1
  51. package/package.json +1 -1
@@ -6,7 +6,7 @@ import { request } from "undici";
6
6
  import { loadConfig } from "../config.js";
7
7
  import { MiosaClient, parseSse } from "../client.js";
8
8
  import { ENV_FILE_OPTION_HELP, ENV_INLINE_SHELL_WARNING, ENV_STDIN_OPTION_HELP, resolveEnvInputs, } from "./env-input.js";
9
- import { handleError, isJsonMode } from "./util.js";
9
+ import { errorExitCode, handleError, isJsonMode, jsonErrorPayload, } from "./util.js";
10
10
  import { spin } from "../ui/spinner.js";
11
11
  import { renderTable } from "../ui/table.js";
12
12
  import { errorEnvelope, hintBlock, icon, kvPanel, printBanner, printElapsed, formatDuration, } from "../ui/render.js";
@@ -153,7 +153,11 @@ async function probePublicUrl(publicUrl, probePath, timeoutMs) {
153
153
  signal: controller.signal,
154
154
  });
155
155
  res.body.resume();
156
- return { ok: res.statusCode >= 200 && res.statusCode < 500, status: res.statusCode, url: url.toString() };
156
+ return {
157
+ ok: res.statusCode >= 200 && res.statusCode < 500,
158
+ status: res.statusCode,
159
+ url: url.toString(),
160
+ };
157
161
  }
158
162
  catch (error) {
159
163
  return {
@@ -174,7 +178,10 @@ async function proveDeployment(client, deploymentId, opts) {
174
178
  addProofCheck(checks, "deployment_row", true, `Deployment ${deployment.id} exists with state=${deployment.state}.`, { state: deployment.state });
175
179
  addProofCheck(checks, "deployment_running", deployment.state === "running", deployment.state === "running"
176
180
  ? "Deployment is marked running."
177
- : `Deployment state is ${deployment.state}, expected running.`, { state: deployment.state }, ["miosa deploy logs <deployment-id>", "miosa deploy redeploy <deployment-id>"]);
181
+ : `Deployment state is ${deployment.state}, expected running.`, { state: deployment.state }, [
182
+ "miosa deploy logs <deployment-id>",
183
+ "miosa deploy redeploy <deployment-id>",
184
+ ]);
178
185
  addProofCheck(checks, "public_url_present", Boolean(publicUrl), publicUrl ? `Public URL is ${publicUrl}.` : "Deployment has no public URL.", { public_url: publicUrl });
179
186
  if (product === "docker_deploy") {
180
187
  const app = dockerDeployApp(deployment);
@@ -183,20 +190,27 @@ async function proveDeployment(client, deploymentId, opts) {
183
190
  stringField(deployment.metadata, "docker_deploy_host_id") ??
184
191
  stringField(app, "docker_deploy_host_id") ??
185
192
  stringField(app, "host_id");
186
- addProofCheck(checks, "docker_deploy_host_link", Boolean(hostId), hostId ? `Deployment links App Engine host ${hostId}.` : "Deployment has no App Engine host id.", { docker_deploy_host_id: hostId }, ["miosa docker-deploy ensure --wait --json"]);
193
+ addProofCheck(checks, "docker_deploy_host_link", Boolean(hostId), hostId
194
+ ? `Deployment links App Engine host ${hostId}.`
195
+ : "Deployment has no App Engine host id.", { docker_deploy_host_id: hostId }, ["miosa docker-deploy ensure --wait --json"]);
187
196
  if (hostId) {
188
197
  try {
189
198
  const host = await client.apiGet(`/api/v1/docker-deploy/hosts/${encodeURIComponent(hostId)}`);
190
199
  const hostRecord = asRecord(asRecord(host).host ?? asRecord(host).data ?? host);
191
200
  const status = stringField(hostRecord, "status");
192
201
  const applianceStatus = stringField(hostRecord, "appliance_status");
193
- addProofCheck(checks, "docker_deploy_host_ready", status === "active" && applianceStatus === "healthy", `Host status=${status ?? "unknown"}, appliance=${applianceStatus ?? "unknown"}.`, { status, appliance_status: applianceStatus }, ["miosa docker-deploy show <host-id> --json", "miosa docker-deploy ensure --wait --json"]);
202
+ addProofCheck(checks, "docker_deploy_host_ready", status === "active" && applianceStatus === "healthy", `Host status=${status ?? "unknown"}, appliance=${applianceStatus ?? "unknown"}.`, { status, appliance_status: applianceStatus }, [
203
+ "miosa docker-deploy show <host-id> --json",
204
+ "miosa docker-deploy ensure --wait --json",
205
+ ]);
194
206
  }
195
207
  catch (error) {
196
208
  addProofCheck(checks, "docker_deploy_host_ready", false, error instanceof Error ? error.message : String(error), { docker_deploy_host_id: hostId });
197
209
  }
198
210
  }
199
- addProofCheck(checks, "docker_deploy_app_row", Boolean(app), app ? `App Engine app status=${stringField(app, "status") ?? "unknown"}.` : "App Engine app row is missing.", app
211
+ addProofCheck(checks, "docker_deploy_app_row", Boolean(app), app
212
+ ? `App Engine app status=${stringField(app, "status") ?? "unknown"}.`
213
+ : "App Engine app row is missing.", app
200
214
  ? {
201
215
  status: stringField(app, "status"),
202
216
  app_id: stringField(app, "app_id"),
@@ -219,7 +233,10 @@ async function proveDeployment(client, deploymentId, opts) {
219
233
  const probe = await probePublicUrl(publicUrl, opts.probePath, opts.timeoutMs);
220
234
  addProofCheck(checks, "public_url_probe", probe.ok, probe.ok
221
235
  ? `Public URL returned HTTP ${probe.status}.`
222
- : `Public URL probe failed: ${probe.error ?? `HTTP ${probe.status}`}.`, probe, ["Check DNS/custom domain routing.", "Check app logs and container health."]);
236
+ : `Public URL probe failed: ${probe.error ?? `HTTP ${probe.status}`}.`, probe, [
237
+ "Check DNS/custom domain routing.",
238
+ "Check app logs and container health.",
239
+ ]);
223
240
  }
224
241
  const nextActions = checks
225
242
  .filter((check) => !check.ok)
@@ -420,16 +437,24 @@ function formatSeconds(value) {
420
437
  return formatMetricValue(value);
421
438
  return formatDuration(value * 1000);
422
439
  }
423
- // ── register ──────────────────────────────────────────────────────────────────
424
440
  export function register(program) {
425
441
  const deploy = program
426
442
  .command("deploy")
427
443
  .alias("launch")
428
444
  .description("Deploy a GitHub repo; use --docker-deploy for the recommended production runtime")
429
445
  .option("--docker-deploy", "Create the deployment on this workspace's dedicated App Engine runtime")
446
+ .option("--name <name>", "Deployment name (defaults to the directory name)")
447
+ .option("--branch <branch>", "Git branch to deploy (defaults to the current branch)")
448
+ .option("--build-command <cmd>", "Build command (defaults to framework detection)")
449
+ .option("--run-command <cmd>", "Run command (defaults to framework detection)")
450
+ .option("--static", "Deploy static files without build or run commands")
451
+ .option("-y, --yes", "Accept detected defaults and skip interactive confirmation")
430
452
  .addHelpText("after", `
431
453
  Examples:
432
454
  miosa deploy --docker-deploy Recommended: deploy current directory to App Engine
455
+ miosa deploy --docker-deploy --json Prompt-free deploy for CI and agents
456
+ miosa deploy --docker-deploy --static --name report --branch main --yes --json
457
+ miosa deploy --name app --branch main --build-command "npm run build" --run-command "npm start" --yes --json
433
458
  miosa deploy Deploy current directory (auto-detects framework)
434
459
  miosa deploy list List all deployments
435
460
  miosa deploy logs Tail build logs for this project
@@ -445,31 +470,49 @@ Examples:
445
470
  const cwd = process.cwd();
446
471
  const config = loadConfig();
447
472
  const client = new MiosaClient(config);
473
+ const json = isJsonMode(opts);
448
474
  // ── Check for existing .miosa.json first (skip git for redeploys) ──
449
475
  let projectCfg = loadProjectConfig(cwd);
450
476
  let deploymentId;
477
+ let queuedBuild = null;
478
+ let createdDeployment = null;
479
+ let webhookSecret = null;
480
+ let buildQueueError = null;
451
481
  if (projectCfg) {
452
482
  // ── Re-deploy from existing .miosa.json ──────────────────────
453
- console.log();
454
- console.log(chalk.dim(" Found .miosa.json redeploying existing deployment"));
455
- console.log(` ${chalk.bold("Project")} ${projectCfg.name} (${projectCfg.framework})`);
456
- console.log(` ${chalk.bold("Branch")} ${projectCfg.branch}`);
457
- console.log();
483
+ const firstDeployOnlyFlags = [
484
+ opts.name !== undefined ? "--name" : null,
485
+ opts.branch !== undefined ? "--branch" : null,
486
+ opts.buildCommand !== undefined ? "--build-command" : null,
487
+ opts.runCommand !== undefined ? "--run-command" : null,
488
+ opts.static ? "--static" : null,
489
+ ].filter((flag) => flag !== null);
490
+ if (firstDeployOnlyFlags.length > 0) {
491
+ throw new UserError(`.miosa.json already exists; ${firstDeployOnlyFlags.join(", ")} only apply when creating a deployment.`, "Remove these flags to redeploy the saved configuration, or delete .miosa.json to create a new deployment.");
492
+ }
493
+ if (!json) {
494
+ console.log();
495
+ console.log(chalk.dim(" Found .miosa.json — redeploying existing deployment"));
496
+ console.log(` ${chalk.bold("Project")} ${projectCfg.name} (${projectCfg.framework})`);
497
+ console.log(` ${chalk.bold("Branch")} ${projectCfg.branch}`);
498
+ console.log();
499
+ }
458
500
  deploymentId = projectCfg.deploymentId;
459
- const buildSpinner = spin("Queuing build...");
501
+ const buildSpinner = json ? null : spin("Queuing build...");
460
502
  try {
461
- await client.redeployDeployment(deploymentId);
462
- buildSpinner.succeed("Build queued");
503
+ queuedBuild = await client.redeployDeployment(deploymentId);
504
+ buildSpinner?.succeed("Build queued");
463
505
  }
464
506
  catch (err) {
465
- buildSpinner.fail("Failed to queue build");
507
+ buildSpinner?.fail("Failed to queue build");
466
508
  handleError(err);
467
509
  }
468
510
  }
469
511
  else {
470
512
  // ── First deploy: verify git repo + remote, then detect ───────
471
513
  const { repoUrl, currentBranch } = getGitInfo(cwd);
472
- console.log();
514
+ if (!json)
515
+ console.log();
473
516
  const detection = detectFramework(cwd);
474
517
  let framework = "unknown";
475
518
  let buildCommand = "npm run build";
@@ -477,60 +520,105 @@ Examples:
477
520
  if (detection) {
478
521
  const label = FRAMEWORK_LABELS[detection.framework] ??
479
522
  detection.framework;
480
- console.log(` Detected: ${chalk.cyan(label)} (confidence ${detection.confidence}%)`);
523
+ if (!json) {
524
+ console.log(` Detected: ${chalk.cyan(label)} (confidence ${detection.confidence}%)`);
525
+ }
481
526
  framework = detection.framework;
482
527
  buildCommand = detection.buildCommand;
483
528
  runCommand = detection.runCommand;
484
529
  }
485
530
  else {
486
- console.log(chalk.yellow(" Could not auto-detect framework. You can set build/run commands manually."));
531
+ if (!json) {
532
+ console.log(chalk.yellow(" Could not auto-detect framework. You can set build/run commands manually."));
533
+ }
534
+ }
535
+ if (!json) {
536
+ console.log(` Repo: ${chalk.dim(repoUrl)}`);
537
+ console.log(` Branch: ${chalk.dim(currentBranch)}`);
538
+ console.log();
539
+ }
540
+ const requiresNonInteractiveInputs = !process.stdin.isTTY || json || Boolean(opts.yes);
541
+ if (opts.static &&
542
+ (opts.buildCommand !== undefined || opts.runCommand !== undefined)) {
543
+ throw new UserError("--static cannot be combined with build or run commands.", "Remove --build-command and --run-command, or remove --static.");
544
+ }
545
+ if (requiresNonInteractiveInputs &&
546
+ !opts.static &&
547
+ !detection &&
548
+ (!opts.buildCommand || !opts.runCommand)) {
549
+ throw new UserError("Could not determine deployment commands non-interactively.", "Pass both --build-command <cmd> and --run-command <cmd>, or use --static for static sites.");
487
550
  }
488
- console.log(` Repo: ${chalk.dim(repoUrl)}`);
489
- console.log(` Branch: ${chalk.dim(currentBranch)}`);
490
- console.log();
491
- // ── Interactive prompts ──────────────────────────────────────
492
- const { default: inquirer } = await import("inquirer");
493
551
  const projectName = path.basename(cwd).replace(/[^a-z0-9-]/gi, "-");
494
- const answers = await inquirer.prompt([
495
- {
496
- type: "input",
497
- name: "name",
498
- message: "Deployment name:",
499
- default: projectName,
500
- validate: (v) => v.length > 0 ? true : "Name is required",
501
- },
502
- {
503
- type: "input",
504
- name: "branch",
505
- message: "Branch to deploy:",
506
- default: currentBranch,
507
- },
508
- {
509
- type: "input",
510
- name: "buildCommand",
511
- message: "Build command:",
512
- default: buildCommand,
513
- },
514
- {
515
- type: "input",
516
- name: "runCommand",
517
- message: "Run command:",
518
- default: runCommand,
519
- },
520
- {
552
+ let answers;
553
+ if (!requiresNonInteractiveInputs) {
554
+ const { default: inquirer } = await import("inquirer");
555
+ const identityQuestions = [
556
+ {
557
+ type: "input",
558
+ name: "name",
559
+ message: "Deployment name:",
560
+ default: opts.name ?? projectName,
561
+ validate: (v) => v.length > 0 ? true : "Name is required",
562
+ },
563
+ {
564
+ type: "input",
565
+ name: "branch",
566
+ message: "Branch to deploy:",
567
+ default: opts.branch ?? currentBranch,
568
+ },
569
+ ];
570
+ const confirmQuestion = {
521
571
  type: "confirm",
522
572
  name: "confirm",
523
573
  message: "Create deployment?",
524
574
  default: true,
525
- },
526
- ]);
575
+ };
576
+ if (opts.static) {
577
+ const staticAnswers = await inquirer.prompt([...identityQuestions, confirmQuestion]);
578
+ answers = {
579
+ ...staticAnswers,
580
+ buildCommand: "",
581
+ runCommand: "",
582
+ };
583
+ }
584
+ else {
585
+ answers = await inquirer.prompt([
586
+ ...identityQuestions,
587
+ {
588
+ type: "input",
589
+ name: "buildCommand",
590
+ message: "Build command:",
591
+ default: opts.buildCommand ?? buildCommand,
592
+ },
593
+ {
594
+ type: "input",
595
+ name: "runCommand",
596
+ message: "Run command:",
597
+ default: opts.runCommand ?? runCommand,
598
+ },
599
+ confirmQuestion,
600
+ ]);
601
+ }
602
+ }
603
+ else {
604
+ answers = {
605
+ name: opts.name ?? projectName,
606
+ branch: opts.branch ?? currentBranch,
607
+ buildCommand: opts.static
608
+ ? ""
609
+ : (opts.buildCommand ?? buildCommand),
610
+ runCommand: opts.static ? "" : (opts.runCommand ?? runCommand),
611
+ confirm: true,
612
+ };
613
+ }
527
614
  if (!answers.confirm) {
528
615
  console.log(chalk.dim(" Cancelled."));
529
616
  process.exit(0);
530
617
  }
531
618
  // ── Step 3: POST /api/v1/deployments ─────────────────────────
532
- const createSpinner = spin(`Creating deployment "${answers.name}"...`);
533
- let webhookSecret;
619
+ const createSpinner = json
620
+ ? null
621
+ : spin(`Creating deployment "${answers.name}"...`);
534
622
  let deployment;
535
623
  try {
536
624
  const metadata = opts.dockerDeploy
@@ -547,10 +635,11 @@ Examples:
547
635
  });
548
636
  deployment = result.data;
549
637
  webhookSecret = result.webhook_secret;
550
- createSpinner.succeed(`${productLabel(deploymentProduct(deployment))} deployment "${deployment.name}" created (slug: ${deployment.slug})`);
638
+ createdDeployment = deployment;
639
+ createSpinner?.succeed(`${productLabel(deploymentProduct(deployment))} deployment "${deployment.name}" created (slug: ${deployment.slug})`);
551
640
  }
552
641
  catch (err) {
553
- createSpinner.fail("Failed to create deployment");
642
+ createSpinner?.fail("Failed to create deployment");
554
643
  handleError(err);
555
644
  }
556
645
  deploymentId = deployment.id;
@@ -566,29 +655,85 @@ Examples:
566
655
  branch: answers.branch,
567
656
  };
568
657
  saveProjectConfig(cwd, projectCfg);
569
- console.log(chalk.dim(` Saved .miosa.json`));
658
+ if (!json)
659
+ console.log(chalk.dim(` Saved .miosa.json`));
570
660
  // ── Step 4b: Print webhook secret (one-time) ─────────────────
571
- console.log();
572
- console.log(chalk.bold.yellow(" ACTION REQUIRED — GitHub Webhook"));
573
- console.log(chalk.dim(" The webhook secret below is shown ONCE. Store it now."));
574
- console.log();
575
- console.log(` ${chalk.bold("Webhook URL:")} https://api.miosa.ai/api/v1/integrations/github/webhook`);
576
- console.log(` ${chalk.bold("Content type:")} application/json`);
577
- console.log(` ${chalk.bold("Secret:")} ${chalk.green(webhookSecret)}`);
578
- console.log(` ${chalk.bold("Events:")} push`);
579
- console.log();
580
- console.log(chalk.dim(" Add this at: " + repoUrl + "/settings/hooks/new"));
581
- console.log();
661
+ if (!json) {
662
+ console.log();
663
+ console.log(chalk.bold.yellow(" ACTION REQUIRED GitHub Webhook"));
664
+ console.log(chalk.dim(" The webhook secret below is shown ONCE. Store it now."));
665
+ console.log();
666
+ console.log(` ${chalk.bold("Webhook URL:")} https://api.miosa.ai/api/v1/integrations/github/webhook`);
667
+ console.log(` ${chalk.bold("Content type:")} application/json`);
668
+ console.log(` ${chalk.bold("Secret:")} ${chalk.green(webhookSecret)}`);
669
+ console.log(` ${chalk.bold("Events:")} push`);
670
+ console.log();
671
+ console.log(chalk.dim(" Add this at: " + repoUrl + "/settings/hooks/new"));
672
+ console.log();
673
+ }
582
674
  // ── Step 5: Trigger initial build ─────────────────────────────
583
- const buildSpinner = spin("Queuing initial build...");
675
+ const buildSpinner = json ? null : spin("Queuing initial build...");
584
676
  try {
585
- await client.redeployDeployment(deploymentId);
586
- buildSpinner.succeed("Initial build queued");
677
+ queuedBuild = await client.redeployDeployment(deploymentId);
678
+ buildSpinner?.succeed("Initial build queued");
587
679
  }
588
680
  catch (err) {
589
- buildSpinner.fail("Failed to queue build");
590
- handleError(err);
681
+ buildSpinner?.fail("Failed to queue build");
682
+ if (!json)
683
+ handleError(err);
684
+ buildQueueError = err;
685
+ }
686
+ }
687
+ if (json) {
688
+ let deployment = createdDeployment;
689
+ if (!deployment) {
690
+ try {
691
+ deployment = await client.getDeployment(deploymentId);
692
+ }
693
+ catch {
694
+ deployment = null;
695
+ }
591
696
  }
697
+ console.log(JSON.stringify({
698
+ ok: buildQueueError === null,
699
+ deployment: deployment
700
+ ? {
701
+ id: deployment.id,
702
+ name: deployment.name,
703
+ slug: deployment.slug,
704
+ state: deployment.state,
705
+ deployment_product: deploymentProduct(deployment),
706
+ docker_deploy_host_id: deployment.docker_deploy_host_id ?? null,
707
+ public_url: deploymentUrl(deployment),
708
+ }
709
+ : {
710
+ id: deploymentId,
711
+ name: projectCfg.name,
712
+ slug: null,
713
+ state: null,
714
+ deployment_product: null,
715
+ docker_deploy_host_id: null,
716
+ public_url: null,
717
+ },
718
+ build: queuedBuild
719
+ ? { id: queuedBuild.id, state: queuedBuild.state }
720
+ : null,
721
+ webhook: webhookSecret
722
+ ? {
723
+ url: "https://api.miosa.ai/api/v1/integrations/github/webhook",
724
+ secret: webhookSecret,
725
+ events: ["push"],
726
+ }
727
+ : null,
728
+ project_config: path.join(cwd, PROJECT_CONFIG_FILE),
729
+ ...(buildQueueError === null
730
+ ? {}
731
+ : { error: jsonErrorPayload(buildQueueError) }),
732
+ }, null, 2));
733
+ if (buildQueueError !== null) {
734
+ process.exit(errorExitCode(buildQueueError));
735
+ }
736
+ return;
592
737
  }
593
738
  // ── Step 6: Stream build logs ─────────────────────────────────────
594
739
  console.log();
@@ -816,11 +961,16 @@ Examples:
816
961
  }
817
962
  printBanner({ subtitle: "Deployment proof" });
818
963
  console.log(kvPanel([
819
- { label: "deployment_id", value: chalk.dim(result.deployment_id) },
964
+ {
965
+ label: "deployment_id",
966
+ value: chalk.dim(result.deployment_id),
967
+ },
820
968
  { label: "type", value: productLabel(result.deployment_product) },
821
969
  {
822
970
  label: "public_url",
823
- value: result.public_url ? chalk.cyan(result.public_url) : chalk.dim("missing"),
971
+ value: result.public_url
972
+ ? chalk.cyan(result.public_url)
973
+ : chalk.dim("missing"),
824
974
  },
825
975
  {
826
976
  label: "proof",