@supercheck/cli 0.1.0-beta.3 → 0.1.0-beta.4

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.
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ confirmPrompt
4
+ } from "../chunk-W54DX2I7.js";
2
5
 
3
6
  // src/bin/supercheck.ts
4
7
  import { Command as Command18 } from "commander";
@@ -210,7 +213,7 @@ function requireTriggerKey() {
210
213
  }
211
214
 
212
215
  // src/version.ts
213
- var CLI_VERSION = true ? "0.1.0-beta.3" : "0.0.0-dev";
216
+ var CLI_VERSION = true ? "0.1.0-beta.4" : "0.0.0-dev";
214
217
 
215
218
  // src/api/client.ts
216
219
  import { ProxyAgent } from "undici";
@@ -407,7 +410,7 @@ var ApiClient = class {
407
410
  return this.request("DELETE", path);
408
411
  }
409
412
  sleep(ms) {
410
- return new Promise((resolve5) => setTimeout(resolve5, ms));
413
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
411
414
  }
412
415
  };
413
416
  var defaultClient = null;
@@ -760,6 +763,203 @@ function safeTokenPreview(token) {
760
763
  return `${token.substring(0, 12)}...${token.substring(token.length - 4)}`;
761
764
  }
762
765
 
766
+ // src/utils/spinner.ts
767
+ import ora from "ora";
768
+
769
+ // src/output/formatter.ts
770
+ import Table from "cli-table3";
771
+ import pc2 from "picocolors";
772
+ var currentFormat = "table";
773
+ function setOutputFormat(format) {
774
+ currentFormat = format;
775
+ }
776
+ function getOutputFormat() {
777
+ return currentFormat;
778
+ }
779
+ function output(data, options) {
780
+ switch (currentFormat) {
781
+ case "json":
782
+ logger.output(JSON.stringify(data, null, 2));
783
+ break;
784
+ case "quiet":
785
+ if (Array.isArray(data)) {
786
+ for (const item of data) {
787
+ if ("id" in item) logger.output(String(item.id));
788
+ }
789
+ } else if ("id" in data) {
790
+ logger.output(String(data.id));
791
+ }
792
+ break;
793
+ case "table":
794
+ default:
795
+ outputTable(data, options?.columns);
796
+ break;
797
+ }
798
+ }
799
+ function outputTable(data, columns) {
800
+ if (data === void 0 || data === null) {
801
+ logger.info("No results found.");
802
+ return;
803
+ }
804
+ const rawItems = Array.isArray(data) ? data : [data];
805
+ const items = rawItems.filter((item) => item !== void 0 && item !== null);
806
+ if (items.length === 0) {
807
+ logger.info("No results found.");
808
+ return;
809
+ }
810
+ const cols = columns ?? Object.keys(items[0]).map((key) => ({
811
+ key,
812
+ header: key.charAt(0).toUpperCase() + key.slice(1)
813
+ }));
814
+ const table = new Table({
815
+ head: cols.map((c) => c.header),
816
+ style: {
817
+ head: ["cyan"],
818
+ border: ["gray"]
819
+ }
820
+ });
821
+ for (const item of items) {
822
+ table.push(cols.map((c) => formatValue(item?.[c.key], c, item)));
823
+ }
824
+ logger.output(table.toString());
825
+ }
826
+ var STATUS_COLORS = {
827
+ // Success states (green)
828
+ up: "success",
829
+ ok: "success",
830
+ success: "success",
831
+ passed: "success",
832
+ active: "success",
833
+ sent: "success",
834
+ enabled: "success",
835
+ healthy: "success",
836
+ running: "success",
837
+ completed: "success",
838
+ // Error states (red)
839
+ down: "error",
840
+ failed: "error",
841
+ error: "error",
842
+ blocked: "error",
843
+ unhealthy: "error",
844
+ cancelled: "error",
845
+ canceled: "error",
846
+ // Warning states (yellow)
847
+ paused: "warning",
848
+ pending: "warning",
849
+ disabled: "warning",
850
+ degraded: "warning",
851
+ unknown: "warning",
852
+ queued: "warning",
853
+ warning: "warning",
854
+ skipped: "warning",
855
+ inactive: "warning"
856
+ };
857
+ function formatStatus(status) {
858
+ const normalizedStatus = status.toLowerCase();
859
+ const colorType = STATUS_COLORS[normalizedStatus];
860
+ if (!colorType) {
861
+ return status;
862
+ }
863
+ switch (colorType) {
864
+ case "success":
865
+ return pc2.green(`\u25CF ${status}`);
866
+ case "error":
867
+ return pc2.red(`\u25CF ${status}`);
868
+ case "warning":
869
+ return pc2.yellow(`\u25CF ${status}`);
870
+ default:
871
+ return status;
872
+ }
873
+ }
874
+ function formatValue(value, column, row) {
875
+ if (column?.format && row) {
876
+ return column.format(value, row);
877
+ }
878
+ if (value === null || value === void 0) return pc2.dim("-");
879
+ if (typeof value === "boolean") {
880
+ return value ? pc2.green("\u2713") : pc2.red("\u2717");
881
+ }
882
+ if (value instanceof Date) return formatDate(value);
883
+ if (Array.isArray(value)) return value.join(", ");
884
+ if (typeof value === "number") {
885
+ const dateForNumber = maybeFormatDate(value, column?.key);
886
+ if (dateForNumber) return dateForNumber;
887
+ return String(value);
888
+ }
889
+ if (typeof value === "object") return JSON.stringify(value);
890
+ const strValue = String(value);
891
+ const dateForString = maybeFormatDate(strValue, column?.key);
892
+ if (dateForString) return dateForString;
893
+ if (STATUS_COLORS[strValue.toLowerCase()]) {
894
+ return formatStatus(strValue);
895
+ }
896
+ return strValue;
897
+ }
898
+ var DATE_KEY_PATTERN = /(At|Date|Time|Timestamp)$/i;
899
+ function maybeFormatDate(value, key) {
900
+ if (!key && typeof value !== "string") return null;
901
+ if (key && !DATE_KEY_PATTERN.test(key) && typeof value !== "string") return null;
902
+ const date = new Date(value);
903
+ if (Number.isNaN(date.getTime())) return null;
904
+ if (typeof value === "string") {
905
+ const isIsoLike = value.includes("T") || value.endsWith("Z") || /[+-]\d{2}:?\d{2}$/.test(value);
906
+ if (!isIsoLike) return null;
907
+ return formatDate(date);
908
+ }
909
+ return formatDate(date);
910
+ }
911
+ function formatDate(date) {
912
+ return date.toISOString().replace("T", " ").replace("Z", "");
913
+ }
914
+ function outputDetail(data) {
915
+ if (currentFormat === "json") {
916
+ logger.output(JSON.stringify(data, null, 2));
917
+ return;
918
+ }
919
+ if (currentFormat === "quiet") {
920
+ if ("id" in data) logger.output(String(data.id));
921
+ return;
922
+ }
923
+ const keys = Object.keys(data);
924
+ if (keys.length === 0) {
925
+ logger.info("No details available.");
926
+ return;
927
+ }
928
+ const maxKeyLen = Math.max(...keys.map((k) => k.length));
929
+ for (const [key, value] of Object.entries(data)) {
930
+ const label = key.padEnd(maxKeyLen + 2);
931
+ logger.output(` ${label}${formatValue(value, { key, header: key }, data)}`);
932
+ }
933
+ }
934
+
935
+ // src/utils/spinner.ts
936
+ function createSpinner(text) {
937
+ const format = getOutputFormat();
938
+ const isInteractive = format === "table";
939
+ const spinner = ora({
940
+ text,
941
+ // Disable spinner in non-interactive modes
942
+ isSilent: !isInteractive,
943
+ // Use dots style for a clean look
944
+ spinner: "dots"
945
+ });
946
+ return spinner;
947
+ }
948
+ async function withSpinner(text, fn, options) {
949
+ const spinner = createSpinner(text);
950
+ spinner.start();
951
+ try {
952
+ const result = await fn();
953
+ const successMessage = typeof options?.successText === "function" ? options.successText(result) : options?.successText ?? text.replace(/\.\.\.?$/, "");
954
+ spinner.succeed(successMessage);
955
+ return result;
956
+ } catch (error) {
957
+ const failMessage = options?.failText ?? text.replace(/\.\.\.?$/, " failed");
958
+ spinner.fail(failMessage);
959
+ throw error;
960
+ }
961
+ }
962
+
763
963
  // src/commands/login.ts
