@videan/cli 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5,8 +5,8 @@ import { Command as Command18 } from "commander";
5
5
 
6
6
  // src/commands/login.ts
7
7
  import { Command } from "commander";
8
- import inquirer from "inquirer";
9
- import chalk from "chalk";
8
+ import inquirer2 from "inquirer";
9
+ import chalk2 from "chalk";
10
10
  import ora from "ora";
11
11
  import os from "node:os";
12
12
 
@@ -783,7 +783,7 @@ function visibilityOf(value) {
783
783
  }
784
784
  var VISIBILITY_LABELS = {
785
785
  private: "Only people with access",
786
- org: "Anyone in the organization",
786
+ org: "Anyone in your organization",
787
787
  authenticated: "Anyone signed in to Videan",
788
788
  public: "Anyone with the link"
789
789
  };
@@ -834,12 +834,94 @@ function clearConfig() {
834
834
  config.clear();
835
835
  }
836
836
 
837
+ // src/prompt.ts
838
+ import inquirer from "inquirer";
839
+ import chalk from "chalk";
840
+ async function prompt(questions, nonInteractiveHint) {
841
+ if (!process.stdin.isTTY) {
842
+ console.error(
843
+ chalk.red(`This command needs a terminal to prompt. ${nonInteractiveHint}`)
844
+ );
845
+ process.exit(1);
846
+ }
847
+ try {
848
+ return await inquirer.prompt(questions);
849
+ } catch (err) {
850
+ if (isStreamClosed(err)) {
851
+ console.error(chalk.red(`Input ended before the prompt was answered. ${nonInteractiveHint}`));
852
+ process.exit(1);
853
+ }
854
+ throw err;
855
+ }
856
+ }
857
+ function isStreamClosed(err) {
858
+ const e = err;
859
+ return e?.code === "ERR_USE_AFTER_CLOSE" || /closed/i.test(e?.message ?? "");
860
+ }
861
+ function installPromptCrashGuard() {
862
+ process.on("uncaughtException", (err) => {
863
+ if (isStreamClosed(err)) {
864
+ console.error(
865
+ chalk.red(
866
+ "Input stream closed mid-prompt. Re-run with the answers as flags \u2014 every prompt has one (see --help)."
867
+ )
868
+ );
869
+ process.exit(1);
870
+ }
871
+ throw err;
872
+ });
873
+ }
874
+
837
875
  // src/commands/login.ts
