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

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.6" : "0.0.0-dev";
217
217
 
218
218
  // src/api/client.ts
219
219
  import { ProxyAgent } from "undici";
@@ -1692,9 +1692,12 @@ function parseIntStrict(value, name, opts) {
1692
1692
  var jobCommand = new Command5("job").description("Manage jobs");
1693
1693
  jobCommand.command("list").description("List all jobs").option("--page <page>", "Page number", "1").option("--limit <limit>", "Items per page", "50").action(async (options) => {
1694
1694
  const client = createAuthenticatedClient();
1695
- const { data } = await client.get(
1696
- "/api/jobs",
1697
- { page: options.page, limit: options.limit }
1695
+ const { data } = await withSpinner(
1696
+ "Fetching jobs",
1697
+ () => client.get(
1698
+ "/api/jobs",
1699
+ { page: options.page, limit: options.limit }
1700
+ )
1698
1701
  );
1699
1702
  output(data.data, {
1700
1703
  columns: [
@@ -1715,8 +1718,11 @@ Page ${data.pagination.page}/${data.pagination.totalPages} (${data.pagination.to
1715
1718
  var keysCommand = jobCommand.command("keys").description("Manage job trigger keys");
1716
1719
  keysCommand.argument("<jobId>", "Job ID").action(async (jobId) => {
1717
1720
  const client = createAuthenticatedClient();
1718
- const { data } = await client.get(
1719
- `/api/jobs/${jobId}/api-keys`
1721
+ const { data } = await withSpinner(
1722
+ "Fetching trigger keys",
1723
+ () => client.get(
1724
+ `/api/jobs/${jobId}/api-keys`
1725
+ )
1720
1726
  );
1721
1727
  output(data.apiKeys ?? [], {
1722
1728
  columns: [
@@ -1735,9 +1741,12 @@ keysCommand.command("create").description("Create a new trigger key for a job").
1735
1741
  if (options.expiresIn !== void 0) {
1736
1742
  body.expiresIn = parseIntStrict(options.expiresIn, "--expires-in", { min: 60 });
1737
1743
  }
1738
- const { data } = await client.post(
1739
- `/api/jobs/${jobId}/api-keys`,
1740
- body
1744
+ const { data } = await withSpinner(
1745
+ "Creating trigger key",
1746
+ () => client.post(
1747
+ `/api/jobs/${jobId}/api-keys`,
1748
+ body
1749
+ )
1741
1750
  );
1742
1751
  logger.success("Trigger key created");
1743
1752
  logger.warn("Save the `key` value now. It will only be shown once.");
@@ -1745,12 +1754,18 @@ keysCommand.command("create").description("Create a new trigger key for a job").
1745
1754
  });
1746
1755
  keysCommand.command("delete").description("Revoke a trigger key").argument("<jobId>", "Job ID").argument("<keyId>", "Key ID").action(async (jobId, keyId) => {
1747
1756
  const client = createAuthenticatedClient();
1748
- await client.delete(`/api/jobs/${jobId}/api-keys/${keyId}`);
1757
+ await withSpinner(
1758
+ "Revoking trigger key",
1759
+ () => client.delete(`/api/jobs/${jobId}/api-keys/${keyId}`)
1760
+ );
1749
1761
  logger.success(`Trigger key ${keyId} revoked`);
1750
1762
  });
1751
1763
  jobCommand.command("get <id>").description("Get job details").action(async (id) => {
1752
1764
  const client = createAuthenticatedClient();
1753
- const { data } = await client.get(`/api/jobs/${id}`);
1765
+ const { data } = await withSpinner(
1766
+ "Fetching job details",
1767
+ () => client.get(`/api/jobs/${id}`)
1768
+ );
1754
1769
  outputDetail(data);
1755
1770
  });
1756
1771
  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 +1780,10 @@ jobCommand.command("create").description("Create a new job").requiredOption("--n
1765
1780
  };
1766
1781
  if (options.type) body.jobType = options.type;
1767
1782
  if (options.schedule) body.cronSchedule = options.schedule;
1768
- const { data } = await client.post("/api/jobs", body);
1783
+ const { data } = await withSpinner(
1784
+ "Creating job",
1785
+ () => client.post("/api/jobs", body)
1786
+ );
1769
1787
  logger.success(`Job "${options.name}" created (${data.id})`);
1770
1788
  outputDetail(data);
1771
1789
  });
@@ -1782,7 +1800,10 @@ jobCommand.command("update <id>").description("Update job configuration").option
1782
1800
  logger.warn("No fields to update. Use --name, --description, --schedule, --timeout, --retries, or --status.");
1783
1801
  return;
1784
1802
  }
1785
- const { data } = await client.patch(`/api/jobs/${id}`, body);
1803
+ const { data } = await withSpinner(
1804
+ "Updating job",
1805
+ () => client.patch(`/api/jobs/${id}`, body)
1806
+ );
1786
1807
  logger.success(`Job ${id} updated`);
1787
1808
  outputDetail(data);
1788
1809
  });
@@ -1796,13 +1817,19 @@ jobCommand.command("delete <id>").description("Delete a job").option("--force",
1796
1817
  }
1797
1818
  }
1798
1819
  const client = createAuthenticatedClient();
1799
- await client.delete(`/api/jobs/${id}`);
1820
+ await withSpinner(
1821
+ "Deleting job",
1822
+ () => client.delete(`/api/jobs/${id}`)
1823
+ );
1800
1824
  logger.success(`Job ${id} deleted`);
1801
1825
  });
1802
1826
  jobCommand.command("run").description("Run a job immediately").requiredOption("--id <id>", "Job ID to run").action(async (options) => {
1803
1827
  const client = createAuthenticatedClient();
1804
- const { data: jobData } = await client.get(
1805
- `/api/jobs/${options.id}`
1828
+ const { data: jobData } = await withSpinner(
1829
+ "Fetching job details",
1830
+ () => client.get(
1831
+ `/api/jobs/${options.id}`
1832
+ )
1806
1833
  );
1807
1834
  const tests = Array.isArray(jobData.tests) ? jobData.tests : [];
1808
1835
  if (tests.length === 0) {
@@ -1815,10 +1842,12 @@ jobCommand.command("run").description("Run a job immediately").requiredOption("-
1815
1842
  id: test.id,
1816
1843
  name: test.name ?? test.title ?? ""
1817
1844
  }));
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" }
1845
+ const { data } = await withSpinner(
1846
+ "Running job",
1847
+ () => client.post(
1848
+ "/api/jobs/run",
1849
+ { jobId: options.id, tests: payloadTests, trigger: "manual" }
1850
+ )
1822
1851
  );
1823
1852
  logger.success(`Job started. Run ID: ${data.runId}`);
1824
1853
  outputDetail(data);
@@ -1828,9 +1857,11 @@ jobCommand.command("trigger <id>").description("Trigger a job run").option("--wa
1828
1857
  token: requireTriggerKey(),
1829
1858
  baseUrl: getStoredBaseUrl() ?? void 0
1830
1859
  });
1831
- logger.info(`Triggering job ${id}...`);
1832
- const { data } = await triggerClient.post(
1833
- `/api/jobs/${id}/trigger`
1860
+ const { data } = await withSpinner(
1861
+ "Triggering job",
1862
+ () => triggerClient.post(
1863
+ `/api/jobs/${id}/trigger`
1864
+ )
1834
1865
  );
1835
1866
  logger.success(`Job triggered. Run ID: ${data.runId}`);
1836
1867
  if (options.wait) {
@@ -1919,17 +1950,19 @@ function getProxyEnv(url) {
1919
1950
  return null;
1920
1951
  }
1921
1952
  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) => {
1953
+ 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
1954
  const client = createAuthenticatedClient();
1924
1955
  const params = {
1925
1956
  page: options.page,
1926
1957
  limit: options.limit
1927
1958
  };
1928
- if (options.job) params.jobId = options.job;
1929
1959
  if (options.status) params.status = options.status;
1930
- const { data } = await client.get(
1931
- "/api/runs",
1932
- params
1960
+ const { data } = await withSpinner(
1961
+ "Fetching runs",
1962
+ () => client.get(
1963
+ "/api/runs",
1964
+ params
1965
+ )
1933
1966
  );
1934
1967
  output(data.data, {
1935
1968
  columns: [
@@ -1937,7 +1970,6 @@ runCommand.command("list").description("List runs").option("--page <page>", "Pag
1937
1970
  { key: "jobName", header: "Job" },
1938
1971
  { key: "status", header: "Status" },
1939
1972
  { key: "trigger", header: "Trigger" },
1940
- { key: "duration", header: "Duration" },
1941
1973
  { key: "startedAt", header: "Started" }
1942
1974
  ]
1943
1975
  });
@@ -1950,22 +1982,18 @@ Page ${data.pagination.page}/${data.pagination.totalPages} (${data.pagination.to
1950
1982
  });
1951
1983
  runCommand.command("get <id>").description("Get run details").action(async (id) => {
1952
1984
  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`);
1985
+ const { data } = await withSpinner(
1986
+ "Fetching run details",
1987
+ () => client.get(`/api/runs/${id}`)
1988
+ );
1959
1989
  outputDetail(data);
1960
1990
  });
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
1991
  runCommand.command("status <id>").description("Get run status").action(async (id) => {
1967
1992
  const client = createAuthenticatedClient();
1968
- const { data } = await client.get(`/api/runs/${id}/status`);
1993
+ const { data } = await withSpinner(
1994
+ "Fetching run status",
1995
+ () => client.get(`/api/runs/${id}/status`)
1996
+ );
1969
1997
  outputDetail(data);
1970
1998
  });
1971
1999
  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 +2058,13 @@ runCommand.command("stream <id>").description("Stream live console output from a
2030
2058
  process.stdout.write(parsed.line + "\n");
2031
2059
  }
2032
2060
  break;
2061
+ case "status":
2062
+ logger.info(`Status: ${parsed.status ?? JSON.stringify(parsed)}`);
2063
+ break;
2033
2064
  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
- }
2065
+ logger.success(`Run complete: ${parsed.status ?? "done"}`);
2040
2066
  if (idleTimer) clearTimeout(idleTimer);
2041
2067
  return;
2042
- case "ready":
2043
- logger.debug(`Stream ready for run ${parsed.runId ?? id}`);
2044
- break;
2045
2068
  case "error":
2046
2069
  logger.error(`Stream error: ${parsed.message ?? JSON.stringify(parsed)}`);
2047
2070
  break;
@@ -2144,9 +2167,12 @@ testCommand.command("list").description("List all tests").option("--page <page>"
2144
2167
  };
2145
2168
  if (options.search) params.search = options.search;
2146
2169
  if (options.type) params.type = normalizeTestType(options.type);
2147
- const { data } = await client.get(
2148
- "/api/tests",
2149
- params
2170
+ const { data } = await withSpinner(
2171
+ "Fetching tests",
2172
+ () => client.get(
2173
+ "/api/tests",
2174
+ params
2175
+ )
2150
2176
  );
2151
2177
  output(data.data, {
2152
2178
  columns: [
@@ -2167,7 +2193,10 @@ testCommand.command("get <id>").description("Get test details").option("--includ
2167
2193
  const client = createAuthenticatedClient();
2168
2194
  const params = {};
2169
2195
  if (options.includeScript) params.includeScript = "true";
2170
- const { data } = await client.get(`/api/tests/${id}`, params);
2196
+ const { data } = await withSpinner(
2197
+ "Fetching test details",
2198
+ () => client.get(`/api/tests/${id}`, params)
2199
+ );
2171
2200
  outputDetail(data);
2172
2201
  });
2173
2202
  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 +2217,10 @@ testCommand.command("create").description("Create a new test").requiredOption("-
2188
2217
  type: normalizeTestType(options.type)
2189
2218
  };
2190
2219
  if (options.description) body.description = options.description;
2191
- const { data } = await client.post("/api/tests", body);
2220
+ const { data } = await withSpinner(
2221
+ "Creating test",
2222
+ () => client.post("/api/tests", body)
2223
+ );
2192
2224
  const createdId = data.test?.id ?? data.id;
2193
2225
  logger.success(`Test "${options.title}" created${createdId ? ` (${createdId})` : ""}`);
2194
2226
  outputDetail(data);
@@ -2213,7 +2245,10 @@ testCommand.command("update <id>").description("Update a test").option("--title
2213
2245
  logger.warn("No fields to update. Use --title, --file, or --description.");
2214
2246
  return;
2215
2247
  }
2216
- const { data } = await client.patch(`/api/tests/${id}`, body);
2248
+ const { data } = await withSpinner(
2249
+ "Updating test",
2250
+ () => client.patch(`/api/tests/${id}`, body)
2251
+ );
2217
2252
  logger.success(`Test ${id} updated`);
2218
2253
  outputDetail(data);
2219
2254
  });
@@ -2227,19 +2262,28 @@ testCommand.command("delete <id>").description("Delete a test").option("--force"
2227
2262
  }
2228
2263
  }
2229
2264
  const client = createAuthenticatedClient();
2230
- await client.delete(`/api/tests/${id}`);
2265
+ await withSpinner(
2266
+ "Deleting test",
2267
+ () => client.delete(`/api/tests/${id}`)
2268
+ );
2231
2269
  logger.success(`Test ${id} deleted`);
2232
2270
  });
2233
2271
  testCommand.command("execute <id>").description("Execute a test immediately").option("--location <location>", "Execution location (k6 only)").action(async (id, options) => {
2234
2272
  const client = createAuthenticatedClient();
2235
2273
  const body = {};
2236
2274
  if (options.location) body.location = options.location;
2237
- const { data } = await client.post(`/api/tests/${id}/execute`, body);
2275
+ const { data } = await withSpinner(
2276
+ "Executing test",
2277
+ () => client.post(`/api/tests/${id}/execute`, body)
2278
+ );
2238
2279
  outputDetail(data);
2239
2280
  });
2240
2281
  testCommand.command("tags <id>").description("Get test tags").action(async (id) => {
2241
2282
  const client = createAuthenticatedClient();
2242
- const { data } = await client.get(`/api/tests/${id}/tags`);
2283
+ const { data } = await withSpinner(
2284
+ "Fetching test tags",
2285
+ () => client.get(`/api/tests/${id}/tags`)
2286
+ );
2243
2287
  output(data, {
2244
2288
  columns: [
2245
2289
  { key: "id", header: "ID" },
@@ -2259,9 +2303,12 @@ testCommand.command("validate").description("Validate a test script").requiredOp
2259
2303
  throw new CLIError(`Cannot read file: ${filePath}`, 1 /* GeneralError */);
2260
2304
  }
2261
2305
  const client = createAuthenticatedClient();
2262
- const { data } = await client.post(
2263
- "/api/validate-script",
2264
- { script, testType: normalizeTestType(options.type) }
2306
+ const { data } = await withSpinner(
2307
+ "Validating script",
2308
+ () => client.post(
2309
+ "/api/validate-script",
2310
+ { script, testType: normalizeTestType(options.type) }
2311
+ )
2265
2312
  );
2266
2313
  if (data.valid) {
2267
2314
  logger.success(`Script is valid (${options.type})`);
@@ -2353,9 +2400,12 @@ import { Command as Command8 } from "commander";
2353
2400
  var monitorCommand = new Command8("monitor").description("Manage monitors");
2354
2401
  monitorCommand.command("list").description("List all monitors").option("--page <page>", "Page number", "1").option("--limit <limit>", "Items per page", "50").action(async (options) => {
2355
2402
  const client = createAuthenticatedClient();
2356
- const { data } = await client.get(
2357
- "/api/monitors",
2358
- { page: options.page, limit: options.limit }
2403
+ const { data } = await withSpinner(
2404
+ "Fetching monitors",
2405
+ () => client.get(
2406
+ "/api/monitors",
2407
+ { page: options.page, limit: options.limit }
2408
+ )
2359
2409
  );
2360
2410
  output(data.data, {
2361
2411
  columns: [
@@ -2375,14 +2425,20 @@ Page ${data.pagination.page}/${data.pagination.totalPages} (${data.pagination.to
2375
2425
  });
2376
2426
  monitorCommand.command("get <id>").description("Get monitor details").action(async (id) => {
2377
2427
  const client = createAuthenticatedClient();
2378
- const { data } = await client.get(`/api/monitors/${id}`);
2428
+ const { data } = await withSpinner(
2429
+ "Fetching monitor details",
2430
+ () => client.get(`/api/monitors/${id}`)
2431
+ );
2379
2432
  outputDetail(data);
2380
2433
  });
2381
2434
  monitorCommand.command("results <id>").description("Get monitor check results").option("--limit <limit>", "Number of results", "20").action(async (id, options) => {
2382
2435
  const client = createAuthenticatedClient();
2383
- const { data } = await client.get(
2384
- `/api/monitors/${id}/results`,
2385
- { limit: options.limit }
2436
+ const { data } = await withSpinner(
2437
+ "Fetching monitor results",
2438
+ () => client.get(
2439
+ `/api/monitors/${id}/results`,
2440
+ { limit: options.limit }
2441
+ )
2386
2442
  );
2387
2443
  output(data.results, {
2388
2444
  columns: [
@@ -2395,12 +2451,18 @@ monitorCommand.command("results <id>").description("Get monitor check results").
2395
2451
  });
2396
2452
  monitorCommand.command("stats <id>").description("Get monitor performance statistics").action(async (id) => {
2397
2453
  const client = createAuthenticatedClient();
2398
- const { data } = await client.get(`/api/monitors/${id}/stats`);
2454
+ const { data } = await withSpinner(
2455
+ "Fetching monitor statistics",
2456
+ () => client.get(`/api/monitors/${id}/stats`)
2457
+ );
2399
2458
  outputDetail(data);
2400
2459
  });
2401
2460
  monitorCommand.command("status <id>").description("Get current monitor status").action(async (id) => {
2402
2461
  const client = createAuthenticatedClient();
2403
- const { data } = await client.get(`/api/monitors/${id}`);
2462
+ const { data } = await withSpinner(
2463
+ "Fetching monitor status",
2464
+ () => client.get(`/api/monitors/${id}`)
2465
+ );
2404
2466
  const statusInfo = {
2405
2467
  id: data.id,
2406
2468
  name: data.name,
@@ -2425,7 +2487,10 @@ monitorCommand.command("create").description("Create a new monitor").requiredOpt
2425
2487
  method: options.method
2426
2488
  }
2427
2489
  };
2428
- const { data } = await client.post("/api/monitors", body);
2490
+ const { data } = await withSpinner(
2491
+ "Creating monitor",
2492
+ () => client.post("/api/monitors", body)
2493
+ );
2429
2494
  logger.success(`Monitor "${options.name}" created (${data.id})`);
2430
2495
  outputDetail(data);
2431
2496
  });
@@ -2447,7 +2512,10 @@ monitorCommand.command("update <id>").description("Update a monitor").option("--
2447
2512
  logger.warn("No fields to update. Use --name, --url, --interval, --timeout, --method, or --active.");
2448
2513
  return;
2449
2514
  }
2450
- const { data } = await client.patch(`/api/monitors/${id}`, body);
2515
+ const { data } = await withSpinner(
2516
+ "Updating monitor",
2517
+ () => client.patch(`/api/monitors/${id}`, body)
2518
+ );
2451
2519
  logger.success(`Monitor ${id} updated`);
2452
2520
  outputDetail(data);
2453
2521
  });
@@ -2461,7 +2529,10 @@ monitorCommand.command("delete <id>").description("Delete a monitor").option("--
2461
2529
  }
2462
2530
  }
2463
2531
  const client = createAuthenticatedClient();
2464
- await client.delete(`/api/monitors/${id}`);
2532
+ await withSpinner(
2533
+ "Deleting monitor",
2534
+ () => client.delete(`/api/monitors/${id}`)
2535
+ );
2465
2536
  logger.success(`Monitor ${id} deleted`);
2466
2537
  });
2467
2538
 
@@ -2470,7 +2541,10 @@ import { Command as Command9 } from "commander";
2470
2541
  var varCommand = new Command9("var").description("Manage project variables");
2471
2542
  varCommand.command("list").description("List all variables").action(async () => {
2472
2543
  const client = createAuthenticatedClient();
2473
- const { data } = await client.get("/api/variables");
2544
+ const { data } = await withSpinner(
2545
+ "Fetching variables",
2546
+ () => client.get("/api/variables")
2547
+ );
2474
2548
  output(data, {
2475
2549
  columns: [
2476
2550
  { key: "id", header: "ID" },
@@ -2482,7 +2556,10 @@ varCommand.command("list").description("List all variables").action(async () =>
2482
2556
  });
2483
2557
  varCommand.command("get <key>").description("Get a variable by key name").action(async (key) => {
2484
2558
  const client = createAuthenticatedClient();
2485
- const { data: variables } = await client.get("/api/variables");
2559
+ const { data: variables } = await withSpinner(
2560
+ "Fetching variable",
2561
+ () => client.get("/api/variables")
2562
+ );
2486
2563
  const variable = variables.find((v) => v.key === key);
2487
2564
  if (!variable) {
2488
2565
  throw new CLIError(`Variable "${key}" not found`, 1 /* GeneralError */);
@@ -2491,29 +2568,41 @@ varCommand.command("get <key>").description("Get a variable by key name").action
2491
2568
  });
2492
2569
  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
2570
  const client = createAuthenticatedClient();
2494
- const { data: variables } = await client.get("/api/variables");
2571
+ const { data: variables } = await withSpinner(
2572
+ "Checking existing variables",
2573
+ () => client.get("/api/variables")
2574
+ );
2495
2575
  const existing = variables.find((v) => v.key === key);
2496
2576
  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
- });
2577
+ await withSpinner(
2578
+ "Updating variable",
2579
+ () => client.put(`/api/variables/${existing.id}`, {
2580
+ key,
2581
+ value,
2582
+ isSecret: options.secret ?? existing.isSecret,
2583
+ ...options.description !== void 0 && { description: options.description }
2584
+ })
2585
+ );
2503
2586
  logger.success(`Variable "${key}" updated`);
2504
2587
  } 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
- });
2588
+ await withSpinner(
2589
+ "Creating variable",
2590
+ () => client.post("/api/variables", {
2591
+ key,
2592
+ value,
2593
+ isSecret: options.secret ?? false,
2594
+ ...options.description !== void 0 && { description: options.description }
2595
+ })
2596
+ );
2511
2597
  logger.success(`Variable "${key}" created`);
2512
2598
  }
2513
2599
  });
2514
2600
  varCommand.command("delete <key>").description("Delete a variable").option("--force", "Skip confirmation").action(async (key, options) => {
2515
2601
  const client = createAuthenticatedClient();
2516
- const { data: variables } = await client.get("/api/variables");
2602
+ const { data: variables } = await withSpinner(
2603
+ "Fetching variables",
2604
+ () => client.get("/api/variables")
2605
+ );
2517
2606
  const variable = variables.find((v) => v.key === key);
2518
2607
  if (!variable) {
2519
2608
  throw new CLIError(`Variable "${key}" not found`, 1 /* GeneralError */);
@@ -2526,7 +2615,10 @@ varCommand.command("delete <key>").description("Delete a variable").option("--fo
2526
2615
  return;
2527
2616
  }
2528
2617
  }
2529
- await client.delete(`/api/variables/${variable.id}`);
2618
+ await withSpinner(
2619
+ "Deleting variable",
2620
+ () => client.delete(`/api/variables/${variable.id}`)
2621
+ );
2530
2622
  logger.success(`Variable "${key}" deleted`);
2531
2623
  });
2532
2624
 
@@ -2535,7 +2627,10 @@ import { Command as Command10 } from "commander";
2535
2627
  var tagCommand = new Command10("tag").description("Manage tags");
2536
2628
  tagCommand.command("list").description("List all tags").action(async () => {
2537
2629
  const client = createAuthenticatedClient();
2538
- const { data } = await client.get("/api/tags");
2630
+ const { data } = await withSpinner(
2631
+ "Fetching tags",
2632
+ () => client.get("/api/tags")
2633
+ );
2539
2634
  output(data, {
2540
2635
  columns: [
2541
2636
  { key: "id", header: "ID" },
@@ -2546,10 +2641,13 @@ tagCommand.command("list").description("List all tags").action(async () => {
2546
2641
  });
2547
2642
  tagCommand.command("create <name>").description("Create a new tag").option("--color <color>", "Tag color (hex)").action(async (name, options) => {
2548
2643
  const client = createAuthenticatedClient();
2549
- const { data } = await client.post("/api/tags", {
2550
- name,
2551
- color: options.color
2552
- });
2644
+ const { data } = await withSpinner(
2645
+ "Creating tag",
2646
+ () => client.post("/api/tags", {
2647
+ name,
2648
+ color: options.color
2649
+ })
2650
+ );
2553
2651
  logger.success(`Tag "${name}" created (${data.id})`);
2554
2652
  });
2555
2653
  tagCommand.command("delete <id>").description("Delete a tag").option("--force", "Skip confirmation").action(async (id, options) => {
@@ -2562,7 +2660,10 @@ tagCommand.command("delete <id>").description("Delete a tag").option("--force",
2562
2660
  }
2563
2661
  }
2564
2662
  const client = createAuthenticatedClient();
2565
- await client.delete(`/api/tags/${id}`);
2663
+ await withSpinner(
2664
+ "Deleting tag",
2665
+ () => client.delete(`/api/tags/${id}`)
2666
+ );
2566
2667
  logger.success(`Tag ${id} deleted`);
2567
2668
  });
2568
2669
 
@@ -3266,17 +3367,44 @@ function buildStatusPageDefinitions(pages) {
3266
3367
  }
3267
3368
  function generateConfigContent(opts) {
3268
3369
  const parts = [];
3370
+ parts.push("/**");
3371
+ parts.push(" * Supercheck Configuration");
3372
+ 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");
3373
+ parts.push(" *");
3374
+ parts.push(" * This file was generated by `supercheck pull`. It is the source of truth");
3375
+ parts.push(" * for your Supercheck project configuration.");
3376
+ parts.push(" *");
3377
+ parts.push(" * Getting Started:");
3378
+ parts.push(" * 1. Review the configuration below and make any changes you need.");
3379
+ parts.push(" * 2. Preview changes: supercheck diff");
3380
+ parts.push(" * 3. Deploy changes: supercheck deploy");
3381
+ parts.push(" * 4. Re-sync: supercheck pull");
3382
+ parts.push(" *");
3383
+ parts.push(" * Useful Commands:");
3384
+ parts.push(" * supercheck test validate Validate local test scripts");
3385
+ parts.push(" * supercheck config validate Validate this config file");
3386
+ parts.push(" * supercheck health Check API connectivity");
3387
+ parts.push(" * supercheck destroy Remove all managed resources from the cloud");
3388
+ parts.push(" *");
3389
+ parts.push(` * Documentation: https://supercheck.io/docs/app/welcome`);
3390
+ parts.push(" */");
3269
3391
  parts.push(`import { defineConfig } from '@supercheck/cli'`);
3270
3392
  parts.push("");
3271
3393
  parts.push("export default defineConfig({");
3272
3394
  parts.push(` schemaVersion: '1.0',`);
3395
+ parts.push("");
3396
+ parts.push(" // Project identifiers \u2014 found in Dashboard > Project Settings");
3273
3397
  parts.push(" project: {");
3274
3398
  parts.push(` organization: '${opts.orgId}',`);
3275
3399
  parts.push(` project: '${opts.projectId}',`);
3276
3400
  parts.push(" },");
3401
+ parts.push("");
3402
+ parts.push(" // API connection \u2014 set SUPERCHECK_URL env var for self-hosted or staging");
3277
3403
  parts.push(" api: {");
3278
3404
  parts.push(` baseUrl: process.env.SUPERCHECK_URL ?? '${opts.baseUrl}',`);
3279
3405
  parts.push(" },");
3406
+ parts.push("");
3407
+ parts.push(" // Test file patterns \u2014 Playwright (.pw.ts) and k6 (.k6.ts) scripts");
3280
3408
  parts.push(" tests: {");
3281
3409
  parts.push(" playwright: {");
3282
3410
  parts.push(` testMatch: '_supercheck_/tests/**/*.pw.ts',`);
@@ -3286,42 +3414,33 @@ function generateConfigContent(opts) {
3286
3414
  parts.push(" },");
3287
3415
  parts.push(" },");
3288
3416
  if (opts.monitors.length > 0) {
3417
+ parts.push("");
3418
+ parts.push(" // Monitors \u2014 uptime checks, HTTP requests, synthetic tests");
3289
3419
  parts.push(` monitors: ${formatArray(opts.monitors, 2)},`);
3290
3420
  }
3291
3421
  if (opts.jobs.length > 0) {
3422
+ parts.push("");
3423
+ parts.push(" // Jobs \u2014 scheduled or triggered test execution groups");
3292
3424
  parts.push(` jobs: ${formatArray(opts.jobs, 2)},`);
3293
3425
  }
3294
3426
  if (opts.variables.length > 0) {
3427
+ parts.push("");
3428
+ parts.push(" // Variables \u2014 key-value pairs available to tests at runtime");
3429
+ parts.push(" // Secret values use process.env references \u2014 set them in your shell or CI/CD");
3295
3430
  parts.push(` variables: ${formatArray(opts.variables, 2)},`);
3296
3431
  }
3297
3432
  if (opts.tags.length > 0) {
3433
+ parts.push("");
3434
+ parts.push(" // Tags \u2014 labels for organizing tests, monitors, and jobs");
3298
3435
  parts.push(` tags: ${formatArray(opts.tags, 2)},`);
3299
3436
  }
3300
3437
  if (opts.statusPages.length > 0) {
3438
+ parts.push("");
3439
+ parts.push(" // Status Pages \u2014 public dashboards showing monitor health");
3301
3440
  parts.push(` statusPages: ${formatArray(opts.statusPages, 2)},`);
3302
3441
  }
3303
3442
  parts.push("})");
3304
3443
  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
3444
  return parts.join("\n");
3326
3445
  }
3327
3446
  function formatArray(arr, baseIndent) {
@@ -3476,9 +3595,12 @@ var pullCommand = new Command14("pull").description("Pull tests, monitors, jobs,
3476
3595
  fullTests.push(t);
3477
3596
  } else {
3478
3597
  try {
3479
- const { data } = await client.get(
3480
- `/api/tests/${t.id}`,
3481
- { includeScript: "true" }
3598
+ const { data } = await withSpinner(
3599
+ `Fetching script for "${t.title}"`,
3600
+ () => client.get(
3601
+ `/api/tests/${t.id}`,
3602
+ { includeScript: "true" }
3603
+ )
3482
3604
  );
3483
3605
  fullTests.push(data);
3484
3606
  } catch (err) {
@@ -3607,7 +3729,10 @@ import { Command as Command15 } from "commander";
3607
3729
  var notificationCommand = new Command15("notification").alias("notifications").description("Manage notification providers");
3608
3730
  notificationCommand.command("list").description("List notification providers").action(async () => {
3609
3731
  const client = createAuthenticatedClient();
3610
- const { data } = await client.get("/api/notification-providers");
3732
+ const { data } = await withSpinner(
3733
+ "Fetching notification providers",
3734
+ () => client.get("/api/notification-providers")
3735
+ );
3611
3736
  output(data, {
3612
3737
  columns: [
3613
3738
  { key: "id", header: "ID" },
@@ -3620,7 +3745,10 @@ notificationCommand.command("list").description("List notification providers").a
3620
3745
  });
3621
3746
  notificationCommand.command("get <id>").description("Get notification provider details").action(async (id) => {
3622
3747
  const client = createAuthenticatedClient();
3623
- const { data } = await client.get(`/api/notification-providers/${id}`);
3748
+ const { data } = await withSpinner(
3749
+ "Fetching provider details",
3750
+ () => client.get(`/api/notification-providers/${id}`)
3751
+ );
3624
3752
  outputDetail(data);
3625
3753
  });
3626
3754
  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 +3768,14 @@ notificationCommand.command("create").description("Create a notification provide
3640
3768
  }
3641
3769
  }
3642
3770
  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
- });
3771
+ const { data } = await withSpinner(
3772
+ "Creating notification provider",
3773
+ () => client.post("/api/notification-providers", {
3774
+ name: options.name,
3775
+ type: options.type,
3776
+ config: { name: options.name, ...config }
3777
+ })
3778
+ );
3648
3779
  logger.success(`Notification provider "${options.name}" created (${data.id})`);
3649
3780
  outputDetail(data);
3650
3781
  });
@@ -3654,7 +3785,10 @@ notificationCommand.command("update <id>").description("Update a notification pr
3654
3785
  logger.warn("No fields to update. Use --name, --type, or --config.");
3655
3786
  return;
3656
3787
  }
3657
- const { data: existing } = await client.get(`/api/notification-providers/${id}`);
3788
+ const { data: existing } = await withSpinner(
3789
+ "Fetching existing provider",
3790
+ () => client.get(`/api/notification-providers/${id}`)
3791
+ );
3658
3792
  let updatedConfig = existing.config ?? {};
3659
3793
  if (options.config) {
3660
3794
  try {
@@ -3671,7 +3805,10 @@ notificationCommand.command("update <id>").description("Update a notification pr
3671
3805
  type: updatedType,
3672
3806
  config: updatedConfig
3673
3807
  };
3674
- const { data } = await client.put(`/api/notification-providers/${id}`, body);
3808
+ const { data } = await withSpinner(
3809
+ "Updating notification provider",
3810
+ () => client.put(`/api/notification-providers/${id}`, body)
3811
+ );
3675
3812
  logger.success(`Notification provider ${id} updated`);
3676
3813
  outputDetail(data);
3677
3814
  });
@@ -3685,7 +3822,10 @@ notificationCommand.command("delete <id>").description("Delete a notification pr
3685
3822
  }
3686
3823
  }
3687
3824
  const client = createAuthenticatedClient();
3688
- await client.delete(`/api/notification-providers/${id}`);
3825
+ await withSpinner(
3826
+ "Deleting notification provider",
3827
+ () => client.delete(`/api/notification-providers/${id}`)
3828
+ );
3689
3829
  logger.success(`Notification provider ${id} deleted`);
3690
3830
  });
3691
3831
  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 +3843,12 @@ notificationCommand.command("test").description("Send a test notification to ver
3703
3843
  throw new CLIError("Invalid JSON in --config", 1 /* GeneralError */);
3704
3844
  }
3705
3845
  const client = createAuthenticatedClient();
3706
- const { data } = await client.post(
3707
- "/api/notification-providers/test",
3708
- { type: options.type, config }
3846
+ const { data } = await withSpinner(
3847
+ "Sending test notification",
3848
+ () => client.post(
3849
+ "/api/notification-providers/test",
3850
+ { type: options.type, config }
3851
+ )
3709
3852
  );
3710
3853
  if (data.success) {
3711
3854
  logger.success(data.message ?? "Test notification sent successfully");
@@ -3719,7 +3862,10 @@ import { Command as Command16 } from "commander";
3719
3862
  var alertCommand = new Command16("alert").alias("alerts").description("View alert history");
3720
3863
  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
3864
  const client = createAuthenticatedClient();
3722
- const { data } = await client.get("/api/alerts/history", { page: options.page, limit: options.limit });
3865
+ const { data } = await withSpinner(
3866
+ "Fetching alert history",
3867
+ () => client.get("/api/alerts/history", { page: options.page, limit: options.limit })
3868
+ );
3723
3869
  const items = Array.isArray(data) ? data : data.data;
3724
3870
  const pagination = Array.isArray(data) ? null : data.pagination;
3725
3871
  output(items, {
@@ -3750,7 +3896,10 @@ var auditCommand = new Command17("audit").description("View audit logs (admin)")
3750
3896
  };
3751
3897
  if (options.search) params.search = options.search;
3752
3898
  if (options.action) params.action = options.action;
3753
- const { data } = await client.get("/api/audit", params);
3899
+ const { data } = await withSpinner(
3900
+ "Fetching audit logs",
3901
+ () => client.get("/api/audit", params)
3902
+ );
3754
3903
  if (!data.success) {
3755
3904
  throw new CLIError("Failed to fetch audit logs", 4 /* ApiError */);
3756
3905
  }