764
964
  var loginCommand = new Command("login").description("Authenticate with Supercheck").option("--token <token>", "Provide a CLI token directly (for CI/CD)").option("--url <url>", "Supercheck API URL (for self-hosted instances)").action(async (options) => {
765
965
  if (options.token) {
@@ -775,7 +975,11 @@ var loginCommand = new Command("login").description("Authenticate with Superchec
775
975
  token
776
976
  });
777
977
  try {
778
- await client.get("/api/cli-tokens");
978
+ await withSpinner(
979
+ "Verifying token...",
980
+ () => client.get("/api/cli-tokens"),
981
+ { successText: "Token verified" }
982
+ );
779
983
  } catch {
780
984
  throw new CLIError(
781
985
  "Token verification failed. Please check your token and try again.",
@@ -821,7 +1025,14 @@ var whoamiCommand = new Command("whoami").description("Show current authenticati
821
1025
  const baseUrl = getStoredBaseUrl();
822
1026
  const client = getApiClient({ token, baseUrl: baseUrl ?? void 0 });
823
1027
  try {
824
- const { data } = await client.get("/api/cli-tokens");
1028
+ const data = await withSpinner(
1029
+ "Fetching context...",
1030
+ async () => {
1031
+ const { data: data2 } = await client.get("/api/cli-tokens");
1032
+ return data2;
1033
+ },
1034
+ { successText: "Context loaded" }
1035
+ );
825
1036
  const tokenPreview = safeTokenPreview(token);
826
1037
  const activeToken = data.tokens?.find((t) => t.start && token.startsWith(t.start.replace(/\.+$/, "")));
827
1038
  logger.newline();
@@ -1069,214 +1280,48 @@ function validateNoSecrets(config) {
1069
1280
  "Config file contains what appears to be an API token. Tokens must be stored in environment variables (SUPERCHECK_TOKEN) or the OS keychain, never in config files.",
1070
1281
  3 /* ConfigError */
1071
1282
  );
1072
- }
1073
- }
1074
- }
1075
- async function loadConfig(options = {}) {
1076
- const cwd = options.cwd ?? process.cwd();
1077
- const configPath = resolveConfigPath(cwd, options.configPath);
1078
- if (!configPath) {
1079
- throw new CLIError(
1080
- "No supercheck.config.ts found. Run `supercheck init` to create one.",
1081
- 3 /* ConfigError */
1082
- );
1083
- }
1084
- let config = await loadConfigFile(configPath);
1085
- const localConfigPath = resolveLocalConfigPath(cwd);
1086
- if (localConfigPath) {
1087
- const localConfig = await loadConfigFile(localConfigPath);
1088
- config = deepmerge(config, localConfig);
1089
- }
1090
- config = applyEnvOverrides(config);
1091
- validateNoSecrets(config);
1092
- const parsed = supercheckConfigSchema.safeParse(config);
1093
- if (!parsed.success) {
1094
- const issues = parsed.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
1095
- throw new CLIError(
1096
- `Invalid configuration:
1097
- ${issues}`,
1098
- 3 /* ConfigError */
1099
- );
1100
- }
1101
- return {
1102
- config: parsed.data,
1103
- configPath
1104
- };
1105
- }
1106
- async function tryLoadConfig(options = {}) {
1107
- try {
1108
- return await loadConfig(options);
1109
- } catch (err) {
1110
- if (err instanceof CLIError && err.message.includes("No supercheck.config.ts found")) {
1111
- return null;
1112
- }
1113
- throw err;
1114
- }
1115
- }
1116
-
1117
- // src/output/formatter.ts
1118
- import Table from "cli-table3";
1119
- import pc2 from "picocolors";
1120
- var currentFormat = "table";
1121
- function setOutputFormat(format) {
1122
- currentFormat = format;
1123
- }
1124
- function getOutputFormat() {
1125
- return currentFormat;
1126
- }
1127
- function output(data, options) {
1128
- switch (currentFormat) {
1129
- case "json":
1130
- logger.output(JSON.stringify(data, null, 2));
1131
- break;
1132
- case "quiet":
1133
- if (Array.isArray(data)) {
1134
- for (const item of data) {
1135
- if ("id" in item) logger.output(String(item.id));
1136
- }
1137
- } else if ("id" in data) {
1138
- logger.output(String(data.id));
1139
- }
1140
- break;
1141
- case "table":
1142
- default:
1143
- outputTable(data, options?.columns);
1144
- break;
1145
- }
1146
- }
1147
- function outputTable(data, columns) {
1148
- if (data === void 0 || data === null) {
1149
- logger.info("No results found.");
1150
- return;
1151
- }
1152
- const rawItems = Array.isArray(data) ? data : [data];
1153
- const items = rawItems.filter((item) => item !== void 0 && item !== null);
1154
- if (items.length === 0) {
1155
- logger.info("No results found.");
1156
- return;
1157
- }
1158
- const cols = columns ?? Object.keys(items[0]).map((key) => ({
1159
- key,
1160
- header: key.charAt(0).toUpperCase() + key.slice(1)
1161
- }));
1162
- const table = new Table({
1163
- head: cols.map((c) => c.header),
1164
- style: {
1165
- head: ["cyan"],
1166
- border: ["gray"]
1167
- }
1168
- });
1169
- for (const item of items) {
1170
- table.push(cols.map((c) => formatValue(item?.[c.key], c, item)));
1171
- }
1172
- logger.output(table.toString());
1173
- }
1174
- var STATUS_COLORS = {
1175
- // Success states (green)
1176
- up: "success",
1177
- ok: "success",
1178
- success: "success",
1179
- passed: "success",
1180
- active: "success",
1181
- sent: "success",
1182
- enabled: "success",
1183
- healthy: "success",
1184
- running: "success",
1185
- completed: "success",
1186
- // Error states (red)
1187
- down: "error",
1188
- failed: "error",
1189
- error: "error",
1190
- blocked: "error",
1191
- unhealthy: "error",
1192
- cancelled: "error",
1193
- canceled: "error",
1194
- // Warning states (yellow)
1195
- paused: "warning",
1196
- pending: "warning",
1197
- disabled: "warning",
1198
- degraded: "warning",
1199
- unknown: "warning",
1200
- queued: "warning",
1201
- warning: "warning",
1202
- skipped: "warning",
1203
- inactive: "warning"
1204
- };
1205
- function formatStatus(status) {
1206
- const normalizedStatus = status.toLowerCase();
1207
- const colorType = STATUS_COLORS[normalizedStatus];
1208
- if (!colorType) {
1209
- return status;
1210
- }
1211
- switch (colorType) {
1212
- case "success":
1213
- return pc2.green(`\u25CF ${status}`);
1214
- case "error":
1215
- return pc2.red(`\u25CF ${status}`);
1216
- case "warning":
1217
- return pc2.yellow(`\u25CF ${status}`);
1218
- default:
1219
- return status;
1220
- }
1221
- }
1222
- function formatValue(value, column, row) {
1223
- if (column?.format && row) {
1224
- return column.format(value, row);
1225
- }
1226
- if (value === null || value === void 0) return pc2.dim("-");
1227
- if (typeof value === "boolean") {
1228
- return value ? pc2.green("\u2713") : pc2.red("\u2717");
1229
- }
1230
- if (value instanceof Date) return formatDate(value);
1231
- if (Array.isArray(value)) return value.join(", ");
1232
- if (typeof value === "number") {
1233
- const dateForNumber = maybeFormatDate(value, column?.key);
1234
- if (dateForNumber) return dateForNumber;
1235
- return String(value);
1236
- }
1237
- if (typeof value === "object") return JSON.stringify(value);
1238
- const strValue = String(value);
1239
- const dateForString = maybeFormatDate(strValue, column?.key);
1240
- if (dateForString) return dateForString;
1241
- if (STATUS_COLORS[strValue.toLowerCase()]) {
1242
- return formatStatus(strValue);
1243
- }
1244
- return strValue;
1245
- }
1246
- var DATE_KEY_PATTERN = /(At|Date|Time|Timestamp)$/i;
1247
- function maybeFormatDate(value, key) {
1248
- if (!key && typeof value !== "string") return null;
1249
- if (key && !DATE_KEY_PATTERN.test(key) && typeof value !== "string") return null;
1250
- const date = new Date(value);
1251
- if (Number.isNaN(date.getTime())) return null;
1252
- if (typeof value === "string") {
1253
- const isIsoLike = value.includes("T") || value.endsWith("Z") || /[+-]\d{2}:?\d{2}$/.test(value);
1254
- if (!isIsoLike) return null;
1255
- return formatDate(date);
1256
- }
1257
- return formatDate(date);
1258
- }
1259
- function formatDate(date) {
1260
- return date.toISOString().replace("T", " ").replace("Z", "");
1261
- }
1262
- function outputDetail(data) {
1263
- if (currentFormat === "json") {
1264
- logger.output(JSON.stringify(data, null, 2));
1265
- return;
1283
+ }
1266
1284
  }
1267
- if (currentFormat === "quiet") {
1268
- if ("id" in data) logger.output(String(data.id));
1269
- return;
1285
+ }
1286
+ async function loadConfig(options = {}) {
1287
+ const cwd = options.cwd ?? process.cwd();
1288
+ const configPath = resolveConfigPath(cwd, options.configPath);
1289
+ if (!configPath) {
1290
+ throw new CLIError(
1291
+ "No supercheck.config.ts found. Run `supercheck init` to create one.",
1292
+ 3 /* ConfigError */
1293
+ );
1270
1294
  }
1271
- const keys = Object.keys(data);
1272
- if (keys.length === 0) {
1273
- logger.info("No details available.");
1274
- return;
1295
+ let config = await loadConfigFile(configPath);
1296
+ const localConfigPath = resolveLocalConfigPath(cwd);
1297
+ if (localConfigPath) {
1298
+ const localConfig = await loadConfigFile(localConfigPath);
1299
+ config = deepmerge(config, localConfig);
1275
1300
  }
1276
- const maxKeyLen = Math.max(...keys.map((k) => k.length));
1277
- for (const [key, value] of Object.entries(data)) {
1278
- const label = key.padEnd(maxKeyLen + 2);
1279
- logger.output(` ${label}${formatValue(value, { key, header: key }, data)}`);
1301
+ config = applyEnvOverrides(config);
1302
+ validateNoSecrets(config);
1303
+ const parsed = supercheckConfigSchema.safeParse(config);
1304
+ if (!parsed.success) {
1305
+ const issues = parsed.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
1306
+ throw new CLIError(
1307
+ `Invalid configuration:
1308
+ ${issues}`,
1309
+ 3 /* ConfigError */
1310
+ );
1311
+ }
1312
+ return {
1313
+ config: parsed.data,
1314
+ configPath
1315
+ };
1316
+ }
1317
+ async function tryLoadConfig(options = {}) {
1318
+ try {
1319
+ return await loadConfig(options);
1320
+ } catch (err) {
1321
+ if (err instanceof CLIError && err.message.includes("No supercheck.config.ts found")) {
1322
+ return null;
1323
+ }
1324
+ throw err;
1280
1325
  }
1281
1326
  }
1282
1327
 
@@ -1286,7 +1331,14 @@ var healthCommand = new Command2("health").description("Check Supercheck API hea
1286
1331
  const baseUrl = options.url ?? getStoredBaseUrl() ?? configResult?.config.api?.baseUrl ?? "https://app.supercheck.io";
1287
1332
  const client = getApiClient({ baseUrl });
1288
1333
  try {
1289
- const { data } = await client.get("/api/health");
1334
+ const data = await withSpinner(
1335
+ `Checking API health at ${baseUrl}...`,
1336
+ async () => {
1337
+ const { data: data2 } = await client.get("/api/health");
1338
+ return data2;
1339
+ },
1340
+ { successText: "Health check complete" }
1341
+ );
1290
1342
  const checks = data.checks ?? {};
1291
1343
  const displayData = {
1292
1344
  status: data.status,
@@ -1322,8 +1374,14 @@ var locationsCommand = new Command2("locations").description("List available exe
1322
1374
  const configResult = await tryLoadConfig();
1323
1375
  const baseUrl = getStoredBaseUrl() ?? configResult?.config.api?.baseUrl ?? "https://app.supercheck.io";
1324
1376
  const client = getApiClient({ baseUrl });
1325
- const { data } = await client.get("/api/locations");
1326
- const locations = "locations" in data ? data.locations : data.data ?? [];
1377
+ const locations = await withSpinner(
1378
+ "Fetching execution locations...",
1379
+ async () => {
1380
+ const { data } = await client.get("/api/locations");
1381
+ return "locations" in data ? data.locations : data.data ?? [];
1382
+ },
1383
+ { successText: "Locations loaded" }
1384
+ );
1327
1385
  output(locations, {
1328
1386
  columns: [
1329
1387
  { key: "code", header: "Code" },
@@ -1349,35 +1407,91 @@ configCommand.command("print").description("Print the resolved configuration").a
1349
1407
 
1350
1408
  // src/commands/init.ts
1351
1409
  import { Command as Command4 } from "commander";
1352
- import { existsSync as existsSync2, mkdirSync, writeFileSync, readFileSync as readFileSync2, appendFileSync } from "fs";
1410
+ import { existsSync as existsSync3, mkdirSync, writeFileSync as writeFileSync2, readFileSync as readFileSync2, appendFileSync } from "fs";
1411
+ import { resolve as resolve4 } from "path";
1412
+
1413
+ // src/utils/package-manager.ts
1414
+ import { existsSync as existsSync2, writeFileSync } from "fs";
1353
1415
  import { resolve as resolve3, basename as basename2 } from "path";
1416
+ function detectPackageManager(cwd) {
1417
+ if (existsSync2(resolve3(cwd, "bun.lockb")) || existsSync2(resolve3(cwd, "bun.lock"))) return "bun";
1418
+ if (existsSync2(resolve3(cwd, "pnpm-lock.yaml"))) return "pnpm";
1419
+ if (existsSync2(resolve3(cwd, "yarn.lock"))) return "yarn";
1420
+ return "npm";
1421
+ }
1422
+ function getInstallCommand(pm, packages) {
1423
+ const pkgs = packages.join(" ");
1424
+ switch (pm) {
1425
+ case "bun":
1426
+ return `bun add -d ${pkgs}`;
1427
+ case "pnpm":
1428
+ return `pnpm add -D ${pkgs}`;
1429
+ case "yarn":
1430
+ return `yarn add -D ${pkgs}`;
1431
+ case "npm":
1432
+ return `npm install --save-dev ${pkgs}`;
1433
+ }
1434
+ }
1435
+ function ensurePackageJson(cwd) {
1436
+ const pkgPath = resolve3(cwd, "package.json");
1437
+ if (existsSync2(pkgPath)) return false;
1438
+ const projectName = basename2(cwd).toLowerCase().replace(/[^a-z0-9-]/g, "-");
1439
+ const minimalPkg = {
1440
+ name: projectName,
1441
+ private: true,
1442
+ type: "module",
1443
+ scripts: {
1444
+ "supercheck:deploy": "supercheck deploy",
1445
+ "supercheck:diff": "supercheck diff",
1446
+ "supercheck:pull": "supercheck pull",
1447
+ "supercheck:test": "supercheck test"
1448
+ }
1449
+ };
1450
+ writeFileSync(pkgPath, JSON.stringify(minimalPkg, null, 2) + "\n", "utf-8");
1451
+ return true;
1452
+ }
1453
+ async function installDependencies(cwd, pm, opts = {}) {
1454
+ if (opts.skipInstall) {
1455
+ logger.info("Skipping dependency installation");
1456
+ return;
1457
+ }
1458
+ const devDeps = opts.packages ?? ["@supercheck/cli", "typescript", "@types/node"];
1459
+ const cmd = getInstallCommand(pm, devDeps);
1460
+ logger.info(`Installing dependencies with ${pm}...`);
1461
+ logger.debug(`Running: ${cmd}`);
1462
+ await withSpinner(
1463
+ `Installing ${devDeps.length} packages...`,
1464
+ async () => {
1465
+ const { execSync } = await import("child_process");
1466
+ try {
1467
+ execSync(cmd, {
1468
+ cwd,
1469
+ stdio: "pipe",
1470
+ timeout: 12e4,
1471
+ // 2 minute timeout
1472
+ env: { ...process.env, NODE_ENV: "development" }
1473
+ });
1474
+ } catch (err) {
1475
+ const message = err instanceof Error ? err.message : String(err);
1476
+ throw new CLIError(
1477
+ `Failed to install dependencies. Run manually:
1478
+ ${cmd}
1354
1479
 
1355
- // src/utils/spinner.ts
1356
- import ora from "ora";
1357
- function createSpinner(text) {
1358
- const format = getOutputFormat();
1359
- const isInteractive = format === "table";
1360
- const spinner = ora({
1361
- text,
1362
- // Disable spinner in non-interactive modes
1363
- isSilent: !isInteractive,
1364
- // Use dots style for a clean look
1365
- spinner: "dots"
1366
- });
1367
- return spinner;
1480
+ Error: ${message}`,
1481
+ 1 /* GeneralError */
1482
+ );
1483
+ }
1484
+ },
1485
+ { successText: "Dependencies installed" }
1486
+ );
1368
1487
  }
1369
- async function withSpinner(text, fn, options) {
1370
- const spinner = createSpinner(text);
1371
- spinner.start();
1488
+ async function checkK6Binary() {
1489
+ const { execSync } = await import("child_process");
1372
1490
  try {
1373
- const result = await fn();
1374
- const successMessage = typeof options?.successText === "function" ? options.successText(result) : options?.successText ?? text.replace(/\.\.\.?$/, "");
1375
- spinner.succeed(successMessage);
1376
- return result;
1377
- } catch (error) {
1378
- const failMessage = options?.failText ?? text.replace(/\.\.\.?$/, " failed");
1379
- spinner.fail(failMessage);
1380
- throw error;
1491
+ execSync("k6 version", { stdio: "ignore" });
1492
+ return true;
1493
+ } catch {
1494
+ return false;
1381
1495
  }
1382
1496
  }
1383
1497
 
@@ -1454,84 +1568,10 @@ supercheck.config.local.ts
1454
1568
  supercheck.config.local.js
1455
1569
  supercheck.config.local.mjs
1456
1570
  `;
1457
- function detectPackageManager(cwd) {
1458
- if (existsSync2(resolve3(cwd, "bun.lockb")) || existsSync2(resolve3(cwd, "bun.lock"))) return "bun";
1459
- if (existsSync2(resolve3(cwd, "pnpm-lock.yaml"))) return "pnpm";
1460
- if (existsSync2(resolve3(cwd, "yarn.lock"))) return "yarn";
1461
- return "npm";
1462
- }
1463
- function getInstallCommand(pm, packages) {
1464
- const pkgs = packages.join(" ");
1465
- switch (pm) {
1466
- case "bun":
1467
- return `bun add -d ${pkgs}`;
1468
- case "pnpm":
1469
- return `pnpm add -D ${pkgs}`;
1470
- case "yarn":
1471
- return `yarn add -D ${pkgs}`;
1472
- case "npm":
1473
- return `npm install --save-dev ${pkgs}`;
1474
- }
1475
- }
1476
- function ensurePackageJson(cwd) {
1477
- const pkgPath = resolve3(cwd, "package.json");
1478
- if (existsSync2(pkgPath)) return false;
1479
- const projectName = basename2(cwd).toLowerCase().replace(/[^a-z0-9-]/g, "-");
1480
- const minimalPkg = {
1481
- name: projectName,
1482
- private: true,
1483
- type: "module",
1484
- scripts: {
1485
- "supercheck:deploy": "supercheck deploy",
1486
- "supercheck:diff": "supercheck diff",
1487
- "supercheck:pull": "supercheck pull"
1488
- }
1489
- };
1490
- writeFileSync(pkgPath, JSON.stringify(minimalPkg, null, 2) + "\n", "utf-8");
1491
- return true;
1492
- }
1493
- async function installDependencies(cwd, pm, opts) {
1494
- if (opts.skipInstall) {
1495
- logger.info("Skipping dependency installation (--skip-install)");
1496
- return;
1497
- }
1498
- const devDeps = ["@supercheck/cli", "typescript", "@types/node"];
1499
- if (opts.playwright) {
1500
- devDeps.push("@playwright/test");
1501
- }
1502
- const cmd = getInstallCommand(pm, devDeps);
1503
- logger.info(`Installing dependencies with ${pm}...`);
1504
- logger.debug(`Running: ${cmd}`);
1505
- await withSpinner(
1506
- `Installing ${devDeps.length} packages...`,
1507
- async () => {
1508
- const { execSync } = await import("child_process");
1509
- try {
1510
- execSync(cmd, {
1511
- cwd,
1512
- stdio: "pipe",
1513
- timeout: 12e4,
1514
- // 2 minute timeout
1515
- env: { ...process.env, NODE_ENV: "development" }
1516
- });
1517
- } catch (err) {
1518
- const message = err instanceof Error ? err.message : String(err);
1519
- throw new CLIError(
1520
- `Failed to install dependencies. Run manually:
1521
- ${cmd}
1522
-
1523
- Error: ${message}`,
1524
- 1 /* GeneralError */
1525
- );
1526
- }
1527
- },
1528
- { successText: "Dependencies installed" }
1529
- );
1530
- }
1531
1571
  var initCommand = new Command4("init").description("Initialize a new Supercheck project with config and example tests").option("--force", "Overwrite existing config file").option("--skip-install", "Skip automatic dependency installation").option("--skip-examples", "Skip creating example test files").option("--pm <manager>", "Package manager to use (npm, yarn, pnpm, bun)").action(async (options) => {
1532
1572
  const cwd = process.cwd();
1533
- const configPath = resolve3(cwd, "supercheck.config.ts");
1534
- if (existsSync2(configPath) && !options.force) {
1573
+ const configPath = resolve4(cwd, "supercheck.config.ts");
1574
+ if (existsSync3(configPath) && !options.force) {
1535
1575
  throw new CLIError(
1536
1576
  "supercheck.config.ts already exists. Use --force to overwrite.",
1537
1577
  3 /* ConfigError */
@@ -1540,11 +1580,11 @@ var initCommand = new Command4("init").description("Initialize a new Supercheck
1540
1580
  logger.newline();
1541
1581
  logger.header("Initializing Supercheck project...");
1542
1582
  logger.newline();
1543
- writeFileSync(configPath, CONFIG_TEMPLATE, "utf-8");
1583
+ writeFileSync2(configPath, CONFIG_TEMPLATE, "utf-8");
1544
1584
  logger.success("Created supercheck.config.ts");
1545
- const tsconfigPath = resolve3(cwd, "tsconfig.supercheck.json");
1546
- if (!existsSync2(tsconfigPath) || options.force) {
1547
- writeFileSync(tsconfigPath, SUPERCHECK_TSCONFIG, "utf-8");
1585
+ const tsconfigPath = resolve4(cwd, "tsconfig.supercheck.json");
1586
+ if (!existsSync3(tsconfigPath) || options.force) {
1587
+ writeFileSync2(tsconfigPath, SUPERCHECK_TSCONFIG, "utf-8");
1548
1588
  logger.success("Created tsconfig.supercheck.json (IDE IntelliSense)");
1549
1589
  }
1550
1590
  const dirs = [
@@ -1553,32 +1593,32 @@ var initCommand = new Command4("init").description("Initialize a new Supercheck
1553
1593
  "_supercheck_/status-pages"
1554
1594
  ];
1555
1595
  for (const dir of dirs) {
1556
- const dirPath = resolve3(cwd, dir);
1557
- if (!existsSync2(dirPath)) {
1596
+ const dirPath = resolve4(cwd, dir);
1597
+ if (!existsSync3(dirPath)) {
1558
1598
  mkdirSync(dirPath, { recursive: true });
1559
1599
  }
1560
1600
  if (dir !== "_supercheck_/tests") {
1561
- const gitkeepPath = resolve3(dirPath, ".gitkeep");
1562
- if (!existsSync2(gitkeepPath)) {
1563
- writeFileSync(gitkeepPath, "", "utf-8");
1601
+ const gitkeepPath = resolve4(dirPath, ".gitkeep");
1602
+ if (!existsSync3(gitkeepPath)) {
1603
+ writeFileSync2(gitkeepPath, "", "utf-8");
1564
1604
  }
1565
1605
  }
1566
1606
  }
1567
1607
  logger.success("Created _supercheck_/ directory structure");
1568
1608
  if (!options.skipExamples) {
1569
- const pwTestPath = resolve3(cwd, "_supercheck_/tests/homepage.pw.ts");
1570
- if (!existsSync2(pwTestPath)) {
1571
- writeFileSync(pwTestPath, EXAMPLE_PW_TEST, "utf-8");
1609
+ const pwTestPath = resolve4(cwd, "_supercheck_/tests/homepage.pw.ts");
1610
+ if (!existsSync3(pwTestPath)) {
1611
+ writeFileSync2(pwTestPath, EXAMPLE_PW_TEST, "utf-8");
1572
1612
  logger.success("Created _supercheck_/tests/homepage.pw.ts (Playwright example)");
1573
1613
  }
1574
- const k6TestPath = resolve3(cwd, "_supercheck_/tests/load-test.k6.ts");
1575
- if (!existsSync2(k6TestPath)) {
1576
- writeFileSync(k6TestPath, EXAMPLE_K6_TEST, "utf-8");
1614
+ const k6TestPath = resolve4(cwd, "_supercheck_/tests/load-test.k6.ts");
1615
+ if (!existsSync3(k6TestPath)) {
1616
+ writeFileSync2(k6TestPath, EXAMPLE_K6_TEST, "utf-8");
1577
1617
  logger.success("Created _supercheck_/tests/load-test.k6.ts (k6 example)");
1578
1618
  }
1579
1619
  }
1580
- const gitignorePath = resolve3(cwd, ".gitignore");
1581
- if (existsSync2(gitignorePath)) {
1620
+ const gitignorePath = resolve4(cwd, ".gitignore");
1621
+ if (existsSync3(gitignorePath)) {
1582
1622
  const content = readFileSync2(gitignorePath, "utf-8");
1583
1623
  if (!content.includes("supercheck.config.local")) {
1584
1624
  appendFileSync(gitignorePath, GITIGNORE_ADDITIONS, "utf-8");
@@ -1592,7 +1632,7 @@ var initCommand = new Command4("init").description("Initialize a new Supercheck
1592
1632
  logger.success("Created package.json");
1593
1633
  }
1594
1634
  await installDependencies(cwd, pm, {
1595
- playwright: !options.skipExamples,
1635
+ packages: options.skipExamples ? ["@supercheck/cli", "typescript", "@types/node"] : ["@supercheck/cli", "typescript", "@types/node", "@playwright/test"],
1596
1636
  skipInstall: options.skipInstall ?? false
1597
1637
  });
1598
1638
  logger.newline();
@@ -1748,13 +1788,9 @@ jobCommand.command("update <id>").description("Update job configuration").option
1748
1788
  });
1749
1789
  jobCommand.command("delete <id>").description("Delete a job").option("--force", "Skip confirmation").action(async (id, options) => {
1750
1790
  if (!options.force) {
1751
- const { createInterface } = await import("readline");
1752
- const rl = createInterface({ input: process.stdin, output: process.stdout });
1753
- const answer = await new Promise((resolve5) => {
1754
- rl.question(`Are you sure you want to delete job ${id}? (y/N) `, resolve5);
1755
- });
1756
- rl.close();
1757
- if (answer.toLowerCase() !== "y") {
1791
+ const { confirmPrompt: confirmPrompt2 } = await import("../prompt-BPDPYRS7.js");
1792
+ const confirmed = await confirmPrompt2(`Delete job ${id}?`, { default: false });
1793
+ if (!confirmed) {
1758
1794
  logger.info("Aborted");
1759
1795
  return;
1760
1796
  }
@@ -1811,7 +1847,7 @@ jobCommand.command("trigger <id>").description("Trigger a job run").option("--wa
1811
1847
  const timeoutMs = parseIntStrict(options.timeout, "--timeout", { min: 1 }) * 1e3;
1812
1848
  const startTime = Date.now();
1813
1849
  while (Date.now() - startTime < timeoutMs) {
1814
- await new Promise((resolve5) => setTimeout(resolve5, 3e3));
1850
+ await new Promise((resolve6) => setTimeout(resolve6, 3e3));
1815
1851
  const { data: runData } = await statusClient.get(
1816
1852
  `/api/runs/${data.runId}`
1817
1853
  );
@@ -2136,8 +2172,8 @@ testCommand.command("get <id>").description("Get test details").option("--includ
2136
2172
  });
2137
2173
  testCommand.command("create").description("Create a new test").requiredOption("--title <title>", "Test title").requiredOption("--file <path>", "Path to the test script file").option("--type <type>", "Test type (playwright, k6)", "playwright").option("--description <description>", "Test description").action(async (options) => {
2138
2174
  const { readFileSync: readFileSync4 } = await import("fs");
2139
- const { resolve: resolve5 } = await import("path");
2140
- const filePath = resolve5(process.cwd(), options.file);
2175
+ const { resolve: resolve6 } = await import("path");
2176
+ const filePath = resolve6(process.cwd(), options.file);
2141
2177
  let script;
2142
2178
  try {
2143
2179
  script = readFileSync4(filePath, "utf-8");
@@ -2164,8 +2200,8 @@ testCommand.command("update <id>").description("Update a test").option("--title
2164
2200
  if (options.description !== void 0) body.description = options.description;
2165
2201
  if (options.file) {
2166
2202
  const { readFileSync: readFileSync4 } = await import("fs");
2167
- const { resolve: resolve5 } = await import("path");
2168
- const filePath = resolve5(process.cwd(), options.file);
2203
+ const { resolve: resolve6 } = await import("path");
2204
+ const filePath = resolve6(process.cwd(), options.file);
2169
2205
  try {
2170
2206
  const raw = readFileSync4(filePath, "utf-8");
2171
2207
  body.script = Buffer2.from(raw, "utf-8").toString("base64");
@@ -2183,13 +2219,9 @@ testCommand.command("update <id>").description("Update a test").option("--title
2183
2219
  });
2184
2220
  testCommand.command("delete <id>").description("Delete a test").option("--force", "Skip confirmation").action(async (id, options) => {
2185
2221
  if (!options.force) {
2186
- const { createInterface } = await import("readline");
2187
- const rl = createInterface({ input: process.stdin, output: process.stdout });
2188
- const answer = await new Promise((resolve5) => {
2189
- rl.question(`Are you sure you want to delete test ${id}? (y/N) `, resolve5);
2190
- });
2191
- rl.close();
2192
- if (answer.toLowerCase() !== "y") {
2222
+ const { confirmPrompt: confirmPrompt2 } = await import("../prompt-BPDPYRS7.js");
2223
+ const confirmed = await confirmPrompt2(`Delete test ${id}?`, { default: false });
2224
+ if (!confirmed) {
2193
2225
  logger.info("Aborted");
2194
2226
  return;
2195
2227
  }
@@ -2218,8 +2250,8 @@ testCommand.command("tags <id>").description("Get test tags").action(async (id)
2218
2250
  });
2219
2251
  testCommand.command("validate").description("Validate a test script").requiredOption("--file <path>", "Path to the test script file").option("--type <type>", "Test type (playwright, k6)", "playwright").action(async (options) => {
2220
2252
  const { readFileSync: readFileSync4 } = await import("fs");
2221
- const { resolve: resolve5 } = await import("path");
2222
- const filePath = resolve5(process.cwd(), options.file);
2253
+ const { resolve: resolve6 } = await import("path");
2254
+ const filePath = resolve6(process.cwd(), options.file);
2223
2255
  let script;
2224
2256
  try {
2225
2257
  script = readFileSync4(filePath, "utf-8");
@@ -2421,13 +2453,9 @@ monitorCommand.command("update <id>").description("Update a monitor").option("--
2421
2453
  });
2422
2454
  monitorCommand.command("delete <id>").description("Delete a monitor").option("--force", "Skip confirmation").action(async (id, options) => {
2423
2455
  if (!options.force) {
2424
- const { createInterface } = await import("readline");
2425
- const rl = createInterface({ input: process.stdin, output: process.stdout });
2426
- const answer = await new Promise((resolve5) => {
2427
- rl.question(`Are you sure you want to delete monitor ${id}? (y/N) `, resolve5);
2428
- });
2429
- rl.close();
2430
- if (answer.toLowerCase() !== "y") {
2456
+ const { confirmPrompt: confirmPrompt2 } = await import("../prompt-BPDPYRS7.js");
2457
+ const confirmed = await confirmPrompt2(`Delete monitor ${id}?`, { default: false });
2458
+ if (!confirmed) {
2431
2459
  logger.info("Aborted");
2432
2460
  return;
2433
2461
  }
@@ -2483,13 +2511,21 @@ varCommand.command("set <key> <value>").description("Create or update a variable
2483
2511
  logger.success(`Variable "${key}" created`);
2484
2512
  }
2485
2513
  });
2486
- varCommand.command("delete <key>").description("Delete a variable").action(async (key) => {
2514
+ varCommand.command("delete <key>").description("Delete a variable").option("--force", "Skip confirmation").action(async (key, options) => {
2487
2515
  const client = createAuthenticatedClient();
2488
2516
  const { data: variables } = await client.get("/api/variables");
2489
2517
  const variable = variables.find((v) => v.key === key);
2490
2518
  if (!variable) {
2491
2519
  throw new CLIError(`Variable "${key}" not found`, 1 /* GeneralError */);
2492
2520
  }
2521
+ if (!options.force) {
2522
+ const { confirmPrompt: confirmPrompt2 } = await import("../prompt-BPDPYRS7.js");
2523
+ const confirmed = await confirmPrompt2(`Delete variable "${key}"?`, { default: false });
2524
+ if (!confirmed) {
2525
+ logger.info("Aborted");
2526
+ return;
2527
+ }
2528
+ }
2493
2529
  await client.delete(`/api/variables/${variable.id}`);
2494
2530
  logger.success(`Variable "${key}" deleted`);
2495
2531
  });
@@ -2516,7 +2552,15 @@ tagCommand.command("create <name>").description("Create a new tag").option("--co
2516
2552
  });
2517
2553
  logger.success(`Tag "${name}" created (${data.id})`);
2518
2554
  });
2519
- tagCommand.command("delete <id>").description("Delete a tag").action(async (id) => {
2555
+ tagCommand.command("delete <id>").description("Delete a tag").option("--force", "Skip confirmation").action(async (id, options) => {
2556
+ if (!options.force) {
2557
+ const { confirmPrompt: confirmPrompt2 } = await import("../prompt-BPDPYRS7.js");
2558
+ const confirmed = await confirmPrompt2(`Delete tag ${id}?`, { default: false });
2559
+ if (!confirmed) {
2560
+ logger.info("Aborted");
2561
+ return;
2562
+ }
2563
+ }
2520
2564
  const client = createAuthenticatedClient();
2521
2565
  await client.delete(`/api/tags/${id}`);
2522
2566
  logger.success(`Tag ${id} deleted`);
@@ -2668,10 +2712,16 @@ var diffCommand = new Command11("diff").description("Preview changes between loc
2668
2712
  const cwd = process.cwd();
2669
2713
  const { config } = await loadConfig({ cwd, configPath: options.config });
2670
2714
  const client = createAuthenticatedClient();
2671
- logger.info("Comparing local config with remote project...");
2672
2715
  logger.newline();
2673
- const localResources = buildLocalResources(config, cwd);
2674
- const remoteResources = await fetchRemoteResources(client);
2716
+ const { localResources, remoteResources } = await withSpinner(
2717
+ "Comparing local and remote resources...",
2718
+ async () => {
2719
+ const local = buildLocalResources(config, cwd);
2720
+ const remote = await fetchRemoteResources(client);
2721
+ return { localResources: local, remoteResources: remote };
2722
+ },
2723
+ { successText: "Resources compared" }
2724
+ );
2675
2725
  logger.debug(`Local: ${localResources.length} resources, Remote: ${remoteResources.length} resources`);
2676
2726
  const changes = reconcile(localResources, remoteResources);
2677
2727
  formatChangePlan(changes);
@@ -2710,12 +2760,20 @@ async function applyChange(client, change) {
2710
2760
  }
2711
2761
  var deployCommand = new Command12("deploy").description("Push local config resources to the Supercheck project").option("--config <path>", "Path to config file").option("--dry-run", "Show what would change without applying").option("--force", "Skip confirmation prompt").option("--no-delete", "Do not delete remote resources missing from config").action(async (options) => {
2712
2762
  const cwd = process.cwd();
2713
- const { config, configPath } = await loadConfig({ cwd, configPath: options.config });
2763
+ const { config } = await loadConfig({ cwd, configPath: options.config });
2714
2764
  const client = createAuthenticatedClient();
2715
- logger.info(`Deploying from ${configPath ?? "config"}...`);
2716
2765
  logger.newline();
2717
- const localResources = buildLocalResources(config, cwd);
2718
- const remoteResources = await fetchRemoteResources(client);
2766
+ logger.header("Deploying from config...");
2767
+ logger.newline();
2768
+ const { localResources, remoteResources } = await withSpinner(
2769
+ "Comparing local and remote resources...",
2770
+ async () => {
2771
+ const local = buildLocalResources(config, cwd);
2772
+ const remote = await fetchRemoteResources(client);
2773
+ return { localResources: local, remoteResources: remote };
2774
+ },
2775
+ { successText: "Resources compared" }
2776
+ );
2719
2777
  let changes = reconcile(localResources, remoteResources);
2720
2778
  if (options.delete === false) {
2721
2779
  changes = changes.filter((c) => c.action !== "delete");
@@ -2731,13 +2789,8 @@ var deployCommand = new Command12("deploy").description("Push local config resou
2731
2789
  return;
2732
2790
  }
2733
2791
  if (!options.force) {
2734
- const { createInterface } = await import("readline");
2735
- const rl = createInterface({ input: process.stdin, output: process.stdout });
2736
- const answer = await new Promise((resolve5) => {
2737
- rl.question("Do you want to apply these changes? (y/N) ", resolve5);
2738
- });
2739
- rl.close();
2740
- if (!answer || answer.toLowerCase() !== "y") {
2792
+ const confirmed = await confirmPrompt("Apply these changes?", { default: false });
2793
+ if (!confirmed) {
2741
2794
  logger.info("Deploy aborted.");
2742
2795
  return;
2743
2796
  }
@@ -2893,9 +2946,12 @@ var destroyCommand = new Command13("destroy").description("Tear down managed res
2893
2946
  const cwd = process.cwd();
2894
2947
  const { config } = await loadConfig({ cwd, configPath: options.config });
2895
2948
  const client = createAuthenticatedClient();
2896
- logger.info("Scanning for managed resources to destroy...");
2897
2949
  logger.newline();
2898
- const managed = await fetchManagedResources(client, config);
2950
+ const managed = await withSpinner(
2951
+ "Scanning for managed resources...",
2952
+ () => fetchManagedResources(client, config),
2953
+ { successText: "Scan complete" }
2954
+ );
2899
2955
  if (managed.length === 0) {
2900
2956
  logger.success("No managed resources found on the remote project.");
2901
2957
  return;
@@ -2949,8 +3005,8 @@ var destroyCommand = new Command13("destroy").description("Tear down managed res
2949
3005
 
2950
3006
  // src/commands/pull.ts
2951
3007
  import { Command as Command14 } from "commander";
2952
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync3 } from "fs";
2953
- import { resolve as resolve4, dirname as dirname2 } from "path";
3008
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, writeFileSync as writeFileSync3, readFileSync as readFileSync3 } from "fs";
3009
+ import { resolve as resolve5, dirname as dirname2 } from "path";
2954
3010
  import pc6 from "picocolors";
2955
3011
  function mapTestType(apiType) {
2956
3012
  if (apiType === "performance" || apiType === "k6") return "k6";
@@ -2964,15 +3020,15 @@ function testFilename(testId, testType) {
2964
3020
  return `${testId}${ext}`;
2965
3021
  }
2966
3022
  function writeIfChanged(filePath, content) {
2967
- if (existsSync3(filePath)) {
3023
+ if (existsSync4(filePath)) {
2968
3024
  const existing = readFileSync3(filePath, "utf-8");
2969
3025
  if (existing === content) return false;
2970
3026
  }
2971
3027
  const dir = dirname2(filePath);
2972
- if (!existsSync3(dir)) {
3028
+ if (!existsSync4(dir)) {
2973
3029
  mkdirSync2(dir, { recursive: true });
2974
3030
  }
2975
- writeFileSync2(filePath, content, "utf-8");
3031
+ writeFileSync3(filePath, content, "utf-8");
2976
3032
  return true;
2977
3033
  }
2978
3034
  function decodeScript(script) {
@@ -3056,8 +3112,8 @@ async function fetchStatusPages(client) {
3056
3112
  }
3057
3113
  }
3058
3114
  function pullTests(tests, cwd, summary) {
3059
- const testsDir = resolve4(cwd, "_supercheck_/tests");
3060
- if (!existsSync3(testsDir)) {
3115
+ const testsDir = resolve5(cwd, "_supercheck_/tests");
3116
+ if (!existsSync4(testsDir)) {
3061
3117
  mkdirSync2(testsDir, { recursive: true });
3062
3118
  }
3063
3119
  for (const test of tests) {
@@ -3071,7 +3127,7 @@ function pullTests(tests, cwd, summary) {
3071
3127
  summary.skipped++;
3072
3128
  continue;
3073
3129
  }
3074
- const filePath = resolve4(cwd, relPath);
3130
+ const filePath = resolve5(cwd, relPath);
3075
3131
  const written = writeIfChanged(filePath, script);
3076
3132
  if (written) {
3077
3133
  logger.info(pc6.green(` + ${relPath}`));
@@ -3246,6 +3302,26 @@ function generateConfigContent(opts) {
3246
3302
  }
3247
3303
  parts.push("})");
3248
3304
  parts.push("");
3305
+ parts.push(`/**`);
3306
+ parts.push(` * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
3307
+ parts.push(` * Getting Started`);
3308
+ parts.push(` * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
3309
+ parts.push(` *`);
3310
+ parts.push(` * 1. Install dependencies:`);
3311
+ parts.push(` * npm install`);
3312
+ parts.push(` * npx playwright install`);
3313
+ parts.push(` *`);
3314
+ parts.push(` * 2. Run tests:`);
3315
+ parts.push(` * npx supercheck test`);
3316
+ parts.push(` *`);
3317
+ parts.push(` * 3. Useful commands:`);
3318
+ parts.push(` * supercheck diff Preview changes against the cloud`);
3319
+ parts.push(` * supercheck deploy Push local config to Supercheck`);
3320
+ parts.push(` * supercheck pull Sync cloud resources locally`);
3321
+ parts.push(` *`);
3322
+ parts.push(` * Documentation: https://docs.supercheck.io`);
3323
+ parts.push(` */`);
3324
+ parts.push("");
3249
3325
  return parts.join("\n");
3250
3326
  }
3251
3327
  function formatArray(arr, baseIndent) {
@@ -3374,13 +3450,9 @@ var pullCommand = new Command14("pull").description("Pull tests, monitors, jobs,
3374
3450
  logger.info(`Found ${pc6.bold(String(totalResources))} resources to pull.`);
3375
3451
  logger.info("This will write test scripts and update supercheck.config.ts.");
3376
3452
  logger.newline();
3377
- const { createInterface } = await import("readline");
3378
- const rl = createInterface({ input: process.stdin, output: process.stdout });
3379
- const answer = await new Promise((resolvePrompt) => {
3380
- rl.question("Continue? (y/N) ", resolvePrompt);
3381
- });
3382
- rl.close();
3383
- if (!answer || answer.toLowerCase() !== "y") {
3453
+ const { confirmPrompt: confirmPrompt2 } = await import("../prompt-BPDPYRS7.js");
3454
+ const confirmed = await confirmPrompt2("Continue?", { default: true });
3455
+ if (!confirmed) {
3384
3456
  logger.info("Pull aborted.");
3385
3457
  return;
3386
3458
  }
@@ -3452,7 +3524,7 @@ var pullCommand = new Command14("pull").description("Pull tests, monitors, jobs,
3452
3524
  tags: tagDefs,
3453
3525
  statusPages: statusPageDefs
3454
3526
  });
3455
- const configPath = resolve4(cwd, "supercheck.config.ts");
3527
+ const configPath = resolve5(cwd, "supercheck.config.ts");
3456
3528
  const configChanged = writeIfChanged(configPath, configContent);
3457
3529
  if (configChanged) {
3458
3530
  logger.header("Config:");
@@ -3490,13 +3562,40 @@ var pullCommand = new Command14("pull").description("Pull tests, monitors, jobs,
3490
3562
  if (resultParts.length > 0) {
3491
3563
  logger.success(`Pull complete: ${resultParts.join(", ")}`);
3492
3564
  }
3493
- if (totalErrors > 0) {
3494
- throw new CLIError(
3495
- `Pull completed with ${totalErrors} error(s)`,
3496
- 1 /* GeneralError */
3497
- );
3565
+ }
3566
+ logger.newline();
3567
+ const createdPkg = ensurePackageJson(cwd);
3568
+ if (createdPkg) logger.success("Created package.json");
3569
+ if (!options.force) {
3570
+ const { confirmPrompt: confirmPrompt2 } = await import("../prompt-BPDPYRS7.js");
3571
+ const shouldInstall = await confirmPrompt2("Install dependencies?", { default: true });
3572
+ if (shouldInstall) {
3573
+ const pm = detectPackageManager(cwd);
3574
+ const hasPlaywrightTests = tests.some((t) => t.type !== "performance" && t.type !== "k6");
3575
+ const packages = ["@supercheck/cli", "typescript", "@types/node"];
3576
+ if (hasPlaywrightTests) {
3577
+ packages.push("@playwright/test");
3578
+ }
3579
+ await installDependencies(cwd, pm, {
3580
+ packages,
3581
+ skipInstall: false
3582
+ });
3498
3583
  }
3499
3584
  }
3585
+ const hasK6Tests = tests.some((t) => t.type === "performance" || t.type === "k6");
3586
+ if (hasK6Tests) {
3587
+ const hasK6 = await checkK6Binary();
3588
+ if (!hasK6) {
3589
+ logger.warn("k6 binary not found in PATH.");
3590
+ logger.info("Please install k6 to run performance tests: https://grafana.com/docs/k6/latest/set-up/install-k6/");
3591
+ }
3592
+ }
3593
+ if (totalErrors > 0) {
3594
+ throw new CLIError(
3595
+ `Pull completed with ${totalErrors} error(s)`,
3596
+ 1 /* GeneralError */
3597
+ );
3598
+ }
3500
3599
  logger.newline();
3501
3600
  logger.info("Tip: Review the changes, then commit to version control.");
3502
3601
  logger.info(" Run `supercheck diff` to compare local vs remote.");
@@ -3578,13 +3677,9 @@ notificationCommand.command("update <id>").description("Update a notification pr
3578
3677
  });
3579
3678
  notificationCommand.command("delete <id>").description("Delete a notification provider").option("--force", "Skip confirmation").action(async (id, options) => {
3580
3679
  if (!options.force) {
3581
- const { createInterface } = await import("readline");
3582
- const rl = createInterface({ input: process.stdin, output: process.stdout });
3583
- const answer = await new Promise((resolve5) => {
3584
- rl.question(`Are you sure you want to delete notification provider ${id}? (y/N) `, resolve5);
3585
- });
3586
- rl.close();
3587
- if (answer.toLowerCase() !== "y") {
3680
+ const { confirmPrompt: confirmPrompt2 } = await import("../prompt-BPDPYRS7.js");
3681
+ const confirmed = await confirmPrompt2(`Delete notification provider ${id}?`, { default: false });
3682
+ if (!confirmed) {
3588
3683
  logger.info("Aborted");
3589
3684
  return;
3590
3685
  }