876
+ async function completeTwoFactor(spinner, baseUrl, login) {
877
+ if (!process.stdin.isTTY) {
878
+ spinner.fail(
879
+ "This account has two-factor enabled: login needs a terminal to type the emailed code. Non-interactively, set VIDEAN_API_KEY instead (create a key from a terminal or the dashboard)."
880
+ );
881
+ process.exit(1);
882
+ }
883
+ spinner.stop();
884
+ console.log(
885
+ login.code_email_sent === false ? chalk2.yellow("Two-factor is on, but the code email could not be sent. Trying anyway \u2014 check your inbox.") : "A 6-digit sign-in code was emailed to you (it expires in 10 minutes)."
886
+ );
887
+ for (let attempt = 0; attempt < 4; attempt++) {
888
+ const { code } = await prompt(
889
+ [
890
+ {
891
+ type: "input",
892
+ name: "code",
893
+ message: "Sign-in code:",
894
+ validate: (v) => /^\d{6}$/.test(v.trim()) ? true : "The code is 6 digits"
895
+ }
896
+ ],
897
+ "Two-factor login cannot be completed non-interactively; set VIDEAN_API_KEY instead."
898
+ );
899
+ let response;
900
+ try {
901
+ response = await fetch(`${baseUrl}/auth/login/2fa`, {
902
+ method: "POST",
903
+ headers: { "Content-Type": "application/json" },
904
+ body: JSON.stringify({ challenge: login.challenge, code: code.trim() })
905
+ });
906
+ } catch (e) {
907
+ spinner.fail(`Login failed: could not reach ${baseUrl} (${e?.message || e})`);
908
+ process.exit(1);
909
+ }
910
+ if (response.ok) {
911
+ const data = await response.json();
912
+ return data.data.token;
913
+ }
914
+ const body = await response.json().catch(() => null);
915
+ console.error(chalk2.red(body?.error?.message || "Invalid or expired sign-in code"));
916
+ }
917
+ spinner.fail("Too many wrong codes. Run `videan login` again to get a new one.");
918
+ process.exit(1);
919
+ }
838
920
  var loginCommand = new Command("login").description("Log in to Cloud").option("-e, --email <email>", "Email (skips the prompt)").option("-p, --password <password>", "Password (skips the prompt; prefer VIDEAN_PASSWORD)").option("-o, --org <id>", "Organization to scope the key to (skips the picker)").option("--api-url <url>", "API base URL to use and remember (default: the public API)").action(async (opts) => {
839
921
  const password = opts.password || process.env.VIDEAN_PASSWORD || process.env.CLOUD_PASSWORD;
840
922
  if ((!opts.email || !password) && !process.stdin.isTTY) {
841
923
  console.error(
842
- chalk.red(
924
+ chalk2.red(
843
925
  "login needs a terminal to prompt. Non-interactively, pass --email and --password (or VIDEAN_PASSWORD), or skip login entirely by setting VIDEAN_API_KEY."
844
926
  )
845
927
  );
@@ -852,12 +934,13 @@ var loginCommand = new Command("login").description("Log in to Cloud").option("-
852
934
  const answers = {
853
935
  email: opts.email,
854
936
  password,
855
- ...prompts.length ? await inquirer.prompt(prompts) : {}
937
+ ...prompts.length ? await inquirer2.prompt(prompts) : {}
856
938
  };
857
939
  if (opts.apiUrl) setBaseUrl(opts.apiUrl.replace(/\/$/, ""));
858
940
  const baseUrl = (getBaseUrl() || DEFAULT_API_URL2).replace(/\/$/, "");
859
941
  const spinner = ora("Logging in...").start();
860
942
  let token;
943
+ let login;
861
944
  try {
862
945
  const response = await fetch(`${baseUrl}/auth/login`, {
863
946
  method: "POST",
@@ -870,17 +953,22 @@ var loginCommand = new Command("login").description("Log in to Cloud").option("-
870
953
  } else {
871
954
  spinner.fail(`Login failed: ${response.status} from ${baseUrl}/auth/login`);
872
955
  console.error(
873
- chalk.gray(" Wrong API URL? Re-run with --api-url <url>, or `videan logout` to reset.")
956
+ chalk2.gray(" Wrong API URL? Re-run with --api-url <url>, or `videan logout` to reset.")
874
957
  );
875
958
  }
876
959
  process.exit(1);
877
960
  }
878
961
  const data = await response.json();
879
- token = data.data.token;
962
+ login = data.data;
880
963
  } catch (e) {
881
964
  spinner.fail(`Login failed: could not reach ${baseUrl} (${e?.message || e})`);
882
965
  process.exit(1);
883
966
  }
967
+ if (login.requires_2fa) {
968
+ token = await completeTwoFactor(spinner, baseUrl, login);
969
+ } else {
970
+ token = login.token;
971
+ }
884
972
  const session = new CloudClient({ apiKey: token, baseUrl });
885
973
  let orgId;
886
974
  try {
@@ -899,7 +987,7 @@ var loginCommand = new Command("login").description("Log in to Cloud").option("-
899
987
  process.exit(1);
900
988
  } else if (orgs.length > 1) {
901
989
  spinner.stop();
902
- const pick = await inquirer.prompt([
990
+ const pick = await inquirer2.prompt([
903
991
  {
904
992
  type: "list",
905
993
  name: "orgId",
@@ -919,7 +1007,7 @@ var loginCommand = new Command("login").description("Log in to Cloud").option("-
919
1007
  if (!orgId) {
920
1008
  setApiKey(token);
921
1009
  spinner.warn(
922
- chalk.yellow("Logged in, but you have no organization yet. Run `videan orgs create <name>`.")
1010
+ chalk2.yellow("Logged in, but you have no organization yet. Run `videan orgs create <name>`.")
923
1011
  );
924
1012
  return;
925
1013
  }
@@ -930,32 +1018,32 @@ var loginCommand = new Command("login").description("Log in to Cloud").option("-
930
1018
  });
931
1019
  setApiKey(key.key);
932
1020
  setDefaultOrg(orgId);
933
- spinner.succeed(chalk.green("Logged in."));
934
- console.log(chalk.gray(` Org: ${orgId}`));
935
- console.log(chalk.gray(` Key: ${key.keyPrefix}\u2026 (revoke with \`videan keys revoke ${key.id}\`)`));
1021
+ spinner.succeed(chalk2.green("Logged in."));
1022
+ console.log(chalk2.gray(` Org: ${orgId}`));
1023
+ console.log(chalk2.gray(` Key: ${key.keyPrefix}\u2026 (revoke with \`videan keys revoke ${key.id}\`)`));
936
1024
  } catch (e) {
937
1025
  setApiKey(token);
938
1026
  setDefaultOrg(orgId);
939
1027
  spinner.warn(
940
- chalk.yellow(`Logged in with a temporary session (key creation failed: ${e?.message}).`)
1028
+ chalk2.yellow(`Logged in with a temporary session (key creation failed: ${e?.message}).`)
941
1029
  );
942
1030
  }
943
1031
  });
944
1032
 
945
1033
  // src/commands/logout.ts
946
1034
  import { Command as Command2 } from "commander";
947
- import chalk2 from "chalk";
1035
+ import chalk3 from "chalk";
948
1036
  var logoutCommand = new Command2("logout").description("Forget the saved key, org, app and API URL").action(() => {
949
1037
  const where = configPath();
950
1038
  clearConfig();
951
- console.log(chalk2.green("Logged out."));
952
- console.log(chalk2.gray(` Cleared ${where}`));
1039
+ console.log(chalk3.green("Logged out."));
1040
+ console.log(chalk3.gray(` Cleared ${where}`));
953
1041
  });
954
1042
 
955
1043
  // src/commands/init.ts
956
1044
  import { Command as Command3 } from "commander";
957
- import inquirer2 from "inquirer";
958
- import chalk3 from "chalk";
1045
+ import inquirer3 from "inquirer";
1046
+ import chalk4 from "chalk";
959
1047
  import fs2 from "fs-extra";
960
1048
  import path2 from "node:path";
961
1049
 
@@ -1022,20 +1110,20 @@ var initCommand = new Command3("init").description("Initialize a new Cloud app i
1022
1110
  const interactive = process.stdin.isTTY && !opts.yes;
1023
1111
  if (await fs2.pathExists(configPath2)) {
1024
1112
  if (!interactive) {
1025
- console.log(chalk3.yellow(`${CONFIG_FILE_NAME} already exists; leaving it alone.`));
1113
+ console.log(chalk4.yellow(`${CONFIG_FILE_NAME} already exists; leaving it alone.`));
1026
1114
  } else {
1027
- console.log(chalk3.yellow(`${CONFIG_FILE_NAME} already exists in this directory.`));
1028
- const { overwrite } = await inquirer2.prompt([
1115
+ console.log(chalk4.yellow(`${CONFIG_FILE_NAME} already exists in this directory.`));
1116
+ const { overwrite } = await inquirer3.prompt([
1029
1117
  { type: "confirm", name: "overwrite", message: "Overwrite?", default: false }
1030
1118
  ]);
1031
1119
  if (!overwrite) {
1032
- for (const f of await writeVideanMd(process.cwd())) console.log(chalk3.green(`Wrote ${f}`));
1120
+ for (const f of await writeVideanMd(process.cwd())) console.log(chalk4.green(`Wrote ${f}`));
1033
1121
  return;
1034
1122
  }
1035
1123
  }
1036
1124
  }
1037
1125
  if (!await fs2.pathExists(configPath2) || interactive) {
1038
- const answers = interactive ? await inquirer2.prompt([
1126
+ const answers = interactive ? await inquirer3.prompt([
1039
1127
  { type: "input", name: "name", message: "App name:", default: opts.name || path2.basename(process.cwd()) },
1040
1128
  {
1041
1129
  type: "list",
@@ -1056,21 +1144,21 @@ var initCommand = new Command3("init").description("Initialize a new Cloud app i
1056
1144
  ""
1057
1145
  ].join("\n");
1058
1146
  await fs2.writeFile(configPath2, config2, "utf-8");
1059
- console.log(chalk3.green(`Created ${CONFIG_FILE_NAME}`));
1147
+ console.log(chalk4.green(`Created ${CONFIG_FILE_NAME}`));
1060
1148
  }
1061
- for (const f of await writeVideanMd(process.cwd())) console.log(chalk3.green(`Wrote ${f}`));
1149
+ for (const f of await writeVideanMd(process.cwd())) console.log(chalk4.green(`Wrote ${f}`));
1062
1150
  });
1063
1151
 
1064
1152
  // src/commands/deploy.ts
1065
1153
  import { Command as Command4 } from "commander";
1066
- import chalk5 from "chalk";
1154
+ import chalk6 from "chalk";
1067
1155
  import ora2 from "ora";
1068
1156
 
1069
1157
  // src/util.ts
1070
1158
  import fs3 from "fs-extra";
1071
1159
  import path3 from "node:path";
1072
1160
  import ignore from "ignore";
1073
- import chalk4 from "chalk";
1161
+ import chalk5 from "chalk";
1074
1162
  var MAX_FILE_BYTES = 10 * 1024 * 1024;
1075
1163
  async function collectFiles(dir) {
1076
1164
  const ig = ignore();
@@ -1131,15 +1219,15 @@ async function collectFiles(dir) {
1131
1219
  function formatStatus(status) {
1132
1220
  switch (status) {
1133
1221
  case "running":
1134
- return chalk4.green(status);
1222
+ return chalk5.green(status);
1135
1223
  case "building":
1136
1224
  case "deploying":
1137
- return chalk4.yellow(status);
1225
+ return chalk5.yellow(status);
1138
1226
  case "failed":
1139
1227
  case "crashed":
1140
- return chalk4.red(status);
1228
+ return chalk5.red(status);
1141
1229
  case "stopped":
1142
- return chalk4.gray(status);
1230
+ return chalk5.gray(status);
1143
1231
  default:
1144
1232
  return status;
1145
1233
  }
@@ -1149,13 +1237,13 @@ function formatRow(columns, widths) {
1149
1237
  }
1150
1238
  function requireApiKey(apiKey) {
1151
1239
  if (!apiKey) {
1152
- console.error(chalk4.red("Not logged in. Run `videan login` first."));
1240
+ console.error(chalk5.red("Not logged in. Run `videan login` first."));
1153
1241
  process.exit(1);
1154
1242
  }
1155
1243
  }
1156
1244
  function requireOrg(orgId) {
1157
1245
  if (!orgId) {
1158
- console.error(chalk4.red("No organization. Pass --org <id> or run `videan login` again."));
1246
+ console.error(chalk5.red("No organization. Pass --org <id> or run `videan login` again."));
1159
1247
  process.exit(1);
1160
1248
  }
1161
1249
  return orgId;
@@ -1167,7 +1255,7 @@ var deployCommand = new Command4("deploy").description("Deploy the current direc
1167
1255
  requireApiKey(apiKey);
1168
1256
  const appId = opts.app || getCurrentApp();
1169
1257
  if (!appId) {
1170
- console.error(chalk5.red("No app specified. Use --app or run `videan init` first."));
1258
+ console.error(chalk6.red("No app specified. Use --app or run `videan init` first."));
1171
1259
  process.exit(1);
1172
1260
  }
1173
1261
  const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
@@ -1176,19 +1264,19 @@ var deployCommand = new Command4("deploy").description("Deploy the current direc
1176
1264
  const binaryCount = Object.keys(binaryFiles).length;
1177
1265
  const fileCount = Object.keys(files).length + binaryCount;
1178
1266
  if (!fileCount) {
1179
- spinner.fail(chalk5.red(`Nothing to deploy: no files under ${process.cwd()}.`));
1267
+ spinner.fail(chalk6.red(`Nothing to deploy: no files under ${process.cwd()}.`));
1180
1268
  process.exit(1);
1181
1269
  }
1182
1270
  if (skipped.length) {
1183
- spinner.warn(chalk5.yellow(`Skipped ${skipped.length} file(s):`));
1184
- for (const file of skipped) console.log(chalk5.gray(` ${file.path} \u2014 ${file.reason}`));
1271
+ spinner.warn(chalk6.yellow(`Skipped ${skipped.length} file(s):`));
1272
+ for (const file of skipped) console.log(chalk6.gray(` ${file.path} \u2014 ${file.reason}`));
1185
1273
  spinner.start();
1186
1274
  }
1187
1275
  spinner.text = `Pushing ${fileCount} files${binaryCount ? ` (${binaryCount} binary)` : ""}...`;
1188
1276
  const snapshot = await client4.apps.push(appId, files, opts.message, binaryFiles);
1189
1277
  spinner.text = "Triggering deployment...";
1190
1278
  if (opts.env && opts.env !== "preview" && opts.env !== "production") {
1191
- spinner.fail(chalk5.red(`--env must be preview or production, not ${opts.env}`));
1279
+ spinner.fail(chalk6.red(`--env must be preview or production, not ${opts.env}`));
1192
1280
  process.exit(1);
1193
1281
  }
1194
1282
  const deployment = await client4.apps.deploy(appId, snapshot.id, opts.env);
@@ -1206,34 +1294,34 @@ var deployCommand = new Command4("deploy").description("Deploy the current direc
1206
1294
  } catch (e) {
1207
1295
  lastError = e?.message || String(e);
1208
1296
  if (++consecutiveErrors >= 5) {
1209
- spinner.fail(chalk5.red(`Lost track of the deployment: ${lastError}`));
1210
- console.log(chalk5.gray(` It may still finish. Check with \`videan status\`.`));
1297
+ spinner.fail(chalk6.red(`Lost track of the deployment: ${lastError}`));
1298
+ console.log(chalk6.gray(` It may still finish. Check with \`videan status\`.`));
1211
1299
  process.exit(1);
1212
1300
  }
1213
1301
  }
1214
1302
  }
1215
1303
  if (current.status === "running") {
1216
- spinner.succeed(chalk5.green(`Deployed successfully!`));
1304
+ spinner.succeed(chalk6.green(`Deployed successfully!`));
1217
1305
  try {
1218
1306
  const app = await client4.apps.get(appId);
1219
1307
  const gated = app.visibility ? app.visibility !== "public" : app.authRequired !== false;
1220
1308
  console.log(
1221
- gated ? chalk5.gray(
1309
+ gated ? chalk6.gray(
1222
1310
  ` Access: ${app.visibility || "org"} \u2014 visitors sign in once at the edge; your app receives X-User-Id / X-User-Email on every request. Don't build a login page.`
1223
- ) : chalk5.gray(` Access: public \u2014 reachable by anyone; bring your own auth if it needs any.`)
1311
+ ) : chalk6.gray(` Access: public \u2014 reachable by anyone; bring your own auth if it needs any.`)
1224
1312
  );
1225
1313
  } catch {
1226
1314
  }
1227
1315
  if (current.url) {
1228
1316
  const url = /^https?:\/\//.test(current.url) ? current.url : `https://${current.url}`;
1229
- console.log(chalk5.cyan(` URL: ${url}`));
1317
+ console.log(chalk6.cyan(` URL: ${url}`));
1230
1318
  }
1231
1319
  if (current.previewUrl) {
1232
- console.log(chalk5.gray(` This version: ${current.previewUrl}`));
1320
+ console.log(chalk6.gray(` This version: ${current.previewUrl}`));
1233
1321
  }
1234
1322
  } else if (current.status === "failed" || current.status === "crashed") {
1235
- spinner.fail(chalk5.red(`Deployment ${current.status}: ${current.error || "unknown error"}`));
1236
- console.log(chalk5.gray(` Logs: videan logs --app ${appId}`));
1323
+ spinner.fail(chalk6.red(`Deployment ${current.status}: ${current.error || "unknown error"}`));
1324
+ console.log(chalk6.gray(` Logs: videan logs --app ${appId}`));
1237
1325
  process.exit(1);
1238
1326
  } else {
1239
1327
  spinner.info(`Deployment status: ${formatStatus(current.status)}`);
@@ -1243,7 +1331,7 @@ var deployCommand = new Command4("deploy").description("Deploy the current direc
1243
1331
 
1244
1332
  // src/commands/pull.ts
1245
1333
  import { Command as Command5 } from "commander";
1246
- import chalk6 from "chalk";
1334
+ import chalk7 from "chalk";
1247
1335
  import ora3 from "ora";
1248
1336
  import fs4 from "fs-extra";
1249
1337
  import path4 from "node:path";
@@ -1267,14 +1355,14 @@ var pullCommand = new Command5("pull").description("Download a snapshot's files
1267
1355
  if (!snapshotId) {
1268
1356
  const appId = opts.app || getCurrentApp();
1269
1357
  if (!appId) {
1270
- spinner.fail(chalk6.red("No app specified. Use --app, VIDEAN_APP_ID, or name a snapshot id."));
1358
+ spinner.fail(chalk7.red("No app specified. Use --app, VIDEAN_APP_ID, or name a snapshot id."));
1271
1359
  process.exit(1);
1272
1360
  }
1273
1361
  const [latest] = await get(
1274
1362
  `/v1/apps/${appId}/snapshots?limit=1`
1275
1363
  );
1276
1364
  if (!latest) {
1277
- spinner.fail(chalk6.red("This app has no snapshots yet."));
1365
+ spinner.fail(chalk7.red("This app has no snapshots yet."));
1278
1366
  process.exit(1);
1279
1367
  }
1280
1368
  snapshotId = latest.id;
@@ -1286,7 +1374,7 @@ var pullCommand = new Command5("pull").description("Download a snapshot's files
1286
1374
  await fs4.ensureDir(outDir);
1287
1375
  const existing = await fs4.readdir(outDir);
1288
1376
  if (existing.length > 0 && !opts.force) {
1289
- spinner.fail(chalk6.red(`${outDir} is not empty. Pass --force to write into it anyway.`));
1377
+ spinner.fail(chalk7.red(`${outDir} is not empty. Pass --force to write into it anyway.`));
1290
1378
  process.exit(1);
1291
1379
  }
1292
1380
  spinner.text = `Pulling ${detail.manifest.length} files...`;
@@ -1306,29 +1394,29 @@ var pullCommand = new Command5("pull").description("Download a snapshot's files
1306
1394
  spinner.text = `Pulling ${written}/${detail.manifest.length}...`;
1307
1395
  }
1308
1396
  spinner.succeed(
1309
- chalk6.green(
1397
+ chalk7.green(
1310
1398
  `Pulled snapshot ${detail.seq ? `#${detail.seq}` : snapshotId} \u2014 ${written} files into ${outDir}`
1311
1399
  )
1312
1400
  );
1313
1401
  } catch (err) {
1314
- spinner.fail(chalk6.red(err instanceof Error ? err.message : String(err)));
1402
+ spinner.fail(chalk7.red(err instanceof Error ? err.message : String(err)));
1315
1403
  process.exit(1);
1316
1404
  }
1317
1405
  });
1318
1406
 
1319
1407
  // src/commands/logs.ts
1320
1408
  import { Command as Command6 } from "commander";
1321
- import chalk7 from "chalk";
1409
+ import chalk8 from "chalk";
1322
1410
  var logsCommand = new Command6("logs").description("View app logs").option("-a, --app <id>", "App ID").option("-f, --follow", "Follow log output").option("-n, --lines <n>", "Number of lines to show", "50").option("-s, --since <when>", "Start of the window: ISO timestamp or an offset like 15m, 2h, 7d", "1h").option("--until <when>", "End of the window (same formats as --since)").option("-q, --search <text>", "Only lines containing this text").option("--pattern <pattern>", "CloudWatch filter pattern, for the real syntax").option("--instance <prefix>", "Only one instance, by log stream prefix").action(async (opts) => {
1323
1411
  const apiKey = getApiKey();
1324
1412
  requireApiKey(apiKey);
1325
1413
  const appId = opts.app || getCurrentApp();
1326
1414
  if (!appId) {
1327
- console.error(chalk7.red("No app specified. Use --app or link an app."));
1415
+ console.error(chalk8.red("No app specified. Use --app or link an app."));
1328
1416
  process.exit(1);
1329
1417
  }
1330
1418
  if (opts.search && opts.pattern) {
1331
- console.error(chalk7.red("Use either --search or --pattern, not both."));
1419
+ console.error(chalk8.red("Use either --search or --pattern, not both."));
1332
1420
  process.exit(1);
1333
1421
  }
1334
1422
  const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
@@ -1351,43 +1439,43 @@ var logsCommand = new Command6("logs").description("View app logs").option("-a,
1351
1439
  });
1352
1440
  for (const entry of page.events) print(entry);
1353
1441
  if (page.events.length === 0) {
1354
- console.log(chalk7.gray(`No log lines since ${page.start}.`));
1442
+ console.log(chalk8.gray(`No log lines since ${page.start}.`));
1355
1443
  }
1356
1444
  if (page.truncated) {
1357
1445
  console.log(
1358
- chalk7.yellow(
1446
+ chalk8.yellow(
1359
1447
  "\nThis is part of the window -- there are more lines than one request reads. Narrow --since or add --search."
1360
1448
  )
1361
1449
  );
1362
1450
  }
1363
1451
  });
1364
1452
  function print(entry) {
1365
- console.log(`${chalk7.gray(entry.timestamp)} ${formatLevel(entry.level)} ${entry.message}`);
1453
+ console.log(`${chalk8.gray(entry.timestamp)} ${formatLevel(entry.level)} ${entry.message}`);
1366
1454
  }
1367
1455
  function formatLevel(level) {
1368
1456
  switch (level) {
1369
1457
  case "error":
1370
- return chalk7.red("[ERR]");
1458
+ return chalk8.red("[ERR]");
1371
1459
  case "warn":
1372
- return chalk7.yellow("[WRN]");
1460
+ return chalk8.yellow("[WRN]");
1373
1461
  case "info":
1374
- return chalk7.blue("[INF]");
1462
+ return chalk8.blue("[INF]");
1375
1463
  case "debug":
1376
- return chalk7.gray("[DBG]");
1464
+ return chalk8.gray("[DBG]");
1377
1465
  default:
1378
- return chalk7.gray("[LOG]");
1466
+ return chalk8.gray("[LOG]");
1379
1467
  }
1380
1468
  }
1381
1469
 
1382
1470
  // src/commands/metrics.ts
1383
1471
  import { Command as Command7 } from "commander";
1384
- import chalk8 from "chalk";
1472
+ import chalk9 from "chalk";
1385
1473
  var metricsCommand = new Command7("metrics").description("Traffic, latency and resource use for an app").option("-a, --app <id>", "App ID").option("-s, --since <when>", "Start of the window: ISO timestamp or an offset like 15m, 2h, 7d", "1h").option("--until <when>", "End of the window (same formats as --since)").option("--series", "Print each bucket instead of just the summary").option("--json", "Print the raw response").action(async (opts) => {
1386
1474
  const apiKey = getApiKey();
1387
1475
  requireApiKey(apiKey);
1388
1476
  const appId = opts.app || getCurrentApp();
1389
1477
  if (!appId) {
1390
- console.error(chalk8.red("No app specified. Use --app or link an app."));
1478
+ console.error(chalk9.red("No app specified. Use --app or link an app."));
1391
1479
  process.exit(1);
1392
1480
  }
1393
1481
  const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
@@ -1398,7 +1486,7 @@ var metricsCommand = new Command7("metrics").description("Traffic, latency and r
1398
1486
  }
1399
1487
  const t = metrics.totals;
1400
1488
  console.log(
1401
- chalk8.dim(`${metrics.start} \u2192 ${metrics.end} (${metrics.period}s buckets)`)
1489
+ chalk9.dim(`${metrics.start} \u2192 ${metrics.end} (${metrics.period}s buckets)`)
1402
1490
  );
1403
1491
  console.log();
1404
1492
  console.log(` ${label("Requests")} ${t.requests}`);
@@ -1406,61 +1494,61 @@ var metricsCommand = new Command7("metrics").description("Traffic, latency and r
1406
1494
  console.log(` ${label("5xx")} ${count(t.serverErrors, true)}`);
1407
1495
  console.log(` ${label("Gate denied")} ${count(t.gateDenied)}`);
1408
1496
  console.log(` ${label("Latency avg")} ${ms(t.latencyAvgMs)}`);
1409
- console.log(` ${label("Latency p95")} ${ms(t.latencyP95Ms)} ${chalk8.dim("(sampled)")}`);
1497
+ console.log(` ${label("Latency p95")} ${ms(t.latencyP95Ms)} ${chalk9.dim("(sampled)")}`);
1410
1498
  console.log(` ${label("CPU avg")} ${units(t.cpuAvg)}`);
1411
1499
  console.log(` ${label("Memory avg")} ${mb(t.memoryAvgMb)}`);
1412
1500
  if (t.requests === 0) {
1413
1501
  console.log();
1414
- console.log(chalk8.dim("No requests recorded in this window."));
1502
+ console.log(chalk9.dim("No requests recorded in this window."));
1415
1503
  }
1416
1504
  if (opts.series) {
1417
1505
  console.log();
1418
1506
  for (const [name, points] of Object.entries(metrics.series)) {
1419
1507
  if (points.length === 0) continue;
1420
- console.log(chalk8.bold(name));
1421
- for (const p of points) console.log(` ${chalk8.dim(p.t)} ${p.v}`);
1508
+ console.log(chalk9.bold(name));
1509
+ for (const p of points) console.log(` ${chalk9.dim(p.t)} ${p.v}`);
1422
1510
  }
1423
1511
  }
1424
1512
  });
1425
1513
  function label(text) {
1426
- return chalk8.dim(text.padEnd(13));
1514
+ return chalk9.dim(text.padEnd(13));
1427
1515
  }
1428
1516
  function count(n, severe = false) {
1429
- if (n === 0) return chalk8.dim("0");
1430
- return severe ? chalk8.red(String(n)) : chalk8.yellow(String(n));
1517
+ if (n === 0) return chalk9.dim("0");
1518
+ return severe ? chalk9.red(String(n)) : chalk9.yellow(String(n));
1431
1519
  }
1432
1520
  function ms(v) {
1433
- return v === null ? chalk8.dim("\u2014") : `${v} ms`;
1521
+ return v === null ? chalk9.dim("\u2014") : `${v} ms`;
1434
1522
  }
1435
1523
  function mb(v) {
1436
- return v === null ? chalk8.dim("\u2014") : `${v} MB`;
1524
+ return v === null ? chalk9.dim("\u2014") : `${v} MB`;
1437
1525
  }
1438
1526
  function units(v) {
1439
- return v === null ? chalk8.dim("\u2014") : `${v} CPU units`;
1527
+ return v === null ? chalk9.dim("\u2014") : `${v} CPU units`;
1440
1528
  }
1441
1529
 
1442
1530
  // src/commands/rollback.ts
1443
1531
  import { Command as Command8 } from "commander";
1444
- import chalk9 from "chalk";
1532
+ import chalk10 from "chalk";
1445
1533
  import ora4 from "ora";
1446
1534
  var rollbackCommand = new Command8("rollback").description("Rollback to the previous production deployment").option("-a, --app <id>", "App ID").action(async (opts) => {
1447
1535
  const apiKey = getApiKey();
1448
1536
  requireApiKey(apiKey);
1449
1537
  const appId = opts.app || getCurrentApp();
1450
1538
  if (!appId) {
1451
- console.error(chalk9.red("No app specified. Use --app or link an app."));
1539
+ console.error(chalk10.red("No app specified. Use --app or link an app."));
1452
1540
  process.exit(1);
1453
1541
  }
1454
1542
  const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
1455
1543
  const spinner = ora4("Rolling back...").start();
1456
1544
  try {
1457
1545
  const deployment = await client4.apps.rollback(appId);
1458
- spinner.succeed(chalk9.green("Rollback successful!"));
1546
+ spinner.succeed(chalk10.green("Rollback successful!"));
1459
1547
  if (deployment.url) {
1460
- console.log(chalk9.cyan(` URL: ${deployment.url}`));
1548
+ console.log(chalk10.cyan(` URL: ${deployment.url}`));
1461
1549
  }
1462
1550
  } catch (error) {
1463
- spinner.fail(chalk9.red(`Rollback failed: ${error.message}`));
1551
+ spinner.fail(chalk10.red(`Rollback failed: ${error.message}`));
1464
1552
  process.exit(1);
1465
1553
  }
1466
1554
  });
@@ -1468,46 +1556,6 @@ var rollbackCommand = new Command8("rollback").description("Rollback to the prev
1468
1556
  // src/commands/secrets.ts
1469
1557
  import { Command as Command9 } from "commander";
1470
1558
  import chalk11 from "chalk";
1471
-
1472
- // src/prompt.ts
1473
- import inquirer3 from "inquirer";
1474
- import chalk10 from "chalk";
1475
- async function prompt(questions, nonInteractiveHint) {
1476
- if (!process.stdin.isTTY) {
1477
- console.error(
1478
- chalk10.red(`This command needs a terminal to prompt. ${nonInteractiveHint}`)
1479
- );
1480
- process.exit(1);
1481
- }
1482
- try {
1483
- return await inquirer3.prompt(questions);
1484
- } catch (err) {
1485
- if (isStreamClosed(err)) {
1486
- console.error(chalk10.red(`Input ended before the prompt was answered. ${nonInteractiveHint}`));
1487
- process.exit(1);
1488
- }
1489
- throw err;
1490
- }
1491
- }
1492
- function isStreamClosed(err) {
1493
- const e = err;
1494
- return e?.code === "ERR_USE_AFTER_CLOSE" || /closed/i.test(e?.message ?? "");
1495
- }
1496
- function installPromptCrashGuard() {
1497
- process.on("uncaughtException", (err) => {
1498
- if (isStreamClosed(err)) {
1499
- console.error(
1500
- chalk10.red(
1501
- "Input stream closed mid-prompt. Re-run with the answers as flags \u2014 every prompt has one (see --help)."
1502
- )
1503
- );
1504
- process.exit(1);
1505
- }
1506
- throw err;
1507
- });
1508
- }
1509
-
1510
- // src/commands/secrets.ts
1511
1559
  var secretsCommand = new Command9("secrets").description("Manage app secrets");
1512
1560
  secretsCommand.command("set <key> [value]").description("Set a secret").option("-a, --app <id>", "App ID").action(async (key, value, opts) => {
1513
1561
  const apiKey = getApiKey();
@@ -2340,7 +2388,7 @@ function period(start, end) {
2340
2388
  // src/index.ts
2341
2389
  installPromptCrashGuard();
2342
2390
  var program = new Command18();
2343
- program.name("videan").description("Videan CLI \u2014 deploy apps in seconds").version("0.4.1");
2391
+ program.name("videan").description("Videan CLI \u2014 deploy apps in seconds").version("0.4.3");
2344
2392
  program.addCommand(loginCommand);
2345
2393
  program.addCommand(logoutCommand);
2346
2394
  program.addCommand(initCommand);