@supercheck/cli 0.1.0-beta.5 → 0.1.0-beta.7

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.
@@ -213,7 +213,7 @@ function requireTriggerKey() {
213
213
  }
214
214
 
215
215
  // src/version.ts
216
- var CLI_VERSION = true ? "0.1.0-beta.5" : "0.0.0-dev";
216
+ var CLI_VERSION = true ? "0.1.0-beta.7" : "0.0.0-dev";
217
217
 
218
218
  // src/api/client.ts
219
219
  import { ProxyAgent } from "undici";
@@ -1485,15 +1485,6 @@ Error: ${message}`,
1485
1485
  { successText: "Dependencies installed" }
1486
1486
  );
1487
1487
  }
1488
- async function checkK6Binary() {
1489
- const { execSync } = await import("child_process");
1490
- try {
1491
- execSync("k6 version", { stdio: "ignore" });
1492
- return true;
1493
- } catch {
1494
- return false;
1495
- }
1496
- }
1497
1488
 
1498
1489
  // src/commands/init.ts
1499
1490
  var CONFIG_TEMPLATE = `import { defineConfig } from '@supercheck/cli'
@@ -1692,9 +1683,12 @@ function parseIntStrict(value, name, opts) {
1692
1683
  var jobCommand = new Command5("job").description("Manage jobs");
1693
1684
  jobCommand.command("list").description("List all jobs").option("--page <page>", "Page number", "1").option("--limit <limit>", "Items per page", "50").action(async (options) => {
1694
1685
  const client = createAuthenticatedClient();
1695
- const { data } = await client.get(
1696
- "/api/jobs",
1697
- { page: options.page, limit: options.limit }
1686
+ const { data } = await withSpinner(
1687
+ "Fetching jobs",
1688
+ () => client.get(
1689
+ "/api/jobs",
1690
+ { page: options.page, limit: options.limit }
1691
+ )
1698
1692
  );
1699
1693
  output(data.data, {
1700
1694
  columns: [
@@ -1715,8 +1709,11 @@ Page ${data.pagination.page}/${data.pagination.totalPages} (${data.pagination.to
1715
1709
  var keysCommand = jobCommand.command("keys").description("Manage job trigger keys");
1716
1710
  keysCommand.argument("<jobId>", "Job ID").action(async (jobId) => {
1717
1711
  const client = createAuthenticatedClient();
1718
- const { data } = await client.get(
1719
- `/api/jobs/${jobId}/api-keys`
1712
+ const { data } = await withSpinner(
1713
+ "Fetching trigger keys",
1714
+ () => client.get(
1715
+ `/api/jobs/${jobId}/api-keys`
1716
+ )
1720
1717
  );
1721
1718
  output(data.apiKeys ?? [], {
1722
1719
  columns: [
@@ -1735,9 +1732,12 @@ keysCommand.command("create").description("Create a new trigger key for a job").
1735
1732
  if (options.expiresIn !== void 0) {
1736
1733
  body.expiresIn = parseIntStrict(options.expiresIn, "--expires-in", { min: 60 });
1737
1734
  }
1738
- const { data } = await client.post(
1739
- `/api/jobs/${jobId}/api-keys`,
1740
- body
1735
+ const { data } = await withSpinner(
1736
+ "Creating trigger key",
1737
+ () => client.post(
1738
+ `/api/jobs/${jobId}/api-keys`,
1739
+ body
1740
+ )
1741
1741
  );
1742
1742
  logger.success("Trigger key created");
1743
1743
  logger.warn("Save the `key` value now. It will only be shown once.");
@@ -1745,12 +1745,18 @@ keysCommand.command("create").description("Create a new trigger key for a job").
1745
1745
  });
1746
1746
  keysCommand.command("delete").description("Revoke a trigger key").argument("<jobId>", "Job ID").argument("<keyId>", "Key ID").action(async (jobId, keyId) => {
1747
1747
  const client = createAuthenticatedClient();
1748
- await client.delete(`/api/jobs/${jobId}/api-keys/${keyId}`);
1748
+ await withSpinner(
1749
+ "Revoking trigger key",
1750
+ () => client.delete(`/api/jobs/${jobId}/api-keys/${keyId}`)
1751
+ );
1749
1752
  logger.success(`Trigger key ${keyId} revoked`);
1750
1753
  });
1751
1754
  jobCommand.command("get <id>").description("Get job details").action(async (id) => {
1752
1755
  const client = createAuthenticatedClient();
1753
- const { data } = await client.get(`/api/jobs/${id}`);
1756
+ const { data } = await withSpinner(
1757
+ "Fetching job details",
1758
+ () => client.get(`/api/jobs/${id}`)
1759
+ );
1754
1760
  outputDetail(data);
1755
1761
  });
1756
1762
  jobCommand.command("create").description("Create a new job").requiredOption("--name <name>", "Job name").option("--description <description>", "Job description", "").option("--type <type>", "Job type (playwright, k6)").option("--schedule <cron>", "Cron schedule expression").option("--timeout <seconds>", "Timeout in seconds", "300").option("--retries <count>", "Retry count on failure", "0").action(async (options) => {
@@ -1765,7 +1771,10 @@ jobCommand.command("create").description("Create a new job").requiredOption("--n
1765
1771
  };
1766
1772
  if (options.type) body.jobType = options.type;
1767
1773
  if (options.schedule) body.cronSchedule = options.schedule;
1768
- const { data } = await client.post("/api/jobs", body);
1774
+ const { data } = await withSpinner(
1775
+ "Creating job",
1776
+ () => client.post("/api/jobs", body)
1777
+ );
1769
1778
  logger.success(`Job "${options.name}" created (${data.id})`);
1770
1779
  outputDetail(data);
1771
1780
  });
@@ -1782,7 +1791,10 @@ jobCommand.command("update <id>").description("Update job configuration").option
1782
1791
  logger.warn("No fields to update. Use --name, --description, --schedule, --timeout, --retries, or --status.");
1783
1792
  return;
1784
1793
  }
1785
- const { data } = await client.patch(`/api/jobs/${id}`, body);
1794
+ const { data } = await withSpinner(
1795
+ "Updating job",
1796
+ () => client.patch(`/api/jobs/${id}`, body)
1797
+ );
1786
1798
  logger.success(`Job ${id} updated`);
1787
1799
  outputDetail(data);
1788
1800
  });
@@ -1796,13 +1808,19 @@ jobCommand.command("delete <id>").description("Delete a job").option("--force",
1796
1808
  }
1797
1809
  }
1798
1810
  const client = createAuthenticatedClient();
1799
- await client.delete(`/api/jobs/${id}`);
1811
+ await withSpinner(
1812
+ "Deleting job",
1813
+ () => client.delete(`/api/jobs/${id}`)
1814
+ );
1800
1815
  logger.success(`Job ${id} deleted`);
1801
1816
  });
1802
1817
  jobCommand.command("run").description("Run a job immediately").requiredOption("--id <id>", "Job ID to run").action(async (options) => {
1803
1818
  const client = createAuthenticatedClient();
1804
- const { data: jobData } = await client.get(
1805
- `/api/jobs/${options.id}`
1819
+ const { data: jobData } = await withSpinner(
1820
+ "Fetching job details",
1821
+ () => client.get(
1822
+ `/api/jobs/${options.id}`
1823
+ )
1806
1824
  );
1807
1825
  const tests = Array.isArray(jobData.tests) ? jobData.tests : [];
1808
1826
  if (tests.length === 0) {
@@ -1815,10 +1833,12 @@ jobCommand.command("run").description("Run a job immediately").requiredOption("-
1815
1833
  id: test.id,
1816
1834
  name: test.name ?? test.title ?? ""
1817
1835
  }));
1818
- logger.info(`Running job ${options.id}...`);
1819
- const { data } = await client.post(
1820
- "/api/jobs/run",
1821
- { jobId: options.id, tests: payloadTests, trigger: "manual" }
1836
+ const { data } = await withSpinner(
1837
+ "Running job",
1838
+ () => client.post(
1839
+ "/api/jobs/run",
1840
+ { jobId: options.id, tests: payloadTests, trigger: "manual" }
1841
+ )
1822
1842
  );
1823
1843
  logger.success(`Job started. Run ID: ${data.runId}`);
1824
1844
  outputDetail(data);
@@ -1828,9 +1848,11 @@ jobCommand.command("trigger <id>").description("Trigger a job run").option("--wa
1828
1848
  token: requireTriggerKey(),
1829
1849
  baseUrl: getStoredBaseUrl() ?? void 0
1830
1850
  });
1831
- logger.info(`Triggering job ${id}...`);
1832
- const { data } = await triggerClient.post(
1833
- `/api/jobs/${id}/trigger`
1851
+ const { data } = await withSpinner(
1852
+ "Triggering job",
1853
+ () => triggerClient.post(
1854
+ `/api/jobs/${id}/trigger`
1855
+ )
1834
1856
  );
1835
1857
  logger.success(`Job triggered. Run ID: ${data.runId}`);
1836
1858
  if (options.wait) {
@@ -1919,17 +1941,19 @@ function getProxyEnv(url) {
1919
1941
  return null;
1920
1942
  }
1921
1943
  var runCommand = new Command6("run").description("Manage runs");
1922
- runCommand.command("list").description("List runs").option("--page <page>", "Page number", "1").option("--limit <limit>", "Items per page", "50").option("--job <jobId>", "Filter by job ID").option("--status <status>", "Filter by status (queued, running, passed, failed, error, blocked)").action(async (options) => {
1944
+ runCommand.command("list").description("List runs").option("--page <page>", "Page number", "1").option("--limit <limit>", "Items per page", "50").option("--status <status>", "Filter by status").action(async (options) => {
1923
1945
  const client = createAuthenticatedClient();
1924
1946
  const params = {
1925
1947
  page: options.page,
1926
1948
  limit: options.limit
1927
1949
  };
1928
- if (options.job) params.jobId = options.job;
1929
1950
  if (options.status) params.status = options.status;
1930
- const { data } = await client.get(
1931
- "/api/runs",
1932
- params
1951
+ const { data } = await withSpinner(
1952
+ "Fetching runs",
1953
+ () => client.get(
1954
+ "/api/runs",
1955
+ params
1956
+ )
1933
1957
  );
1934
1958
  output(data.data, {
1935
1959
  columns: [
@@ -1937,7 +1961,6 @@ runCommand.command("list").description("List runs").option("--page <page>", "Pag
1937
1961
  { key: "jobName", header: "Job" },
1938
1962
  { key: "status", header: "Status" },
1939
1963
  { key: "trigger", header: "Trigger" },
1940
- { key: "duration", header: "Duration" },
1941
1964
  { key: "startedAt", header: "Started" }
1942
1965
  ]
1943
1966
  });
@@ -1950,22 +1973,18 @@ Page ${data.pagination.page}/${data.pagination.totalPages} (${data.pagination.to
1950
1973
  });
1951
1974
  runCommand.command("get <id>").description("Get run details").action(async (id) => {
1952
1975
  const client = createAuthenticatedClient();
1953
- const { data } = await client.get(`/api/runs/${id}`);
1954
- outputDetail(data);
1955
- });
1956
- runCommand.command("permissions <id>").description("Get run access permissions").action(async (id) => {
1957
- const client = createAuthenticatedClient();
1958
- const { data } = await client.get(`/api/runs/${id}/permissions`);
1976
+ const { data } = await withSpinner(
1977
+ "Fetching run details",
1978
+ () => client.get(`/api/runs/${id}`)
1979
+ );
1959
1980
  outputDetail(data);
1960
1981
  });
1961
- runCommand.command("cancel <id>").description("Cancel a running execution").action(async (id) => {
1962
- const client = createAuthenticatedClient();
1963
- await client.post(`/api/runs/${id}/cancel`);
1964
- logger.success(`Run ${id} cancelled`);
1965
- });
1966
1982
  runCommand.command("status <id>").description("Get run status").action(async (id) => {
1967
1983
  const client = createAuthenticatedClient();
1968
- const { data } = await client.get(`/api/runs/${id}/status`);
1984
+ const { data } = await withSpinner(
1985
+ "Fetching run status",
1986
+ () => client.get(`/api/runs/${id}/status`)
1987
+ );
1969
1988
  outputDetail(data);
1970
1989
  });
1971
1990
  runCommand.command("stream <id>").description("Stream live console output from a run").option("--idle-timeout <seconds>", "Abort if no data received within this period", "60").action(async (id, options) => {
@@ -2030,18 +2049,13 @@ runCommand.command("stream <id>").description("Stream live console output from a
2030
2049
  process.stdout.write(parsed.line + "\n");
2031
2050
  }
2032
2051
  break;
2052
+ case "status":
2053
+ logger.info(`Status: ${parsed.status ?? JSON.stringify(parsed)}`);
2054
+ break;
2033
2055
  case "complete":
2034
- logger.newline();
2035
- if (parsed.status === "completed" || parsed.status === "success") {
2036
- logger.success(`Run ${id} ${parsed.status}`);
2037
- } else {
2038
- logger.warn(`Run ${id} finished with status: ${parsed.status}`);
2039
- }
2056
+ logger.success(`Run complete: ${parsed.status ?? "done"}`);
2040
2057
  if (idleTimer) clearTimeout(idleTimer);
2041
2058
  return;
2042
- case "ready":
2043
- logger.debug(`Stream ready for run ${parsed.runId ?? id}`);
2044
- break;
2045
2059
  case "error":
2046
2060
  logger.error(`Stream error: ${parsed.message ?? JSON.stringify(parsed)}`);
2047
2061
  break;
@@ -2144,9 +2158,12 @@ testCommand.command("list").description("List all tests").option("--page <page>"
2144
2158
  };
2145
2159
  if (options.search) params.search = options.search;
2146
2160
  if (options.type) params.type = normalizeTestType(options.type);
2147
- const { data } = await client.get(
2148
- "/api/tests",
2149
- params
2161
+ const { data } = await withSpinner(
2162
+ "Fetching tests",
2163
+ () => client.get(
2164
+ "/api/tests",
2165
+ params
2166
+ )
2150
2167
  );
2151
2168
  output(data.data, {
2152
2169
  columns: [
@@ -2167,7 +2184,10 @@ testCommand.command("get <id>").description("Get test details").option("--includ
2167
2184
  const client = createAuthenticatedClient();
2168
2185
  const params = {};
2169
2186
  if (options.includeScript) params.includeScript = "true";
2170
- const { data } = await client.get(`/api/tests/${id}`, params);
2187
+ const { data } = await withSpinner(
2188
+ "Fetching test details",
2189
+ () => client.get(`/api/tests/${id}`, params)
2190
+ );
2171
2191
  outputDetail(data);
2172
2192
  });
2173
2193
  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) => {
@@ -2188,7 +2208,10 @@ testCommand.command("create").description("Create a new test").requiredOption("-
2188
2208
  type: normalizeTestType(options.type)
2189
2209
  };
2190
2210
  if (options.description) body.description = options.description;
2191
- const { data } = await client.post("/api/tests", body);
2211
+ const { data } = await withSpinner(
2212
+ "Creating test",
2213
+ () => client.post("/api/tests", body)
2214
+ );
2192
2215
  const createdId = data.test?.id ?? data.id;
2193
2216
  logger.success(`Test "${options.title}" created${createdId ? ` (${createdId})` : ""}`);
2194
2217
  outputDetail(data);
@@ -2213,7 +2236,10 @@ testCommand.command("update <id>").description("Update a test").option("--title
2213
2236
  logger.warn("No fields to update. Use --title, --file, or --description.");
2214
2237
  return;
2215
2238
  }
2216
- const { data } = await client.patch(`/api/tests/${id}`, body);
2239
+ const { data } = await withSpinner(
2240
+ "Updating test",
2241
+ () => client.patch(`/api/tests/${id}`, body)
2242
+ );
2217
2243
  logger.success(`Test ${id} updated`);
2218
2244
  outputDetail(data);
2219
2245
  });
@@ -2227,19 +2253,28 @@ testCommand.command("delete <id>").description("Delete a test").option("--force"
2227
2253
  }
2228
2254
  }
2229
2255
  const client = createAuthenticatedClient();
2230
- await client.delete(`/api/tests/${id}`);
2256
+ await withSpinner(
2257
+ "Deleting test",
2258
+ () => client.delete(`/api/tests/${id}`)
2259
+ );
2231
2260
  logger.success(`Test ${id} deleted`);
2232
2261
  });
2233
2262
  testCommand.command("execute <id>").description("Execute a test immediately").option("--location <location>", "Execution location (k6 only)").action(async (id, options) => {
2234
2263
  const client = createAuthenticatedClient();
2235
2264
  const body = {};
2236
2265
  if (options.location) body.location = options.location;
2237
- const { data } = await client.post(`/api/tests/${id}/execute`, body);
2266
+ const { data } = await withSpinner(
2267
+ "Executing test",
2268
+ () => client.post(`/api/tests/${id}/execute`, body)
2269
+ );
2238
2270
  outputDetail(data);
2239
2271
  });
2240
2272
  testCommand.command("tags <id>").description("Get test tags").action(async (id) => {
2241
2273
  const client = createAuthenticatedClient();
2242
- const { data } = await client.get(`/api/tests/${id}/tags`);
2274
+ const { data } = await withSpinner(
2275
+ "Fetching test tags",
2276
+ () => client.get(`/api/tests/${id}/tags`)
2277
+ );
2243
2278
  output(data, {
2244
2279
  columns: [
2245
2280
  { key: "id", header: "ID" },
@@ -2259,9 +2294,12 @@ testCommand.command("validate").description("Validate a test script").requiredOp
2259
2294
  throw new CLIError(`Cannot read file: ${filePath}`, 1 /* GeneralError */);
2260
2295
  }
2261
2296
  const client = createAuthenticatedClient();
2262
- const { data } = await client.post(
2263
- "/api/validate-script",
2264
- { script, testType: normalizeTestType(options.type) }
2297
+ const { data } = await withSpinner(
2298
+ "Validating script",
2299
+ () => client.post(
2300
+ "/api/validate-script",
2301
+ { script, testType: normalizeTestType(options.type) }
2302
+ )
2265
2303
  );
2266
2304
  if (data.valid) {
2267
2305
  logger.success(`Script is valid (${options.type})`);
@@ -2353,9 +2391,12 @@ import { Command as Command8 } from "commander";
2353
2391
  var monitorCommand = new Command8("monitor").description("Manage monitors");
2354
2392
  monitorCommand.command("list").description("List all monitors").option("--page <page>", "Page number", "1").option("--limit <limit>", "Items per page", "50").action(async (options) => {
2355
2393
  const client = createAuthenticatedClient();
2356
- const { data } = await client.get(
2357
- "/api/monitors",
2358
- { page: options.page, limit: options.limit }
2394
+ const { data } = await withSpinner(
2395
+ "Fetching monitors",
2396
+ () => client.get(
2397
+ "/api/monitors",
2398
+ { page: options.page, limit: options.limit }
2399
+ )
2359
2400
  );
2360
2401
  output(data.data, {
2361
2402
  columns: [
@@ -2375,14 +2416,20 @@ Page ${data.pagination.page}/${data.pagination.totalPages} (${data.pagination.to
2375
2416
  });
2376
2417
  monitorCommand.command("get <id>").description("Get monitor details").action(async (id) => {
2377
2418
  const client = createAuthenticatedClient();
2378
- const { data } = await client.get(`/api/monitors/${id}`);
2419
+ const { data } = await withSpinner(
2420
+ "Fetching monitor details",
2421
+ () => client.get(`/api/monitors/${id}`)
2422
+ );
2379
2423
  outputDetail(data);
2380
2424
  });
2381
2425
  monitorCommand.command("results <id>").description("Get monitor check results").option("--limit <limit>", "Number of results", "20").action(async (id, options) => {
2382
2426
  const client = createAuthenticatedClient();
2383
- const { data } = await client.get(
2384
- `/api/monitors/${id}/results`,
2385
- { limit: options.limit }
2427
+ const { data } = await withSpinner(
2428
+ "Fetching monitor results",
2429
+ () => client.get(
2430
+ `/api/monitors/${id}/results`,
2431
+ { limit: options.limit }
2432
+ )
2386
2433
  );
2387
2434
  output(data.results, {
2388
2435
  columns: [
@@ -2395,12 +2442,18 @@ monitorCommand.command("results <id>").description("Get monitor check results").
2395
2442
  });
2396
2443
  monitorCommand.command("stats <id>").description("Get monitor performance statistics").action(async (id) => {
2397
2444
  const client = createAuthenticatedClient();
2398
- const { data } = await client.get(`/api/monitors/${id}/stats`);
2445
+ const { data } = await withSpinner(
2446
+ "Fetching monitor statistics",
2447
+ () => client.get(`/api/monitors/${id}/stats`)
2448
+ );
2399
2449
  outputDetail(data);
2400
2450
  });
2401
2451
  monitorCommand.command("status <id>").description("Get current monitor status").action(async (id) => {
2402
2452
  const client = createAuthenticatedClient();
2403
- const { data } = await client.get(`/api/monitors/${id}`);
2453
+ const { data } = await withSpinner(
2454
+ "Fetching monitor status",
2455
+ () => client.get(`/api/monitors/${id}`)
2456
+ );
2404
2457
  const statusInfo = {
2405
2458
  id: data.id,
2406
2459
  name: data.name,
@@ -2425,7 +2478,10 @@ monitorCommand.command("create").description("Create a new monitor").requiredOpt
2425
2478
  method: options.method
2426
2479
  }
2427
2480
  };
2428
- const { data } = await client.post("/api/monitors", body);
2481
+ const { data } = await withSpinner(
2482
+ "Creating monitor",
2483
+ () => client.post("/api/monitors", body)
2484
+ );
2429
2485
  logger.success(`Monitor "${options.name}" created (${data.id})`);
2430
2486
  outputDetail(data);
2431
2487
  });
@@ -2447,7 +2503,10 @@ monitorCommand.command("update <id>").description("Update a monitor").option("--
2447
2503
  logger.warn("No fields to update. Use --name, --url, --interval, --timeout, --method, or --active.");
2448
2504
  return;
2449
2505
  }
2450
- const { data } = await client.patch(`/api/monitors/${id}`, body);
2506
+ const { data } = await withSpinner(
2507
+ "Updating monitor",
2508
+ () => client.patch(`/api/monitors/${id}`, body)
2509
+ );
2451
2510
  logger.success(`Monitor ${id} updated`);
2452
2511
  outputDetail(data);
2453
2512
  });
@@ -2461,7 +2520,10 @@ monitorCommand.command("delete <id>").description("Delete a monitor").option("--
2461
2520
  }
2462
2521
  }
2463
2522
  const client = createAuthenticatedClient();
2464
- await client.delete(`/api/monitors/${id}`);
2523
+ await withSpinner(
2524
+ "Deleting monitor",
2525
+ () => client.delete(`/api/monitors/${id}`)
2526
+ );
2465
2527
  logger.success(`Monitor ${id} deleted`);
2466
2528
  });
2467
2529
 
@@ -2470,7 +2532,10 @@ import { Command as Command9 } from "commander";
2470
2532
  var varCommand = new Command9("var").description("Manage project variables");
2471
2533
  varCommand.command("list").description("List all variables").action(async () => {
2472
2534
  const client = createAuthenticatedClient();
2473
- const { data } = await client.get("/api/variables");
2535
+ const { data } = await withSpinner(
2536
+ "Fetching variables",
2537
+ () => client.get("/api/variables")
2538
+ );
2474
2539
  output(data, {
2475
2540
  columns: [
2476
2541
  { key: "id", header: "ID" },
@@ -2482,7 +2547,10 @@ varCommand.command("list").description("List all variables").action(async () =>
2482
2547
  });
2483
2548
  varCommand.command("get <key>").description("Get a variable by key name").action(async (key) => {
2484
2549
  const client = createAuthenticatedClient();
2485
- const { data: variables } = await client.get("/api/variables");
2550
+ const { data: variables } = await withSpinner(
2551
+ "Fetching variable",
2552
+ () => client.get("/api/variables")
2553
+ );
2486
2554
  const variable = variables.find((v) => v.key === key);
2487
2555
  if (!variable) {
2488
2556
  throw new CLIError(`Variable "${key}" not found`, 1 /* GeneralError */);
@@ -2491,29 +2559,41 @@ varCommand.command("get <key>").description("Get a variable by key name").action
2491
2559
  });
2492
2560
  varCommand.command("set <key> <value>").description("Create or update a variable").option("--secret", "Mark as secret (value will be encrypted)").option("--description <description>", "Variable description").action(async (key, value, options) => {
2493
2561
  const client = createAuthenticatedClient();
2494
- const { data: variables } = await client.get("/api/variables");
2562
+ const { data: variables } = await withSpinner(
2563
+ "Checking existing variables",
2564
+ () => client.get("/api/variables")
2565
+ );
2495
2566
  const existing = variables.find((v) => v.key === key);
2496
2567
  if (existing) {
2497
- await client.put(`/api/variables/${existing.id}`, {
2498
- key,
2499
- value,
2500
- isSecret: options.secret ?? existing.isSecret,
2501
- ...options.description !== void 0 && { description: options.description }
2502
- });
2568
+ await withSpinner(
2569
+ "Updating variable",
2570
+ () => client.put(`/api/variables/${existing.id}`, {
2571
+ key,
2572
+ value,
2573
+ isSecret: options.secret ?? existing.isSecret,
2574
+ ...options.description !== void 0 && { description: options.description }
2575
+ })
2576
+ );
2503
2577
  logger.success(`Variable "${key}" updated`);
2504
2578
  } else {
2505
- await client.post("/api/variables", {
2506
- key,
2507
- value,
2508
- isSecret: options.secret ?? false,
2509
- ...options.description !== void 0 && { description: options.description }
2510
- });
2579
+ await withSpinner(
2580
+ "Creating variable",
2581
+ () => client.post("/api/variables", {
2582
+ key,
2583
+ value,
2584
+ isSecret: options.secret ?? false,
2585
+ ...options.description !== void 0 && { description: options.description }
2586
+ })
2587
+ );
2511
2588
  logger.success(`Variable "${key}" created`);
2512
2589
  }
2513
2590
  });
2514
2591
  varCommand.command("delete <key>").description("Delete a variable").option("--force", "Skip confirmation").action(async (key, options) => {
2515
2592
  const client = createAuthenticatedClient();
2516
- const { data: variables } = await client.get("/api/variables");
2593
+ const { data: variables } = await withSpinner(
2594
+ "Fetching variables",
2595
+ () => client.get("/api/variables")
2596
+ );
2517
2597
  const variable = variables.find((v) => v.key === key);
2518
2598
  if (!variable) {
2519
2599
  throw new CLIError(`Variable "${key}" not found`, 1 /* GeneralError */);
@@ -2526,7 +2606,10 @@ varCommand.command("delete <key>").description("Delete a variable").option("--fo
2526
2606
  return;
2527
2607
  }
2528
2608
  }
2529
- await client.delete(`/api/variables/${variable.id}`);
2609
+ await withSpinner(
2610
+ "Deleting variable",
2611
+ () => client.delete(`/api/variables/${variable.id}`)
2612
+ );
2530
2613
  logger.success(`Variable "${key}" deleted`);
2531
2614
  });
2532
2615
 
@@ -2535,7 +2618,10 @@ import { Command as Command10 } from "commander";
2535
2618
  var tagCommand = new Command10("tag").description("Manage tags");
2536
2619
  tagCommand.command("list").description("List all tags").action(async () => {
2537
2620
  const client = createAuthenticatedClient();
2538
- const { data } = await client.get("/api/tags");
2621
+ const { data } = await withSpinner(
2622
+ "Fetching tags",
2623
+ () => client.get("/api/tags")
2624
+ );
2539
2625
  output(data, {
2540
2626
  columns: [
2541
2627
  { key: "id", header: "ID" },
@@ -2546,10 +2632,13 @@ tagCommand.command("list").description("List all tags").action(async () => {
2546
2632
  });
2547
2633
  tagCommand.command("create <name>").description("Create a new tag").option("--color <color>", "Tag color (hex)").action(async (name, options) => {
2548
2634
  const client = createAuthenticatedClient();
2549
- const { data } = await client.post("/api/tags", {
2550
- name,
2551
- color: options.color
2552
- });
2635
+ const { data } = await withSpinner(
2636
+ "Creating tag",
2637
+ () => client.post("/api/tags", {
2638
+ name,
2639
+ color: options.color
2640
+ })
2641
+ );
2553
2642
  logger.success(`Tag "${name}" created (${data.id})`);
2554
2643
  });
2555
2644
  tagCommand.command("delete <id>").description("Delete a tag").option("--force", "Skip confirmation").action(async (id, options) => {
@@ -2562,7 +2651,10 @@ tagCommand.command("delete <id>").description("Delete a tag").option("--force",
2562
2651
  }
2563
2652
  }
2564
2653
  const client = createAuthenticatedClient();
2565
- await client.delete(`/api/tags/${id}`);
2654
+ await withSpinner(
2655
+ "Deleting tag",
2656
+ () => client.delete(`/api/tags/${id}`)
2657
+ );
2566
2658
  logger.success(`Tag ${id} deleted`);
2567
2659
  });
2568
2660
 
@@ -3266,17 +3358,66 @@ function buildStatusPageDefinitions(pages) {
3266
3358
  }
3267
3359
  function generateConfigContent(opts) {
3268
3360
  const parts = [];
3361
+ parts.push("/**");
3362
+ parts.push(" * Supercheck Configuration");
3363
+ 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");
3364
+ parts.push(" *");
3365
+ parts.push(" * This file was generated by `supercheck pull`. It is the source of truth");
3366
+ parts.push(" * for your Supercheck project configuration.");
3367
+ parts.push(" *");
3368
+ parts.push(" * Getting Started:");
3369
+ parts.push(" * 1. Install dependencies:");
3370
+ parts.push(" * npm install -D @supercheck/cli typescript @types/node");
3371
+ parts.push(" * # If using Playwright tests, also install:");
3372
+ parts.push(" * npm install -D @playwright/test");
3373
+ parts.push(" * # If using k6 tests, install k6: https://grafana.com/docs/k6/latest/set-up/install-k6/");
3374
+ parts.push(" *");
3375
+ parts.push(" * 2. Review the configuration below and make any changes you need.");
3376
+ parts.push(" * 3. Preview what will change: npx supercheck diff");
3377
+ parts.push(" * 4. Deploy your changes: npx supercheck deploy");
3378
+ parts.push(" * 5. Re-sync from cloud: npx supercheck pull");
3379
+ parts.push(" *");
3380
+ parts.push(" * CLI Commands:");
3381
+ parts.push(" * supercheck pull Pull remote config & test scripts to local");
3382
+ parts.push(" * supercheck diff Compare local config vs remote");
3383
+ parts.push(" * supercheck deploy Deploy local config to the cloud");
3384
+ parts.push(" * supercheck destroy Remove all managed resources from the cloud");
3385
+ parts.push(" *");
3386
+ parts.push(" * supercheck test list List all tests");
3387
+ parts.push(" * supercheck test validate Validate a local test script");
3388
+ parts.push(" * supercheck test execute <id> Execute a test immediately");
3389
+ parts.push(" *");
3390
+ parts.push(" * supercheck monitor list List all monitors");
3391
+ parts.push(" * supercheck job list List all jobs");
3392
+ parts.push(" * supercheck job run --id <id> Run a job immediately");
3393
+ parts.push(" *");
3394
+ parts.push(" * supercheck config validate Validate this config file");
3395
+ parts.push(" * supercheck health Check API connectivity");
3396
+ parts.push(" * supercheck whoami Show current authentication info");
3397
+ parts.push(" *");
3398
+ parts.push(" * CI/CD Integration:");
3399
+ parts.push(" * Set the SUPERCHECK_TOKEN environment variable in your CI/CD pipeline");
3400
+ parts.push(" * and run `supercheck deploy` to push changes automatically.");
3401
+ parts.push(" *");
3402
+ parts.push(` * Documentation: https://supercheck.io/docs/app/welcome`);
3403
+ parts.push(" */");
3269
3404
  parts.push(`import { defineConfig } from '@supercheck/cli'`);
3270
3405
  parts.push("");
3271
3406
  parts.push("export default defineConfig({");
3272
3407
  parts.push(` schemaVersion: '1.0',`);
3408
+ parts.push("");
3409
+ parts.push(" // Project identifiers \u2014 found in Dashboard > Project Settings");
3273
3410
  parts.push(" project: {");
3274
3411
  parts.push(` organization: '${opts.orgId}',`);
3275
3412
  parts.push(` project: '${opts.projectId}',`);
3276
3413
  parts.push(" },");
3414
+ parts.push("");
3415
+ parts.push(" // API connection \u2014 set SUPERCHECK_URL env var for self-hosted or staging");
3277
3416
  parts.push(" api: {");
3278
3417
  parts.push(` baseUrl: process.env.SUPERCHECK_URL ?? '${opts.baseUrl}',`);
3279
3418
  parts.push(" },");
3419
+ parts.push("");
3420
+ parts.push(" // Test file patterns \u2014 Playwright (.pw.ts) and k6 (.k6.ts) scripts");
3280
3421
  parts.push(" tests: {");
3281
3422
  parts.push(" playwright: {");
3282
3423
  parts.push(` testMatch: '_supercheck_/tests/**/*.pw.ts',`);
@@ -3286,42 +3427,33 @@ function generateConfigContent(opts) {
3286
3427
  parts.push(" },");
3287
3428
  parts.push(" },");
3288
3429
  if (opts.monitors.length > 0) {
3430
+ parts.push("");
3431
+ parts.push(" // Monitors \u2014 uptime checks, HTTP requests, synthetic tests");
3289
3432
  parts.push(` monitors: ${formatArray(opts.monitors, 2)},`);
3290
3433
  }
3291
3434
  if (opts.jobs.length > 0) {
3435
+ parts.push("");
3436
+ parts.push(" // Jobs \u2014 scheduled or triggered test execution groups");
3292
3437
  parts.push(` jobs: ${formatArray(opts.jobs, 2)},`);
3293
3438
  }
3294
3439
  if (opts.variables.length > 0) {
3440
+ parts.push("");
3441
+ parts.push(" // Variables \u2014 key-value pairs available to tests at runtime");
3442
+ parts.push(" // Secret values use process.env references \u2014 set them in your shell or CI/CD");
3295
3443
  parts.push(` variables: ${formatArray(opts.variables, 2)},`);
3296
3444
  }
3297
3445
  if (opts.tags.length > 0) {
3446
+ parts.push("");
3447
+ parts.push(" // Tags \u2014 labels for organizing tests, monitors, and jobs");
3298
3448
  parts.push(` tags: ${formatArray(opts.tags, 2)},`);
3299
3449
  }
3300
3450
  if (opts.statusPages.length > 0) {
3451
+ parts.push("");
3452
+ parts.push(" // Status Pages \u2014 public dashboards showing monitor health");
3301
3453
  parts.push(` statusPages: ${formatArray(opts.statusPages, 2)},`);
3302
3454
  }
3303
3455
  parts.push("})");
3304
3456
  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("");
3325
3457
  return parts.join("\n");
3326
3458
  }
3327
3459
  function formatArray(arr, baseIndent) {
@@ -3476,9 +3608,12 @@ var pullCommand = new Command14("pull").description("Pull tests, monitors, jobs,
3476
3608
  fullTests.push(t);
3477
3609
  } else {
3478
3610
  try {
3479
- const { data } = await client.get(
3480
- `/api/tests/${t.id}`,
3481
- { includeScript: "true" }
3611
+ const { data } = await withSpinner(
3612
+ `Fetching script for "${t.title}"`,
3613
+ () => client.get(
3614
+ `/api/tests/${t.id}`,
3615
+ { includeScript: "true" }
3616
+ )
3482
3617
  );
3483
3618
  fullTests.push(data);
3484
3619
  } catch (err) {
@@ -3564,41 +3699,12 @@ var pullCommand = new Command14("pull").description("Pull tests, monitors, jobs,
3564
3699
  }
3565
3700
  }
3566
3701
  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
- });
3583
- }
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
- }
3702
+ logger.info("Next steps:");
3703
+ logger.info(" 1. Install dependencies: npm install -D @supercheck/cli typescript");
3704
+ logger.info(" 2. Preview changes: npx supercheck diff");
3705
+ logger.info(" 3. Deploy changes: npx supercheck deploy");
3599
3706
  logger.newline();
3600
3707
  logger.info("Tip: Review the changes, then commit to version control.");
3601
- logger.info(" Run `supercheck diff` to compare local vs remote.");
3602
3708
  logger.newline();
3603
3709
  });
3604
3710
 
@@ -3607,7 +3713,10 @@ import { Command as Command15 } from "commander";
3607
3713
  var notificationCommand = new Command15("notification").alias("notifications").description("Manage notification providers");
3608
3714
  notificationCommand.command("list").description("List notification providers").action(async () => {
3609
3715
  const client = createAuthenticatedClient();
3610
- const { data } = await client.get("/api/notification-providers");
3716
+ const { data } = await withSpinner(
3717
+ "Fetching notification providers",
3718
+ () => client.get("/api/notification-providers")
3719
+ );
3611
3720
  output(data, {
3612
3721
  columns: [
3613
3722
  { key: "id", header: "ID" },
@@ -3620,7 +3729,10 @@ notificationCommand.command("list").description("List notification providers").a
3620
3729
  });
3621
3730
  notificationCommand.command("get <id>").description("Get notification provider details").action(async (id) => {
3622
3731
  const client = createAuthenticatedClient();
3623
- const { data } = await client.get(`/api/notification-providers/${id}`);
3732
+ const { data } = await withSpinner(
3733
+ "Fetching provider details",
3734
+ () => client.get(`/api/notification-providers/${id}`)
3735
+ );
3624
3736
  outputDetail(data);
3625
3737
  });
3626
3738
  notificationCommand.command("create").description("Create a notification provider").requiredOption("--type <type>", "Provider type (email, slack, webhook, telegram, discord, teams)").requiredOption("--name <name>", "Provider name").option("--config <json>", "Provider config as JSON string").action(async (options) => {
@@ -3640,11 +3752,14 @@ notificationCommand.command("create").description("Create a notification provide
3640
3752
  }
3641
3753
  }
3642
3754
  const client = createAuthenticatedClient();
3643
- const { data } = await client.post("/api/notification-providers", {
3644
- name: options.name,
3645
- type: options.type,
3646
- config: { name: options.name, ...config }
3647
- });
3755
+ const { data } = await withSpinner(
3756
+ "Creating notification provider",
3757
+ () => client.post("/api/notification-providers", {
3758
+ name: options.name,
3759
+ type: options.type,
3760
+ config: { name: options.name, ...config }
3761
+ })
3762
+ );
3648
3763
  logger.success(`Notification provider "${options.name}" created (${data.id})`);
3649
3764
  outputDetail(data);
3650
3765
  });
@@ -3654,7 +3769,10 @@ notificationCommand.command("update <id>").description("Update a notification pr
3654
3769
  logger.warn("No fields to update. Use --name, --type, or --config.");
3655
3770
  return;
3656
3771
  }
3657
- const { data: existing } = await client.get(`/api/notification-providers/${id}`);
3772
+ const { data: existing } = await withSpinner(
3773
+ "Fetching existing provider",
3774
+ () => client.get(`/api/notification-providers/${id}`)
3775
+ );
3658
3776
  let updatedConfig = existing.config ?? {};
3659
3777
  if (options.config) {
3660
3778
  try {
@@ -3671,7 +3789,10 @@ notificationCommand.command("update <id>").description("Update a notification pr
3671
3789
  type: updatedType,
3672
3790
  config: updatedConfig
3673
3791
  };
3674
- const { data } = await client.put(`/api/notification-providers/${id}`, body);
3792
+ const { data } = await withSpinner(
3793
+ "Updating notification provider",
3794
+ () => client.put(`/api/notification-providers/${id}`, body)
3795
+ );
3675
3796
  logger.success(`Notification provider ${id} updated`);
3676
3797
  outputDetail(data);
3677
3798
  });
@@ -3685,7 +3806,10 @@ notificationCommand.command("delete <id>").description("Delete a notification pr
3685
3806
  }
3686
3807
  }
3687
3808
  const client = createAuthenticatedClient();
3688
- await client.delete(`/api/notification-providers/${id}`);
3809
+ await withSpinner(
3810
+ "Deleting notification provider",
3811
+ () => client.delete(`/api/notification-providers/${id}`)
3812
+ );
3689
3813
  logger.success(`Notification provider ${id} deleted`);
3690
3814
  });
3691
3815
  notificationCommand.command("test").description("Send a test notification to verify provider configuration").requiredOption("--type <type>", "Provider type (email, slack, webhook, telegram, discord, teams)").requiredOption("--config <json>", "Provider config as JSON string").action(async (options) => {
@@ -3703,9 +3827,12 @@ notificationCommand.command("test").description("Send a test notification to ver
3703
3827
  throw new CLIError("Invalid JSON in --config", 1 /* GeneralError */);
3704
3828
  }
3705
3829
  const client = createAuthenticatedClient();
3706
- const { data } = await client.post(
3707
- "/api/notification-providers/test",
3708
- { type: options.type, config }
3830
+ const { data } = await withSpinner(
3831
+ "Sending test notification",
3832
+ () => client.post(
3833
+ "/api/notification-providers/test",
3834
+ { type: options.type, config }
3835
+ )
3709
3836
  );
3710
3837
  if (data.success) {
3711
3838
  logger.success(data.message ?? "Test notification sent successfully");
@@ -3719,7 +3846,10 @@ import { Command as Command16 } from "commander";
3719
3846
  var alertCommand = new Command16("alert").alias("alerts").description("View alert history");
3720
3847
  alertCommand.command("history").description("Get alert history").option("--page <page>", "Page number", "1").option("--limit <limit>", "Number of results per page", "50").action(async (options) => {
3721
3848
  const client = createAuthenticatedClient();
3722
- const { data } = await client.get("/api/alerts/history", { page: options.page, limit: options.limit });
3849
+ const { data } = await withSpinner(
3850
+ "Fetching alert history",
3851
+ () => client.get("/api/alerts/history", { page: options.page, limit: options.limit })
3852
+ );
3723
3853
  const items = Array.isArray(data) ? data : data.data;
3724
3854
  const pagination = Array.isArray(data) ? null : data.pagination;
3725
3855
  output(items, {
@@ -3750,7 +3880,10 @@ var auditCommand = new Command17("audit").description("View audit logs (admin)")
3750
3880
  };
3751
3881
  if (options.search) params.search = options.search;
3752
3882
  if (options.action) params.action = options.action;
3753
- const { data } = await client.get("/api/audit", params);
3883
+ const { data } = await withSpinner(
3884
+ "Fetching audit logs",
3885
+ () => client.get("/api/audit", params)
3886
+ );
3754
3887
  if (!data.success) {
3755
3888
  throw new CLIError("Failed to fetch audit logs", 4 /* ApiError */);
3756
3889
  }