@kolatts/pncli 5.4.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -28,11 +28,13 @@ import {
28
28
  windowsListCredentials,
29
29
  writeGlobalConfig,
30
30
  writeRepoConfig
31
- } from "./chunk-L6OY2546.js";
31
+ } from "./chunk-WDWAPTVC.js";
32
32
  import {
33
+ configureTls,
33
34
  createHttpClient,
35
+ describeFetchError,
34
36
  validateAlationAccessToken
35
- } from "./chunk-2J2HAFKO.js";
37
+ } from "./chunk-4FR4WGNR.js";
36
38
  import {
37
39
  ExitCode,
38
40
  fail,
@@ -599,9 +601,9 @@ var BitbucketClient = class {
599
601
  )
600
602
  );
601
603
  }
602
- async getPR(project, repo, id) {
604
+ async getPR(project, repo, id2) {
603
605
  return this.http.bitbucket(
604
- `${API}/projects/${project}/repos/${repo}/pull-requests/${id}`
606
+ `${API}/projects/${project}/repos/${repo}/pull-requests/${id2}`
605
607
  );
606
608
  }
607
609
  async createPR(opts) {
@@ -653,9 +655,9 @@ var BitbucketClient = class {
653
655
  { method: "POST", params, body }
654
656
  );
655
657
  }
656
- async declinePR(project, repo, id, version) {
658
+ async declinePR(project, repo, id2, version) {
657
659
  return this.http.bitbucket(
658
- `${API}/projects/${project}/repos/${repo}/pull-requests/${id}/decline`,
660
+ `${API}/projects/${project}/repos/${repo}/pull-requests/${id2}/decline`,
659
661
  { method: "POST", params: { version } }
660
662
  );
661
663
  }
@@ -2885,9 +2887,9 @@ function parseFieldsFile(filePath, fieldMap) {
2885
2887
  }
2886
2888
  function translateFieldErrors(err, fieldMap) {
2887
2889
  if (!(err instanceof PncliError) || fieldMap.byId.size === 0) return err;
2888
- const translated = err.message.replace(/\bcustomfield_\d+\b/g, (id) => {
2889
- const def = fieldMap.byId.get(id);
2890
- return def ? `${def.name} (${id})` : id;
2890
+ const translated = err.message.replace(/\bcustomfield_\d+\b/g, (id2) => {
2891
+ const def = fieldMap.byId.get(id2);
2892
+ return def ? `${def.name} (${id2})` : id2;
2891
2893
  });
2892
2894
  if (translated === err.message) return err;
2893
2895
  return new PncliError(translated, err.status, err.url);
@@ -4255,8 +4257,8 @@ var ConfluenceClient = class {
4255
4257
  this.http = http;
4256
4258
  }
4257
4259
  http;
4258
- async getPage(id, expand = "body.storage,version,space,ancestors") {
4259
- return this.http.confluence(`${API3}/content/${id}`, {
4260
+ async getPage(id2, expand = "body.storage,version,space,ancestors") {
4261
+ return this.http.confluence(`${API3}/content/${id2}`, {
4260
4262
  params: { expand }
4261
4263
  });
4262
4264
  }
@@ -4274,16 +4276,16 @@ var ConfluenceClient = class {
4274
4276
  });
4275
4277
  }, opts.limit);
4276
4278
  }
4277
- async getPageChildren(id) {
4279
+ async getPageChildren(id2) {
4278
4280
  return this.http.confluencePaginate(async (start, limit) => {
4279
- return this.http.confluence(`${API3}/content/${id}/child/page`, {
4281
+ return this.http.confluence(`${API3}/content/${id2}/child/page`, {
4280
4282
  params: { expand: "version", start, limit }
4281
4283
  });
4282
4284
  });
4283
4285
  }
4284
- async getLabels(id) {
4286
+ async getLabels(id2) {
4285
4287
  return this.http.confluencePaginate(async (start, limit) => {
4286
- return this.http.confluence(`${API3}/content/${id}/label`, {
4288
+ return this.http.confluence(`${API3}/content/${id2}/label`, {
4287
4289
  params: { start, limit }
4288
4290
  });
4289
4291
  });
@@ -4316,7 +4318,7 @@ var ConfluenceClient = class {
4316
4318
  body
4317
4319
  });
4318
4320
  }
4319
- async updatePage(id, opts) {
4321
+ async updatePage(id2, opts) {
4320
4322
  const body = {
4321
4323
  version: { number: opts.version },
4322
4324
  type: "page",
@@ -4331,13 +4333,13 @@ var ConfluenceClient = class {
4331
4333
  }
4332
4334
  };
4333
4335
  }
4334
- return this.http.confluence(`${API3}/content/${id}`, {
4336
+ return this.http.confluence(`${API3}/content/${id2}`, {
4335
4337
  method: "PUT",
4336
4338
  body
4337
4339
  });
4338
4340
  }
4339
- async deletePage(id) {
4340
- return this.http.confluence(`${API3}/content/${id}`, { method: "DELETE" });
4341
+ async deletePage(id2) {
4342
+ return this.http.confluence(`${API3}/content/${id2}`, { method: "DELETE" });
4341
4343
  }
4342
4344
  async listComments(pageId) {
4343
4345
  return this.http.confluencePaginate(async (start, limit) => {
@@ -4422,8 +4424,8 @@ var ConfluenceClient = class {
4422
4424
  async deleteAttachment(attachmentId) {
4423
4425
  return this.http.confluence(`${API3}/content/${attachmentId}`, { method: "DELETE" });
4424
4426
  }
4425
- async getPageHistory(id) {
4426
- return this.http.confluence(`${API3}/content/${id}/history`);
4427
+ async getPageHistory(id2) {
4428
+ return this.http.confluence(`${API3}/content/${id2}/history`);
4427
4429
  }
4428
4430
  async convertToStorage(value, fromRepresentation = "markdown") {
4429
4431
  const result = await this.http.confluence(
@@ -5115,9 +5117,9 @@ function resolveProject2(config, cliProject) {
5115
5117
  const project = cliProject ?? config.defaults.sde.project;
5116
5118
  if (!project) throw new PncliError("No project specified. Use --project or set defaults.sde.project in config.");
5117
5119
  if (!/^\d+$/.test(project)) throw new PncliError(`Invalid project ID: "${project}". SDElements project IDs must be positive integers.`);
5118
- const id = parseInt(project, 10);
5119
- if (id <= 0) throw new PncliError(`Invalid project ID: "${project}". SDElements project IDs must be positive integers.`);
5120
- return id;
5120
+ const id2 = parseInt(project, 10);
5121
+ if (id2 <= 0) throw new PncliError(`Invalid project ID: "${project}". SDElements project IDs must be positive integers.`);
5122
+ return id2;
5121
5123
  }
5122
5124
  function parsePage(val, flag) {
5123
5125
  const n = parseInt(val, 10);
@@ -6045,7 +6047,7 @@ async function checkOsvConnectivity() {
6045
6047
  } catch (err) {
6046
6048
  return {
6047
6049
  reachable: false,
6048
- error: err instanceof Error ? err.message : String(err)
6050
+ error: describeFetchError(err)
6049
6051
  };
6050
6052
  }
6051
6053
  }
@@ -6250,7 +6252,7 @@ async function checkSonatypeConnectivity() {
6250
6252
  } catch (err) {
6251
6253
  return {
6252
6254
  reachable: false,
6253
- error: err instanceof Error ? err.message : String(err)
6255
+ error: describeFetchError(err)
6254
6256
  };
6255
6257
  }
6256
6258
  }
@@ -6314,10 +6316,10 @@ function mapViolationToVuln(violation) {
6314
6316
  const cveRefs = violation.constraintViolations.flatMap(
6315
6317
  (cv) => cv.reasons.filter((r) => r.reference?.type === "VULNERABILITY").map((r) => r.reference.value)
6316
6318
  );
6317
- const id = cveRefs[0] ?? violation.policyId;
6319
+ const id2 = cveRefs[0] ?? violation.policyId;
6318
6320
  const severity = threatLevelToSeverity(violation.policyThreatLevel);
6319
6321
  return {
6320
- id,
6322
+ id: id2,
6321
6323
  summary: violation.policyName,
6322
6324
  severity,
6323
6325
  cvss: null,
@@ -6473,7 +6475,7 @@ async function checkSonatypeIqConnectivity(baseUrl, userCode, passcode) {
6473
6475
  return {
6474
6476
  reachable: false,
6475
6477
  authenticated: false,
6476
- error: err instanceof Error ? err.message : String(err)
6478
+ error: describeFetchError(err)
6477
6479
  };
6478
6480
  }
6479
6481
  }
@@ -6533,7 +6535,7 @@ async function checkArtifactoryConnectivity(config) {
6533
6535
  reachable: false,
6534
6536
  authenticated: false,
6535
6537
  configured: true,
6536
- error: err instanceof Error ? err.message : String(err)
6538
+ error: describeFetchError(err)
6537
6539
  };
6538
6540
  }
6539
6541
  }
@@ -7080,9 +7082,9 @@ var AdoWorkClient = class {
7080
7082
  this.http = http;
7081
7083
  }
7082
7084
  http;
7083
- async getWorkItem(collection, id) {
7085
+ async getWorkItem(collection, id2) {
7084
7086
  return this.http.ado(
7085
- `/${encodeURIComponent(collection)}/_apis/wit/workitems/${id}?api-version=${API6}&$expand=all`
7087
+ `/${encodeURIComponent(collection)}/_apis/wit/workitems/${id2}?api-version=${API6}&$expand=all`
7086
7088
  );
7087
7089
  }
7088
7090
  async createWorkItem(collection, project, type, patch) {
@@ -7091,9 +7093,9 @@ var AdoWorkClient = class {
7091
7093
  { method: "POST", body: patch, headers: { "Content-Type": "application/json-patch+json" } }
7092
7094
  );
7093
7095
  }
7094
- async updateWorkItem(collection, id, patch) {
7096
+ async updateWorkItem(collection, id2, patch) {
7095
7097
  return this.http.ado(
7096
- `/${encodeURIComponent(collection)}/_apis/wit/workitems/${id}?api-version=${API6}`,
7098
+ `/${encodeURIComponent(collection)}/_apis/wit/workitems/${id2}?api-version=${API6}`,
7097
7099
  { method: "PATCH", body: patch, headers: { "Content-Type": "application/json-patch+json" } }
7098
7100
  );
7099
7101
  }
@@ -7115,23 +7117,23 @@ var AdoWorkClient = class {
7115
7117
  { method: "POST", body: { text } }
7116
7118
  );
7117
7119
  }
7118
- async addTags(collection, id, tags) {
7119
- const item = await this.getWorkItem(collection, id);
7120
+ async addTags(collection, id2, tags) {
7121
+ const item = await this.getWorkItem(collection, id2);
7120
7122
  const existing = item.fields["System.Tags"] ?? "";
7121
7123
  const current = existing.split(";").map((t) => t.trim()).filter(Boolean);
7122
7124
  const lowerSet = new Set(current.map((t) => t.toLowerCase()));
7123
7125
  const toAdd = tags.filter((t) => !lowerSet.has(t.toLowerCase()));
7124
7126
  const merged = [...current, ...toAdd];
7125
7127
  const patch = [{ op: "add", path: "/fields/System.Tags", value: merged.join("; ") }];
7126
- return this.updateWorkItem(collection, id, patch);
7128
+ return this.updateWorkItem(collection, id2, patch);
7127
7129
  }
7128
- async removeTags(collection, id, tags) {
7129
- const item = await this.getWorkItem(collection, id);
7130
+ async removeTags(collection, id2, tags) {
7131
+ const item = await this.getWorkItem(collection, id2);
7130
7132
  const existing = item.fields["System.Tags"] ?? "";
7131
7133
  const lower = new Set(tags.map((t) => t.toLowerCase()));
7132
7134
  const remaining = existing.split(";").map((t) => t.trim()).filter((t) => t && !lower.has(t.toLowerCase()));
7133
7135
  const patch = [{ op: "add", path: "/fields/System.Tags", value: remaining.join("; ") }];
7134
- return this.updateWorkItem(collection, id, patch);
7136
+ return this.updateWorkItem(collection, id2, patch);
7135
7137
  }
7136
7138
  async listWorkItemTypes(collection, project) {
7137
7139
  const result = await this.http.ado(
@@ -7272,6 +7274,422 @@ function buildDefaultAliases(fields) {
7272
7274
  return aliases;
7273
7275
  }
7274
7276
 
7277
+ // src/services/saucelabs/commands.ts
7278
+ var SERVICE = "saucelabs";
7279
+ var RDA = "/rdc/v2";
7280
+ var TEAM_MGMT = "/team-management/v1";
7281
+ var BUILD_SOURCES = ["vdc", "rdc"];
7282
+ var BUILD_STATUSES = ["running", "error", "failed", "complete", "success"];
7283
+ var JOB_VISIBILITY = ["public", "public restricted", "share", "team", "private"];
7284
+ var PLATFORM_APIS = ["all", "appium", "webdriver"];
7285
+ var DEVICE_OS = ["ANDROID", "IOS"];
7286
+ var DEVICE_TYPES = ["PHONE", "TABLET"];
7287
+ var DEVICE_STATES = ["AVAILABLE", "IN_USE", "CLEANING", "MAINTENANCE", "REBOOTING", "OFFLINE"];
7288
+ var SESSION_STATES = ["PENDING", "CREATING", "ACTIVE", "CLOSING", "CLOSED", "ERRORED"];
7289
+ var ORIENTATIONS = ["PORTRAIT", "LANDSCAPE"];
7290
+ var STORAGE_KINDS = ["android", "ios"];
7291
+ function getHttp(program2) {
7292
+ const opts = program2.optsWithGlobals();
7293
+ return createHttpClient(
7294
+ loadConfig({ configPath: opts.config }),
7295
+ Boolean(opts.dryRun)
7296
+ );
7297
+ }
7298
+ async function verifySauceLabsCredentials(http, timeoutMs) {
7299
+ return http.saucelabs(
7300
+ `/rest/v1.2/users/${encodeURIComponent(http.saucelabsUsername())}/concurrency`,
7301
+ timeoutMs !== void 0 ? { timeoutMs } : {}
7302
+ );
7303
+ }
7304
+ function parseIntOption(value, flag) {
7305
+ if (value === void 0) return void 0;
7306
+ const trimmed = value.trim();
7307
+ if (!/^\d+$/.test(trimmed)) {
7308
+ throw new PncliError(`Invalid ${flag} "${value}". Expected a non-negative integer.`, 1);
7309
+ }
7310
+ return Number(trimmed);
7311
+ }
7312
+ function parseUnixTime(value, flag) {
7313
+ if (value === void 0) return void 0;
7314
+ const trimmed = value.trim();
7315
+ if (/^\d+$/.test(trimmed)) return Number(trimmed);
7316
+ const ms = Date.parse(trimmed);
7317
+ if (!Number.isFinite(ms)) {
7318
+ throw new PncliError(`Invalid ${flag} "${value}". Expected Unix seconds or an ISO 8601 date such as 2026-09-01T00:00:00Z.`, 1);
7319
+ }
7320
+ return Math.floor(ms / 1e3);
7321
+ }
7322
+ function parseChoice(value, choices, flag) {
7323
+ if (value === void 0) return void 0;
7324
+ const match = choices.find((c) => c.toLowerCase() === value.trim().toLowerCase());
7325
+ if (!match) {
7326
+ throw new PncliError(`Invalid ${flag} "${value}". Expected one of: ${choices.join(", ")}.`, 1);
7327
+ }
7328
+ return match;
7329
+ }
7330
+ function collectList(value, previous = []) {
7331
+ return [...previous, ...value.split(",").map((v) => v.trim()).filter(Boolean)];
7332
+ }
7333
+ function withRepeatedParam(path14, key, values) {
7334
+ if (!values || values.length === 0) return path14;
7335
+ const qs = values.map((v) => `${encodeURIComponent(key)}=${encodeURIComponent(v)}`).join("&");
7336
+ return `${path14}${path14.includes("?") ? "&" : "?"}${qs}`;
7337
+ }
7338
+ function passedFlag(opts) {
7339
+ if (opts.passed && opts.failed) throw new PncliError("Pass only one of --passed or --failed.", 1);
7340
+ if (opts.passed) return true;
7341
+ if (opts.failed) return false;
7342
+ return void 0;
7343
+ }
7344
+ function requireBody(body, hint) {
7345
+ if (Object.keys(body).length === 0) throw new PncliError(`Nothing to update. ${hint}`, 1);
7346
+ return body;
7347
+ }
7348
+ function id(value) {
7349
+ return encodeURIComponent(value.trim());
7350
+ }
7351
+ function countOf(data, key) {
7352
+ const list = key ? data?.[key] : data;
7353
+ return Array.isArray(list) ? list.length : void 0;
7354
+ }
7355
+ function registerSauceLabsCommands(program2) {
7356
+ function run(action, fn) {
7357
+ return async (...args) => {
7358
+ const start = Date.now();
7359
+ const { success: success2, fail: fail2 } = await import("./output-HDUIIXUI.js");
7360
+ try {
7361
+ success2(await fn(getHttp(program2), ...args), SERVICE, action, start);
7362
+ } catch (err) {
7363
+ fail2(err, SERVICE, action, start);
7364
+ }
7365
+ };
7366
+ }
7367
+ const sauce = program2.command("saucelabs").description("Sauce Labs (jobs, builds, real devices and device sessions, tunnels, platforms, app storage, teams)");
7368
+ sauce.command("status").description("Sauce Labs service status (operational flag, wait time)").action(run("status", (http) => http.saucelabs("/rest/v1/info/status")));
7369
+ sauce.command("concurrency").description("Concurrency allowed and in use for your organization and team (also verifies credentials)").action(run("concurrency", (http) => verifySauceLabsCredentials(http)));
7370
+ sauce.command("platforms").description("Supported OS/browser/device platforms").option("--api <api>", "Automation API: all, appium, webdriver", "all").action(run("platforms", async (http, opts) => {
7371
+ const api = parseChoice(opts.api, PLATFORM_APIS, "--api");
7372
+ const data = await http.saucelabs(`/rest/v1/info/platforms/${api}`);
7373
+ return { api, count: countOf(data), platforms: data };
7374
+ }));
7375
+ const job = sauce.command("job").description("Test jobs on Sauce virtual devices and desktop browsers (VDC)");
7376
+ job.command("list").description("List your recent jobs").option("--limit <n>", "Maximum number of jobs to return").option("--skip <n>", "Number of jobs to skip").option("--from <time>", "Only jobs created after this time (Unix seconds or ISO 8601)").option("--to <time>", "Only jobs created before this time (Unix seconds or ISO 8601)").action(run("job-list", async (http, opts) => {
7377
+ const params = {
7378
+ limit: parseIntOption(opts.limit, "--limit"),
7379
+ skip: parseIntOption(opts.skip, "--skip"),
7380
+ from: parseUnixTime(opts.from, "--from"),
7381
+ to: parseUnixTime(opts.to, "--to")
7382
+ };
7383
+ const data = await http.saucelabs(`/rest/v1/${id(http.saucelabsUsername())}/jobs`, { params });
7384
+ return { count: countOf(data), jobs: data };
7385
+ }));
7386
+ job.command("get").description("Get one job: status, platform, timings, and log/video URLs").argument("<job-id>", "Job ID").action(run("job-get", (http, jobId) => http.saucelabs(`/rest/v1/${id(http.saucelabsUsername())}/jobs/${id(jobId)}`)));
7387
+ job.command("update").description("Update a job's name, build, tags, result, or visibility").argument("<job-id>", "Job ID").option("--name <name>", "Job name").option("--build <build>", "Build name to group the job under").option("--tags <tags>", "Comma-separated tags; replaces the existing tags", collectList).option("--passed", "Mark the job as passed").option("--failed", "Mark the job as failed").option("--public <visibility>", "Visibility: public, public restricted, share, team, private").action(run("job-update", (http, jobId, opts) => {
7388
+ const body = {};
7389
+ if (opts.name !== void 0) body["name"] = opts.name;
7390
+ if (opts.build !== void 0) body["build"] = opts.build;
7391
+ if (opts.tags !== void 0) body["tags"] = opts.tags;
7392
+ const passed = passedFlag(opts);
7393
+ if (passed !== void 0) body["passed"] = passed;
7394
+ const visibility = parseChoice(opts.public, JOB_VISIBILITY, "--public");
7395
+ if (visibility !== void 0) body["public"] = visibility;
7396
+ return http.saucelabs(`/rest/v1/${id(http.saucelabsUsername())}/jobs/${id(jobId)}`, {
7397
+ method: "PUT",
7398
+ body: requireBody(body, "Pass at least one of --name, --build, --tags, --passed/--failed, --public.")
7399
+ });
7400
+ }));
7401
+ job.command("stop").description("Stop a running job").argument("<job-id>", "Job ID").action(run("job-stop", (http, jobId) => http.saucelabs(`/rest/v1/${id(http.saucelabsUsername())}/jobs/${id(jobId)}/stop`, { method: "PUT" })));
7402
+ job.command("delete").description("Delete a job and all of its assets").argument("<job-id>", "Job ID").action(run("job-delete", async (http, jobId) => {
7403
+ await http.saucelabs(`/rest/v1/${id(http.saucelabsUsername())}/jobs/${id(jobId)}`, { method: "DELETE" });
7404
+ return { jobId, deleted: true };
7405
+ }));
7406
+ job.command("assets").description("List the asset files a job produced (logs, video, screenshots) \u2014 names only, no download").argument("<job-id>", "Job ID").action(run("job-assets", async (http, jobId) => {
7407
+ const data = await http.saucelabs(
7408
+ `/rest/v1/${id(http.saucelabsUsername())}/jobs/${id(jobId)}/assets`
7409
+ );
7410
+ return { jobId, assets: data };
7411
+ }));
7412
+ const rdcJob = sauce.command("rdc-job").description("Test jobs that ran on real devices");
7413
+ rdcJob.command("list").description("List real-device jobs").option("--limit <n>", "Maximum number of jobs to return").option("--offset <n>", "Number of jobs to skip").option("--live", "Only manual (live) tests").action(run("rdc-job-list", (http, opts) => http.saucelabs("/v1/rdc/jobs", {
7414
+ params: {
7415
+ limit: parseIntOption(opts.limit, "--limit"),
7416
+ offset: parseIntOption(opts.offset, "--offset"),
7417
+ type: opts.live ? "LIVE" : void 0
7418
+ }
7419
+ })));
7420
+ rdcJob.command("get").description("Get one real-device job: device, result, timings, and log URLs").argument("<job-id>", "Job ID").action(run("rdc-job-get", (http, jobId) => http.saucelabs(`/v1/rdc/jobs/${id(jobId)}`)));
7421
+ rdcJob.command("update").description("Update a real-device job's name, build, tags, or result").argument("<job-id>", "Job ID").option("--name <name>", "Job name").option("--build <build>", "Build name").option("--tags <tags>", "Comma-separated tags; replaces the existing tags", collectList).option("--passed", "Mark the job as passed").option("--failed", "Mark the job as failed").action(run("rdc-job-update", (http, jobId, opts) => {
7422
+ const body = {};
7423
+ if (opts.name !== void 0) body["name"] = opts.name;
7424
+ if (opts.build !== void 0) body["build"] = opts.build;
7425
+ if (opts.tags !== void 0) body["tags"] = opts.tags;
7426
+ const passed = passedFlag(opts);
7427
+ if (passed !== void 0) body["passed"] = passed;
7428
+ return http.saucelabs(`/v1/rdc/jobs/${id(jobId)}`, {
7429
+ method: "PUT",
7430
+ body: requireBody(body, "Pass at least one of --name, --build, --tags, --passed/--failed.")
7431
+ });
7432
+ }));
7433
+ rdcJob.command("stop").description("Stop a running real-device job").argument("<job-id>", "Job ID").action(run("rdc-job-stop", async (http, jobId) => {
7434
+ await http.saucelabs(`/v1/rdc/jobs/${id(jobId)}/stop`, { method: "PUT" });
7435
+ return { jobId, stopped: true };
7436
+ }));
7437
+ rdcJob.command("delete").description("Delete a real-device job (owner, team admin, or org admin only)").argument("<job-id>", "Job ID").action(run("rdc-job-delete", async (http, jobId) => {
7438
+ await http.saucelabs(`/v1/rdc/jobs/${id(jobId)}`, { method: "DELETE" });
7439
+ return { jobId, deleted: true };
7440
+ }));
7441
+ const build = sauce.command("build").description("Builds (groups of jobs) on virtual (vdc) or real (rdc) devices");
7442
+ build.command("list").description("List builds").option("--source <source>", "Build source: vdc (virtual devices) or rdc (real devices)", "vdc").option("--status <status>", "Filter by status (repeatable or comma-separated): running, error, failed, complete, success", collectList).option("--name <name>", "Filter by build name").option("--start <time>", "Only builds started after this time (Unix seconds or ISO 8601)").option("--end <time>", "Only builds ended before this time (Unix seconds or ISO 8601)").option("--limit <n>", "Maximum number of builds to return").option("--offset <n>", "Number of builds to skip").option("--sort <order>", "Sort by creation time: asc or desc").action(run("build-list", async (http, opts) => {
7443
+ const source = parseChoice(opts.source, BUILD_SOURCES, "--source");
7444
+ const statuses = opts.status?.map((s) => parseChoice(s, BUILD_STATUSES, "--status"));
7445
+ const data = await http.saucelabs(withRepeatedParam(`/v2/builds/${source}/`, "status", statuses), {
7446
+ params: {
7447
+ name: opts.name,
7448
+ start: parseUnixTime(opts.start, "--start"),
7449
+ end: parseUnixTime(opts.end, "--end"),
7450
+ limit: parseIntOption(opts.limit, "--limit"),
7451
+ offset: parseIntOption(opts.offset, "--offset"),
7452
+ sort: parseChoice(opts.sort, ["asc", "desc"], "--sort")
7453
+ }
7454
+ });
7455
+ return { source, count: countOf(data, "builds"), builds: data?.builds ?? [] };
7456
+ }));
7457
+ build.command("get").description("Get one build with its job counts").argument("<build-id>", "Build ID").option("--source <source>", "Build source: vdc (virtual devices) or rdc (real devices)", "vdc").action(run("build-get", (http, buildId, opts) => {
7458
+ const source = parseChoice(opts.source, BUILD_SOURCES, "--source");
7459
+ return http.saucelabs(`/v2/builds/${source}/${id(buildId)}/`);
7460
+ }));
7461
+ build.command("jobs").description("List the jobs in a build").argument("<build-id>", "Build ID").option("--source <source>", "Build source: vdc (virtual devices) or rdc (real devices)", "vdc").option("--failed", "Only failed jobs").option("--errored", "Only errored jobs").option("--passed", "Only passed jobs").option("--running", "Only running jobs").option("--queued", "Only queued jobs").option("--completed", "Only completed jobs").option("--finished", "Only finished jobs").option("--faulty", "Only faulty (failed or errored) jobs").option("--modified-since <time>", "Only jobs modified since this ISO 8601 time").action(run("build-jobs", async (http, buildId, opts) => {
7462
+ const source = parseChoice(opts.source, BUILD_SOURCES, "--source");
7463
+ const params = { modified_since: opts["modifiedSince"] };
7464
+ for (const flag of ["failed", "errored", "passed", "running", "queued", "completed", "finished", "faulty"]) {
7465
+ if (opts[flag]) params[flag] = true;
7466
+ }
7467
+ const data = await http.saucelabs(`/v2/builds/${source}/${id(buildId)}/jobs/`, { params });
7468
+ return { buildId, source, count: countOf(data, "jobs"), jobs: data?.jobs ?? [] };
7469
+ }));
7470
+ build.command("for-job").description("Get the build a job belongs to").argument("<job-id>", "Job ID").option("--source <source>", "Build source: vdc (virtual devices) or rdc (real devices)", "vdc").action(run("build-for-job", (http, jobId, opts) => {
7471
+ const source = parseChoice(opts.source, BUILD_SOURCES, "--source");
7472
+ return http.saucelabs(`/v2/builds/${source}/jobs/${id(jobId)}/build/`);
7473
+ }));
7474
+ const device = sauce.command("device").description("Real-device catalog and live availability");
7475
+ device.command("list").description("List real devices your account can use").option("--name <regex>", 'Device name (regex), for example "iPhone.*" or "Samsung Galaxy S2[34].*"').option("--os <os>", "Operating system: android or ios").option("--os-version <version>", 'OS major version or regex, for example 17 or "1[78]"').option("--type <type>", "Device type: phone or tablet").action(run("device-list", async (http, opts) => {
7476
+ const data = await http.saucelabs(`${RDA}/devices`, {
7477
+ params: {
7478
+ deviceName: opts.name,
7479
+ os: parseChoice(opts.os, DEVICE_OS, "--os"),
7480
+ osVersion: opts.osVersion,
7481
+ deviceType: parseChoice(opts.type, DEVICE_TYPES, "--type")
7482
+ }
7483
+ });
7484
+ return { count: countOf(data), devices: data };
7485
+ }));
7486
+ device.command("get").description("Get one device's full descriptor (hardware, OS, screen)").argument("<device-id>", "Device ID, for example iPhone_15_real").action(run("device-get", (http, deviceId) => http.saucelabs(`/v1/rdc/devices/${id(deviceId)}`)));
7487
+ device.command("status").description("Live availability of real devices (AVAILABLE, IN_USE, CLEANING, ...)").option("--state <state>", "Only devices in this state: available, in_use, cleaning, maintenance, rebooting, offline").option("--name <regex>", "Device name (regex)").option("--private-only", "Only your organization's private devices").action(run("device-status", async (http, opts) => {
7488
+ const data = await http.saucelabs(`${RDA}/devices/status`, {
7489
+ params: {
7490
+ state: parseChoice(opts.state, DEVICE_STATES, "--state"),
7491
+ deviceName: opts.name,
7492
+ privateOnly: opts.privateOnly ? true : void 0
7493
+ }
7494
+ });
7495
+ return { count: countOf(data, "devices"), devices: data?.devices ?? [] };
7496
+ }));
7497
+ const session = sauce.command("session").description("Reserve a real device and drive it over the API (open URLs, install apps, ADB shell, tests)");
7498
+ const sessionPath = (sessionId) => `${RDA}/sessions/${id(sessionId)}`;
7499
+ const deviceAction = (sessionId, action) => `${sessionPath(sessionId)}/device/${action}`;
7500
+ session.command("list").description("List your device sessions").option("--state <state>", "Only sessions in this state: pending, creating, active, closing, closed, errored").option("--device-name <regex>", "Device name (regex)").action(run("session-list", async (http, opts) => {
7501
+ const data = await http.saucelabs(`${RDA}/sessions`, {
7502
+ params: { state: parseChoice(opts.state, SESSION_STATES, "--state"), deviceName: opts.deviceName }
7503
+ });
7504
+ return { count: countOf(data, "sessions"), sessions: data?.sessions ?? [] };
7505
+ }));
7506
+ session.command("create").description("Reserve a real device. Returns immediately in PENDING state unless --wait is passed").option("--device-name <regex>", 'Device name or regex, for example "iPhone 15.*" (any device when omitted)').option("--os <os>", "Operating system: android or ios").option("--tunnel-name <name>", "Route device traffic through this Sauce Connect tunnel").option("--tunnel-owner <username>", "Owner of a shared tunnel").option("--duration <iso8601>", "Session length as an ISO 8601 duration, for example PT30M (default 6h; public devices cap at 1h)").option("--wait", "Poll until the session is ACTIVE (or fails) before returning").option("--timeout <seconds>", "Maximum seconds to wait with --wait", "300").action(run("session-create", async (http, opts) => {
7507
+ if (opts.tunnelOwner && !opts.tunnelName) throw new PncliError("--tunnel-owner requires --tunnel-name.", 1);
7508
+ const timeoutSeconds = parseIntOption(opts.timeout, "--timeout") ?? 300;
7509
+ const deviceQuery = {};
7510
+ if (opts.deviceName) deviceQuery["deviceName"] = opts.deviceName;
7511
+ const os4 = parseChoice(opts.os, ["android", "ios"], "--os");
7512
+ if (os4) deviceQuery["os"] = os4;
7513
+ const configuration = {};
7514
+ if (opts.tunnelName) {
7515
+ configuration["tunnel"] = { name: opts.tunnelName, ...opts.tunnelOwner ? { owner: opts.tunnelOwner } : {} };
7516
+ }
7517
+ if (opts.duration) configuration["sessionDuration"] = opts.duration;
7518
+ const body = { device: deviceQuery };
7519
+ if (Object.keys(configuration).length > 0) body["configuration"] = configuration;
7520
+ const created = await http.saucelabs(`${RDA}/sessions`, { method: "POST", body });
7521
+ if (!opts.wait) return created;
7522
+ return waitForSession(http, created.id, timeoutSeconds);
7523
+ }));
7524
+ session.command("get").description("Get a session: state, device, expiry, Appium URL, and live-view link").argument("<session-id>", "Session ID").action(run("session-get", (http, sessionId) => http.saucelabs(sessionPath(sessionId))));
7525
+ session.command("delete").description("Close a session and release the device").argument("<session-id>", "Session ID").option("--reboot", "Reboot the device after closing (private devices only)").action(run("session-delete", (http, sessionId, opts) => http.saucelabs(sessionPath(sessionId), {
7526
+ method: "DELETE",
7527
+ params: { rebootDevice: opts.reboot ? true : void 0 }
7528
+ })));
7529
+ session.command("open-url").description("Open a URL on the device (Chrome on Android; Safari or a deep link on iOS)").argument("<session-id>", "Session ID").argument("<url>", "URL to open").action(run("session-open-url", async (http, sessionId, url) => {
7530
+ await http.saucelabs(deviceAction(sessionId, "openUrl"), { method: "POST", body: { url } });
7531
+ return { sessionId, url, opened: true };
7532
+ }));
7533
+ session.command("shell").description("Run an ADB shell command on an Android device (public devices allow only a safe subset)").argument("<session-id>", "Session ID").argument("<command>", 'Shell command without the "adb shell" prefix, for example "getprop ro.build.version.release"').action(run("session-shell", async (http, sessionId, command) => {
7534
+ const data = await http.saucelabs(deviceAction(sessionId, "executeShellCommand"), {
7535
+ method: "POST",
7536
+ body: { adbShellCommand: command },
7537
+ timeoutMs: 12e4
7538
+ });
7539
+ return { sessionId, command, stdout: data?.stdout ?? "" };
7540
+ }));
7541
+ session.command("install-app").description("Install an app from Sauce app storage onto the device (runs in the background)").argument("<session-id>", "Session ID").argument("<app>", "App reference: storage:<file-id> or storage:filename=<name>").option("--launch", "Launch the app once installed").option("--no-instrumentation", "Install without Sauce instrumentation").action(run("session-install-app", (http, sessionId, app, opts) => http.saucelabs(deviceAction(sessionId, "installApp"), {
7542
+ method: "POST",
7543
+ body: { app, enableInstrumentation: opts.instrumentation, launchAfterInstall: Boolean(opts.launch) }
7544
+ })));
7545
+ session.command("installations").description("List app installations on the device and their status").argument("<session-id>", "Session ID").action(run("session-installations", (http, sessionId) => http.saucelabs(deviceAction(sessionId, "listAppInstallations"), { method: "POST" })));
7546
+ const appIdentity = (opts) => {
7547
+ if (Boolean(opts.bundleId) === Boolean(opts.packageName)) {
7548
+ throw new PncliError("Pass exactly one of --bundle-id (iOS) or --package-name (Android).", 1);
7549
+ }
7550
+ return opts.bundleId ? { bundleId: opts.bundleId } : { packageName: opts.packageName };
7551
+ };
7552
+ session.command("launch-app").description("Launch an installed app").argument("<session-id>", "Session ID").option("--bundle-id <id>", "iOS bundle ID").option("--package-name <name>", "Android package name").option("--activity <name>", "Android activity to launch").action(run("session-launch-app", async (http, sessionId, opts) => {
7553
+ if (opts.activity && !opts.packageName) throw new PncliError("--activity requires --package-name.", 1);
7554
+ const body = { ...appIdentity(opts), ...opts.activity ? { activityName: opts.activity } : {} };
7555
+ await http.saucelabs(deviceAction(sessionId, "launchApp"), { method: "POST", body });
7556
+ return { sessionId, ...body, launched: true };
7557
+ }));
7558
+ session.command("uninstall-app").description("Uninstall an app").argument("<session-id>", "Session ID").option("--bundle-id <id>", "iOS bundle ID").option("--package-name <name>", "Android package name").action(run("session-uninstall-app", async (http, sessionId, opts) => {
7559
+ const body = appIdentity(opts);
7560
+ await http.saucelabs(deviceAction(sessionId, "uninstallApp"), { method: "POST", body });
7561
+ return { sessionId, ...body, uninstalled: true };
7562
+ }));
7563
+ session.command("settings").description("Change device settings: orientation (both), locale and animations (Android only)").argument("<session-id>", "Session ID").option("--orientation <orientation>", "Screen orientation: portrait or landscape").option("--locale <locale>", "Locale, for example en_US (Android only)").option("--animations <on|off>", "Enable or disable system animations (Android only)").action(run("session-settings", async (http, sessionId, opts) => {
7564
+ const body = {};
7565
+ const orientation = parseChoice(opts.orientation, ORIENTATIONS, "--orientation");
7566
+ if (orientation) body["orientation"] = orientation;
7567
+ if (opts.locale) body["locale"] = opts.locale;
7568
+ const animations = parseChoice(opts.animations, ["on", "off"], "--animations");
7569
+ if (animations) body["animations"] = animations === "on";
7570
+ await http.saucelabs(deviceAction(sessionId, "applySettings"), {
7571
+ method: "POST",
7572
+ body: requireBody(body, "Pass at least one of --orientation, --locale, --animations.")
7573
+ });
7574
+ return { sessionId, applied: body };
7575
+ }));
7576
+ session.command("start-test").description("Start recording a test (a job) inside the session").argument("<session-id>", "Session ID").option("--name <name>", "Test name").option("--build <build>", "Build name").option("--tags <tags>", "Comma-separated tags", collectList).option("--video", "Record video").option("--device-logs", "Capture device logs").option("--network-capture", "Capture network traffic").option("--screenshots", "Capture screenshots").option("--appium-logs", "Capture Appium logs").action(run("session-start-test", (http, sessionId, opts) => {
7577
+ const body = {};
7578
+ if (opts.name) body["testName"] = opts.name;
7579
+ if (opts.build) body["build"] = opts.build;
7580
+ if (opts.tags) body["tags"] = opts.tags;
7581
+ const artifacts = {
7582
+ video: Boolean(opts.video),
7583
+ deviceLogs: Boolean(opts.deviceLogs),
7584
+ networkCapture: Boolean(opts.networkCapture),
7585
+ screenshots: Boolean(opts.screenshots),
7586
+ appiumLogs: Boolean(opts.appiumLogs)
7587
+ };
7588
+ if (Object.values(artifacts).some(Boolean)) body["artifacts"] = artifacts;
7589
+ return http.saucelabs(`${sessionPath(sessionId)}/startTest`, { method: "POST", body });
7590
+ }));
7591
+ session.command("end-test").description("End the active test in the session, optionally recording its result").argument("<session-id>", "Session ID").option("--passed", "Mark the test as passed").option("--failed", "Mark the test as failed").action(run("session-end-test", (http, sessionId, opts) => {
7592
+ const passed = passedFlag(opts);
7593
+ return http.saucelabs(`${sessionPath(sessionId)}/endTest`, {
7594
+ method: "POST",
7595
+ body: passed === void 0 ? {} : { status: passed ? "passed" : "failed" }
7596
+ });
7597
+ }));
7598
+ session.command("tests").description("List the tests recorded in a session, with links to each job").argument("<session-id>", "Session ID").action(run("session-tests", (http, sessionId) => http.saucelabs(`${sessionPath(sessionId)}/tests`)));
7599
+ session.command("network-profiles").description("List the network throttling profiles available to a session").argument("<session-id>", "Session ID").action(run("session-network-profiles", (http, sessionId) => http.saucelabs(`${sessionPath(sessionId)}/network/profiles`)));
7600
+ session.command("network").description("Throttle the device network with a named profile or explicit conditions, or --reset to clear").argument("<session-id>", "Session ID").option("--profile <name>", "Named profile from network-profiles, for example 4G-fast").option("--download <kbps>", "Download speed in kbps (0-50000)").option("--upload <kbps>", "Upload speed in kbps (0-50000)").option("--latency <ms>", "Added latency in ms (0-3000)").option("--loss <percent>", "Packet loss percent (0-100)").option("--reset", "Remove all throttling").action(run("session-network", async (http, sessionId, opts) => {
7601
+ const conditions = {};
7602
+ const set = (key, value, flag, max) => {
7603
+ const n = parseIntOption(value, flag);
7604
+ if (n === void 0) return;
7605
+ if (n > max) throw new PncliError(`${flag} must be between 0 and ${max}.`, 1);
7606
+ conditions[key] = n;
7607
+ };
7608
+ set("downloadSpeed", opts.download, "--download", 5e4);
7609
+ set("uploadSpeed", opts.upload, "--upload", 5e4);
7610
+ set("latency", opts.latency, "--latency", 3e3);
7611
+ set("loss", opts.loss, "--loss", 100);
7612
+ const modes = [Boolean(opts.reset), Boolean(opts.profile), Object.keys(conditions).length > 0].filter(Boolean).length;
7613
+ if (modes !== 1) {
7614
+ throw new PncliError("Pass exactly one of --profile, --reset, or explicit conditions (--download/--upload/--latency/--loss).", 1);
7615
+ }
7616
+ if (opts.reset) {
7617
+ await http.saucelabs(`${sessionPath(sessionId)}/network/condition`, { method: "DELETE" });
7618
+ return { sessionId, reset: true };
7619
+ }
7620
+ if (opts.profile) {
7621
+ return http.saucelabs(`${sessionPath(sessionId)}/network/profile`, { method: "POST", body: { networkProfile: opts.profile } });
7622
+ }
7623
+ return http.saucelabs(`${sessionPath(sessionId)}/network/condition`, { method: "POST", body: { networkConditions: conditions } });
7624
+ }));
7625
+ session.command("appium").description("Get the session's Appium server URL, or --start one").argument("<session-id>", "Session ID").option("--start", "Start an Appium server for the session").option("--appium-version <version>", "Appium version to start (see appium-versions)").action(run("session-appium", (http, sessionId, opts) => {
7626
+ if (opts.appiumVersion && !opts.start) throw new PncliError("--appium-version requires --start.", 1);
7627
+ if (!opts.start) return http.saucelabs(`${sessionPath(sessionId)}/appiumserver`);
7628
+ return http.saucelabs(`${sessionPath(sessionId)}/appiumserver`, {
7629
+ method: "POST",
7630
+ body: opts.appiumVersion ? { appiumVersion: opts.appiumVersion } : {}
7631
+ });
7632
+ }));
7633
+ sauce.command("appium-versions").description("Appium versions available for real-device sessions, with end-of-life dates").action(run("appium-versions", (http) => http.saucelabs(`${RDA}/appium/versions`)));
7634
+ const tunnel = sauce.command("tunnel").description("Sauce Connect tunnels");
7635
+ tunnel.command("list").description("List your tunnels with full details").option("--all", "Include tunnels shared with you").action(run("tunnel-list", async (http, opts) => {
7636
+ const data = await http.saucelabs(`/rest/v1/${id(http.saucelabsUsername())}/tunnels`, {
7637
+ params: { full: true, all: opts.all ? true : void 0 }
7638
+ });
7639
+ return { count: countOf(data), tunnels: data };
7640
+ }));
7641
+ tunnel.command("get").description("Get one tunnel").argument("<tunnel-id>", "Tunnel ID").action(run("tunnel-get", (http, tunnelId) => http.saucelabs(`/rest/v1/${id(http.saucelabsUsername())}/tunnels/${id(tunnelId)}`)));
7642
+ tunnel.command("jobs").description("Number of jobs currently running through a tunnel").argument("<tunnel-id>", "Tunnel ID").action(run("tunnel-jobs", (http, tunnelId) => http.saucelabs(`/rest/v1/${id(http.saucelabsUsername())}/tunnels/${id(tunnelId)}/num_jobs`)));
7643
+ tunnel.command("stop").description("Shut down a tunnel").argument("<tunnel-id>", "Tunnel ID").action(run("tunnel-stop", (http, tunnelId) => http.saucelabs(`/rest/v1/${id(http.saucelabsUsername())}/tunnels/${id(tunnelId)}`, { method: "DELETE" })));
7644
+ const storage = sauce.command("storage").description("App storage metadata (no upload or download)");
7645
+ storage.command("files").description("List uploaded app files").option("--query <text>", "Search file names and app metadata").option("--kind <kind>", "App kind: android or ios").option("--page <n>", "Page number (starts at 1)").option("--per-page <n>", "Results per page (max 100)").action(run("storage-files", (http, opts) => http.saucelabs("/v1/storage/files", {
7646
+ params: {
7647
+ q: opts.query,
7648
+ kind: parseChoice(opts.kind, STORAGE_KINDS, "--kind"),
7649
+ page: parseIntOption(opts.page, "--page"),
7650
+ per_page: parseIntOption(opts.perPage, "--per-page")
7651
+ }
7652
+ })));
7653
+ storage.command("groups").description("List app groups (all versions of one app)").option("--query <text>", "Search group names").option("--kind <kind>", "App kind: android or ios").option("--page <n>", "Page number (starts at 1)").option("--per-page <n>", "Results per page (max 100)").action(run("storage-groups", (http, opts) => http.saucelabs("/v1/storage/groups", {
7654
+ params: {
7655
+ q: opts.query,
7656
+ kind: parseChoice(opts.kind, STORAGE_KINDS, "--kind"),
7657
+ page: parseIntOption(opts.page, "--page"),
7658
+ per_page: parseIntOption(opts.perPage, "--per-page")
7659
+ }
7660
+ })));
7661
+ const team = sauce.command("team").description("Teams in your organization");
7662
+ team.command("list").description("List teams").option("--name <prefix>", "Team name prefix").action(run("team-list", (http, opts) => http.saucelabs(`${TEAM_MGMT}/teams/`, { params: { name: opts.name } })));
7663
+ team.command("get").description("Get one team and its settings").argument("<team-id>", "Team ID").action(run("team-get", (http, teamId) => http.saucelabs(`${TEAM_MGMT}/teams/${id(teamId)}/`)));
7664
+ team.command("members").description("List a team's members").argument("<team-id>", "Team ID").action(run("team-members", (http, teamId) => http.saucelabs(`${TEAM_MGMT}/teams/${id(teamId)}/members/`)));
7665
+ sauce.command("users").description("Look up users in your organization").option("--username <username>", "Exact username").option("--phrase <text>", "Search username, name, and email").option("--limit <n>", "Maximum results (default 20, max 100)").option("--offset <n>", "Number of results to skip").action(run("users", (http, opts) => http.saucelabs(`${TEAM_MGMT}/users/`, {
7666
+ params: {
7667
+ username: opts.username,
7668
+ phrase: opts.phrase,
7669
+ limit: parseIntOption(opts.limit, "--limit"),
7670
+ offset: parseIntOption(opts.offset, "--offset")
7671
+ }
7672
+ })));
7673
+ }
7674
+ var TERMINAL_SESSION_STATES = /* @__PURE__ */ new Set(["CLOSING", "CLOSED", "ERRORED"]);
7675
+ async function waitForSession(http, sessionId, timeoutSeconds, pollMs = 5e3) {
7676
+ const deadline = Date.now() + timeoutSeconds * 1e3;
7677
+ for (; ; ) {
7678
+ const current = await http.saucelabs(`${RDA}/sessions/${id(sessionId)}`);
7679
+ if (current.state === "ACTIVE") return current;
7680
+ if (TERMINAL_SESSION_STATES.has(current.state)) {
7681
+ throw new PncliError(`Session ${sessionId} ended in state ${current.state} before becoming ACTIVE.`, 1);
7682
+ }
7683
+ if (Date.now() + pollMs > deadline) {
7684
+ throw new PncliError(
7685
+ `Session ${sessionId} still ${current.state} after ${timeoutSeconds}s. It keeps starting in the background \u2014 check it with: pncli saucelabs session get ${sessionId}`,
7686
+ 1
7687
+ );
7688
+ }
7689
+ await new Promise((resolve) => setTimeout(resolve, pollMs));
7690
+ }
7691
+ }
7692
+
7275
7693
  // src/services/config/check.ts
7276
7694
  async function runCredentialChecks(cfg, http) {
7277
7695
  const results = {};
@@ -7602,6 +8020,20 @@ async function runCredentialChecks(cfg, http) {
7602
8020
  results.alation = categorize(err);
7603
8021
  }
7604
8022
  }
8023
+ if (!cfg.saucelabs.accessKey) {
8024
+ results.saucelabs = { status: "blank", message: "not configured" };
8025
+ } else if (!cfg.saucelabs.baseUrl) {
8026
+ results.saucelabs = { status: "error", message: "baseUrl not configured" };
8027
+ } else if (!cfg.saucelabs.username) {
8028
+ results.saucelabs = { status: "error", message: "username not configured" };
8029
+ } else {
8030
+ try {
8031
+ await verifySauceLabsCredentials(http, 1e4);
8032
+ results.saucelabs = { status: "valid", message: "ok" };
8033
+ } catch (err) {
8034
+ results.saucelabs = categorize(err);
8035
+ }
8036
+ }
7605
8037
  return results;
7606
8038
  }
7607
8039
 
@@ -10509,6 +10941,20 @@ function registerConfigCommands(program2) {
10509
10941
  } else {
10510
10942
  results.alation = { ok: null, message: "not configured" };
10511
10943
  }
10944
+ if (cfg.saucelabs.accessKey && !cfg.saucelabs.baseUrl) {
10945
+ results.saucelabs = { ok: false, message: "baseUrl not configured" };
10946
+ } else if (cfg.saucelabs.accessKey && !cfg.saucelabs.username) {
10947
+ results.saucelabs = { ok: false, message: "username not configured" };
10948
+ } else if (cfg.saucelabs.accessKey) {
10949
+ try {
10950
+ await verifySauceLabsCredentials(http);
10951
+ results.saucelabs = { ok: true, message: "connected" };
10952
+ } catch (err) {
10953
+ results.saucelabs = { ok: false, message: err instanceof Error ? err.message : String(err) };
10954
+ }
10955
+ } else {
10956
+ results.saucelabs = { ok: null, message: "not configured" };
10957
+ }
10512
10958
  success(results, "config", "test", start);
10513
10959
  } catch (err) {
10514
10960
  fail(err, "config", "test", start);
@@ -10551,7 +10997,8 @@ function registerConfigCommands(program2) {
10551
10997
  "logscale",
10552
10998
  "splitio",
10553
10999
  "figma",
10554
- "alation"
11000
+ "alation",
11001
+ "saucelabs"
10555
11002
  ];
10556
11003
  if (cmdOpts.output === "table") {
10557
11004
  const labelWidth = Math.max(14, ...allServices.map((s) => s.length + 2));
@@ -11208,6 +11655,48 @@ async function initGlobalConfig(start) {
11208
11655
  }
11209
11656
  }
11210
11657
  }
11658
+ process.stderr.write("\n\u2500\u2500 Sauce Labs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
11659
+ const useSaucelabs = await confirm({
11660
+ message: "Configure Sauce Labs for jobs, builds, and real-device access?",
11661
+ default: false
11662
+ });
11663
+ let saucelabsBaseUrl = "";
11664
+ let saucelabsUsername = "";
11665
+ let saucelabsAccessKey = "";
11666
+ if (useSaucelabs) {
11667
+ process.stderr.write(" Find your username and access key in Sauce Labs under Account \u2192 User Settings.\n");
11668
+ process.stderr.write(" The API host depends on your data center: https://api.us-west-1.saucelabs.com,\n");
11669
+ process.stderr.write(" https://api.eu-central-1.saucelabs.com, or https://api.us-east-4.saucelabs.com.\n");
11670
+ saucelabsBaseUrl = await input2({
11671
+ message: "Sauce Labs API base URL:",
11672
+ validate: (v) => v.trim().length > 0 || "Required"
11673
+ });
11674
+ saucelabsUsername = await input2({
11675
+ message: "Sauce Labs username:",
11676
+ validate: (v) => v.trim().length > 0 || "Required"
11677
+ });
11678
+ saucelabsAccessKey = await password2({
11679
+ message: "Sauce Labs access key:"
11680
+ });
11681
+ if (saucelabsBaseUrl && saucelabsUsername && saucelabsAccessKey) {
11682
+ process.stderr.write("\n Verifying connection...\n");
11683
+ try {
11684
+ const tempConfig = {
11685
+ ...loadConfig(),
11686
+ saucelabs: {
11687
+ baseUrl: normalizeBaseUrl(saucelabsBaseUrl),
11688
+ username: saucelabsUsername.trim(),
11689
+ accessKey: saucelabsAccessKey
11690
+ }
11691
+ };
11692
+ await verifySauceLabsCredentials(createHttpClient(tempConfig));
11693
+ process.stderr.write(" Connected.\n");
11694
+ } catch (err) {
11695
+ warn(`Could not connect to Sauce Labs: ${err instanceof Error ? err.message : String(err)}`);
11696
+ warn("Config will be saved anyway. Check your data center URL, username, and access key and re-run pncli config init or pncli config test.");
11697
+ }
11698
+ }
11699
+ }
11211
11700
  process.stderr.write("\n\u2500\u2500 Defaults \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
11212
11701
  const jiraProject = await input2({
11213
11702
  message: "Default Jira project key (optional):",
@@ -11377,6 +11866,13 @@ async function initGlobalConfig(start) {
11377
11866
  userId: alationUserId.trim() || void 0
11378
11867
  }
11379
11868
  } : {},
11869
+ ...useSaucelabs && saucelabsBaseUrl ? {
11870
+ saucelabs: {
11871
+ baseUrl: normalizeBaseUrl(saucelabsBaseUrl),
11872
+ username: saucelabsUsername.trim() || void 0,
11873
+ accessKey: saucelabsAccessKey || void 0
11874
+ }
11875
+ } : {},
11380
11876
  defaults: {
11381
11877
  jira: {
11382
11878
  project: jiraProject || void 0
@@ -11993,9 +12489,9 @@ var AdoBuildClient = class {
11993
12489
  );
11994
12490
  });
11995
12491
  }
11996
- async getDefinition(collection, project, id) {
12492
+ async getDefinition(collection, project, id2) {
11997
12493
  return this.http.ado(
11998
- `/${encodeURIComponent(collection)}/${encodeURIComponent(project)}/_apis/build/definitions/${id}?api-version=${API8}`
12494
+ `/${encodeURIComponent(collection)}/${encodeURIComponent(project)}/_apis/build/definitions/${id2}?api-version=${API8}`
11999
12495
  );
12000
12496
  }
12001
12497
  // ── Builds (runs) ─────────────────────────────────────────────────
@@ -12605,7 +13101,7 @@ function registerAdoRepoCommands(ado) {
12605
13101
  const start = Date.now();
12606
13102
  try {
12607
13103
  const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
12608
- const reviewers = opts.reviewers ? opts.reviewers.split(",").map((id) => ({ id: id.trim() })) : [];
13104
+ const reviewers = opts.reviewers ? opts.reviewers.split(",").map((id2) => ({ id: id2.trim() })) : [];
12609
13105
  const data = await gitClient.createPR(collection, project, repo2, {
12610
13106
  title: opts.title,
12611
13107
  description: opts.description,
@@ -12623,7 +13119,7 @@ function registerAdoRepoCommands(ado) {
12623
13119
  const start = Date.now();
12624
13120
  try {
12625
13121
  const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
12626
- const reviewers = opts.reviewers ? opts.reviewers.split(",").map((id) => ({ id: id.trim() })) : void 0;
13122
+ const reviewers = opts.reviewers ? opts.reviewers.split(",").map((id2) => ({ id: id2.trim() })) : void 0;
12627
13123
  const body = {};
12628
13124
  if (opts.title) body.title = opts.title;
12629
13125
  if (opts.description !== void 0) body.description = opts.description;
@@ -12810,11 +13306,11 @@ function registerAdoRepoCommands(ado) {
12810
13306
  const start = Date.now();
12811
13307
  try {
12812
13308
  const globalOpts = ado.optsWithGlobals();
12813
- const { loadConfig: lc } = await import("./config-OH2O5E3P.js");
13309
+ const { loadConfig: lc } = await import("./config-5LC7ZTEP.js");
12814
13310
  const config = lc({ configPath: globalOpts.config });
12815
13311
  const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
12816
13312
  const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
12817
- const { createHttpClient: createHttpClient2 } = await import("./http-V7PZD7LE.js");
13313
+ const { createHttpClient: createHttpClient2 } = await import("./http-DJSGEGD6.js");
12818
13314
  const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
12819
13315
  const coreClient = new AdoCoreClient2(http);
12820
13316
  const me = await coreClient.getConnectionData(collection);
@@ -13489,8 +13985,8 @@ var CheckmarxClient = class {
13489
13985
  }
13490
13986
  return projects;
13491
13987
  }
13492
- async getProject(id) {
13493
- return this.http.checkmarx(`projects/${id}`);
13988
+ async getProject(id2) {
13989
+ return this.http.checkmarx(`projects/${id2}`);
13494
13990
  }
13495
13991
  async listScans(opts = {}) {
13496
13992
  const params = { limit: opts.last ?? 100 };
@@ -13503,8 +13999,8 @@ var CheckmarxClient = class {
13503
13999
  }
13504
14000
  return scans;
13505
14001
  }
13506
- async getScan(id) {
13507
- return this.http.checkmarx(`scans/${id}`);
14002
+ async getScan(id2) {
14003
+ return this.http.checkmarx(`scans/${id2}`);
13508
14004
  }
13509
14005
  async getScanResultsStatistics(scanId) {
13510
14006
  return this.http.checkmarx("results/summary", {
@@ -13820,7 +14316,7 @@ function resolveCluster(config, env, instance) {
13820
14316
  }
13821
14317
  return { baseUrl: config.openshift.baseUrl, token: config.openshift.token };
13822
14318
  }
13823
- function getHttp(oc) {
14319
+ function getHttp2(oc) {
13824
14320
  const opts = oc.optsWithGlobals();
13825
14321
  const config = loadConfig({ configPath: opts.config });
13826
14322
  const cluster = resolveCluster(config, opts.env, opts.instance);
@@ -13896,7 +14392,7 @@ function registerOpenShiftCommands(program2) {
13896
14392
  oc.command("pods").description("List pod health summary for a namespace \u2014 pre-processed for LLM analysis. Use --raw to return the full unmodified Kubernetes pod objects.").requiredOption("--namespace <ns>", "Kubernetes namespace").option("--label-selector <selector>", "Label selector (e.g. app=my-app)").option("--raw", "Return full Kubernetes pod objects without summarization (includes env vars, images, node name, annotations, resources, etc.)", false).action(async (opts) => {
13897
14393
  const start = Date.now();
13898
14394
  try {
13899
- const http = getHttp(oc);
14395
+ const http = getHttp2(oc);
13900
14396
  const params = {};
13901
14397
  if (opts.labelSelector) params["labelSelector"] = opts.labelSelector;
13902
14398
  const podList = await http.openshift(
@@ -14005,7 +14501,7 @@ function registerOpenShiftCommands(program2) {
14005
14501
  oc.command("events").description("List Warning events for a namespace \u2014 pre-filtered for LLM analysis").requiredOption("--namespace <ns>", "Kubernetes namespace").option("--field-selector <selector>", "Additional field selector (e.g. involvedObject.name=my-pod)").option("--all", "Include Normal events in addition to Warning events", false).action(async (opts) => {
14006
14502
  const start = Date.now();
14007
14503
  try {
14008
- const http = getHttp(oc);
14504
+ const http = getHttp2(oc);
14009
14505
  const params = {};
14010
14506
  const selectors = [];
14011
14507
  if (!opts.all) selectors.push("type=Warning");
@@ -14039,7 +14535,7 @@ function registerOpenShiftCommands(program2) {
14039
14535
  oc.command("logs").description("Get recent log lines from a pod container").requiredOption("--namespace <ns>", "Kubernetes namespace").requiredOption("--pod <name>", "Pod name").option("--container <name>", "Container name (defaults to first container)").option("--lines <n>", "Number of most-recent lines to return", "100").option("--previous", "Return logs from the previous container instance", false).action(async (opts) => {
14040
14536
  const start = Date.now();
14041
14537
  try {
14042
- const http = getHttp(oc);
14538
+ const http = getHttp2(oc);
14043
14539
  const lines = opts.lines ? parseInt(opts.lines, 10) : 100;
14044
14540
  const logText = await http.openshiftText(
14045
14541
  `/api/v1/namespaces/${encodeURIComponent(opts.namespace)}/pods/${encodeURIComponent(opts.pod)}/log`,
@@ -14071,7 +14567,7 @@ function registerOpenShiftCommands(program2) {
14071
14567
  oc.command("pod-metrics").description("Get CPU and memory usage for pods in a namespace (requires metrics-server)").requiredOption("--namespace <ns>", "Kubernetes namespace").action(async (opts) => {
14072
14568
  const start = Date.now();
14073
14569
  try {
14074
- const http = getHttp(oc);
14570
+ const http = getHttp2(oc);
14075
14571
  const metricsList = await http.openshift(
14076
14572
  `/apis/metrics.k8s.io/v1beta1/namespaces/${encodeURIComponent(opts.namespace)}/pods`
14077
14573
  );
@@ -14091,7 +14587,7 @@ function registerOpenShiftCommands(program2) {
14091
14587
  oc.command("resource-usage").description("Combined CPU/memory usage, limits, and requests for all pods in a namespace \u2014 all values normalized to millicores (m) and mebibytes (Mi). Requires metrics-server. Use --csv to emit a spreadsheet-friendly CSV.").requiredOption("--namespace <ns>", "Kubernetes namespace").option("--label-selector <selector>", "Label selector (e.g. app=my-app)").option("--csv", "Output as CSV instead of JSON (suitable for Excel)", false).action(async (opts) => {
14092
14588
  const start = Date.now();
14093
14589
  try {
14094
- const http = getHttp(oc);
14590
+ const http = getHttp2(oc);
14095
14591
  const nsEncoded = encodeURIComponent(opts.namespace);
14096
14592
  const podParams = {};
14097
14593
  if (opts.labelSelector) podParams["labelSelector"] = opts.labelSelector;
@@ -14156,7 +14652,7 @@ function registerOpenShiftCommands(program2) {
14156
14652
  }
14157
14653
 
14158
14654
  // src/services/dynatrace/commands.ts
14159
- function getHttp2(program2, envName) {
14655
+ function getHttp3(program2, envName) {
14160
14656
  const opts = program2.optsWithGlobals();
14161
14657
  const cfg = loadConfig({ configPath: opts.config });
14162
14658
  if (envName) {
@@ -14280,7 +14776,7 @@ function registerDynatraceCommands(program2) {
14280
14776
  const start = Date.now();
14281
14777
  try {
14282
14778
  const envName = dynatrace.opts().env;
14283
- const data = await listEntities(getHttp2(program2, envName), opts.selector, opts);
14779
+ const data = await listEntities(getHttp3(program2, envName), opts.selector, opts);
14284
14780
  success({ totalCount: data.totalCount, entities: data.items }, "dynatrace", "entities list", start);
14285
14781
  } catch (err) {
14286
14782
  fail(err, "dynatrace", "entities list", start);
@@ -14290,7 +14786,7 @@ function registerDynatraceCommands(program2) {
14290
14786
  const start = Date.now();
14291
14787
  try {
14292
14788
  const envName = dynatrace.opts().env;
14293
- const data = await getHttp2(program2, envName).dynatrace(
14789
+ const data = await getHttp3(program2, envName).dynatrace(
14294
14790
  `/api/v2/entities/${encodeURIComponent(opts.id)}`,
14295
14791
  { params: { from: opts.from, to: opts.to, fields: opts.fields } }
14296
14792
  );
@@ -14303,7 +14799,7 @@ function registerDynatraceCommands(program2) {
14303
14799
  const start = Date.now();
14304
14800
  try {
14305
14801
  const envName = dynatrace.opts().env;
14306
- const data = await listEntities(getHttp2(program2, envName), 'type("SERVICE")', opts);
14802
+ const data = await listEntities(getHttp3(program2, envName), 'type("SERVICE")', opts);
14307
14803
  success({ totalCount: data.totalCount, services: data.items }, "dynatrace", "services", start);
14308
14804
  } catch (err) {
14309
14805
  fail(err, "dynatrace", "services", start);
@@ -14314,7 +14810,7 @@ function registerDynatraceCommands(program2) {
14314
14810
  try {
14315
14811
  const envName = dynatrace.opts().env;
14316
14812
  const selector = 'type("CLOUD_APPLICATION")';
14317
- const data = await listEntities(getHttp2(program2, envName), selector, opts);
14813
+ const data = await listEntities(getHttp3(program2, envName), selector, opts);
14318
14814
  success({ totalCount: data.totalCount, workloads: data.items }, "dynatrace", "workloads", start);
14319
14815
  } catch (err) {
14320
14816
  fail(err, "dynatrace", "workloads", start);
@@ -14326,7 +14822,7 @@ function registerDynatraceCommands(program2) {
14326
14822
  try {
14327
14823
  const envName = dynatrace.opts().env;
14328
14824
  const data = await allPages(async (nextPageKey) => {
14329
- const page = await getHttp2(program2, envName).dynatrace("/api/v2/problems", {
14825
+ const page = await getHttp3(program2, envName).dynatrace("/api/v2/problems", {
14330
14826
  params: nextPageKey ? { nextPageKey } : {
14331
14827
  from: opts.from ?? "now-2h",
14332
14828
  to: opts.to ?? "now",
@@ -14346,7 +14842,7 @@ function registerDynatraceCommands(program2) {
14346
14842
  const start = Date.now();
14347
14843
  try {
14348
14844
  const envName = dynatrace.opts().env;
14349
- const data = await getHttp2(program2, envName).dynatrace(
14845
+ const data = await getHttp3(program2, envName).dynatrace(
14350
14846
  `/api/v2/problems/${encodeURIComponent(opts.id)}`,
14351
14847
  { params: { fields: opts.fields } }
14352
14848
  );
@@ -14359,7 +14855,7 @@ function registerDynatraceCommands(program2) {
14359
14855
  const start = Date.now();
14360
14856
  try {
14361
14857
  const envName = dynatrace.opts().env;
14362
- const result = await runTraceQuery(getHttp2(program2, envName), opts.id);
14858
+ const result = await runTraceQuery(getHttp3(program2, envName), opts.id);
14363
14859
  success(result, "dynatrace", "trace", start);
14364
14860
  } catch (err) {
14365
14861
  fail(err, "dynatrace", "trace", start);
@@ -14368,7 +14864,7 @@ function registerDynatraceCommands(program2) {
14368
14864
  }
14369
14865
 
14370
14866
  // src/services/logscale/commands.ts
14371
- function getHttp3(program2) {
14867
+ function getHttp4(program2) {
14372
14868
  const opts = program2.optsWithGlobals();
14373
14869
  return createHttpClient(
14374
14870
  loadConfig({ configPath: opts.config }),
@@ -14381,7 +14877,7 @@ function registerLogscaleCommands(program2) {
14381
14877
  repositories.command("list").description("List available repositories").action(async () => {
14382
14878
  const start = Date.now();
14383
14879
  try {
14384
- const data = await getHttp3(program2).logscale("/api/v1/repositories");
14880
+ const data = await getHttp4(program2).logscale("/api/v1/repositories");
14385
14881
  success({ count: data.length, repositories: data }, "logscale", "repositories list", start);
14386
14882
  } catch (err) {
14387
14883
  fail(err, "logscale", "repositories list", start);
@@ -14398,7 +14894,7 @@ function registerLogscaleCommands(program2) {
14398
14894
  showQueryEventDistribution: false,
14399
14895
  isLive: false
14400
14896
  };
14401
- const data = await getHttp3(program2).logscale(
14897
+ const data = await getHttp4(program2).logscale(
14402
14898
  `/api/v1/repositories/${encodeURIComponent(opts.repository)}/query`,
14403
14899
  {
14404
14900
  method: "POST",
@@ -14429,7 +14925,7 @@ function registerLogscaleCommands(program2) {
14429
14925
  // src/services/splitio/commands.ts
14430
14926
  import fs11 from "fs";
14431
14927
  import confirm2 from "@inquirer/confirm";
14432
- function getHttp4(program2) {
14928
+ function getHttp5(program2) {
14433
14929
  const opts = program2.optsWithGlobals();
14434
14930
  return createHttpClient(
14435
14931
  loadConfig({ configPath: opts.config }),
@@ -14464,7 +14960,7 @@ function buildCrBase(opts) {
14464
14960
  crNumber: opts.changeNumber
14465
14961
  };
14466
14962
  if (opts.approvers) {
14467
- base["approvers"] = opts.approvers.split(",").map((s) => s.trim()).filter(Boolean).map((id) => ({ id }));
14963
+ base["approvers"] = opts.approvers.split(",").map((s) => s.trim()).filter(Boolean).map((id2) => ({ id: id2 }));
14468
14964
  }
14469
14965
  return base;
14470
14966
  }
@@ -14474,7 +14970,7 @@ function registerSplitioCommands(program2) {
14474
14970
  workspaces.command("list").description("List all Split.IO workspaces").action(async () => {
14475
14971
  const start = Date.now();
14476
14972
  try {
14477
- const http = getHttp4(program2);
14973
+ const http = getHttp5(program2);
14478
14974
  const data = await http.splitio("/internal/api/v2/workspaces");
14479
14975
  success(
14480
14976
  { count: data.objects.length, workspaces: data.objects },
@@ -14490,7 +14986,7 @@ function registerSplitioCommands(program2) {
14490
14986
  environments.command("list").description("List environments in a workspace").requiredOption("--workspace <id>", "Workspace ID").action(async (opts) => {
14491
14987
  const start = Date.now();
14492
14988
  try {
14493
- const http = getHttp4(program2);
14989
+ const http = getHttp5(program2);
14494
14990
  const data = await http.splitio(
14495
14991
  `/internal/api/v2/environments/ws/${encodeURIComponent(opts.workspace)}`
14496
14992
  );
@@ -14508,7 +15004,7 @@ function registerSplitioCommands(program2) {
14508
15004
  flags.command("list").description("List feature flags in a workspace").requiredOption("--workspace <id>", "Workspace ID").option("--limit <n>", "Maximum number of flags to return", "50").option("--offset <n>", "Pagination offset", "0").option("--flag-set <name>", "Filter by flag set name").action(async (opts) => {
14509
15005
  const start = Date.now();
14510
15006
  try {
14511
- const http = getHttp4(program2);
15007
+ const http = getHttp5(program2);
14512
15008
  const params = {
14513
15009
  limit: parseInt(opts.limit, 10),
14514
15010
  offset: parseInt(opts.offset, 10)
@@ -14531,7 +15027,7 @@ function registerSplitioCommands(program2) {
14531
15027
  flags.command("get").description("Get a flag definition").requiredOption("--workspace <id>", "Workspace ID").requiredOption("--flag <name>", "Feature flag name").option("--environment <id>", "Environment ID \u2014 include targeting rules for this environment").action(async (opts) => {
14532
15028
  const start = Date.now();
14533
15029
  try {
14534
- const http = getHttp4(program2);
15030
+ const http = getHttp5(program2);
14535
15031
  if (opts.environment) {
14536
15032
  const data = await http.splitio(
14537
15033
  `/internal/api/v2/splits/ws/${encodeURIComponent(opts.workspace)}/name/${encodeURIComponent(opts.flag)}/environment/${encodeURIComponent(opts.environment)}`
@@ -14553,7 +15049,7 @@ function registerSplitioCommands(program2) {
14553
15049
  if (!opts.inputFile) throw new PncliError("--input-file is required for flags update");
14554
15050
  const definition = loadInputFile(opts.inputFile);
14555
15051
  await confirmWrite(opts, `UPDATE ${opts.flag} in ${opts.environment}`);
14556
- const http = getHttp4(program2);
15052
+ const http = getHttp5(program2);
14557
15053
  const body = {
14558
15054
  ...buildCrBase(opts),
14559
15055
  operationType: "UPDATE_SPLIT_DEFINITION",
@@ -14579,7 +15075,7 @@ function registerSplitioCommands(program2) {
14579
15075
  if (!opts.treatment && !opts.weights) {
14580
15076
  throw new PncliError("Either --treatment or --weights must be specified");
14581
15077
  }
14582
- const http = getHttp4(program2);
15078
+ const http = getHttp5(program2);
14583
15079
  const current = await http.splitio(
14584
15080
  `/internal/api/v2/splits/ws/${encodeURIComponent(opts.workspace)}/name/${encodeURIComponent(opts.flag)}/environment/${encodeURIComponent(opts.environment)}`
14585
15081
  );
@@ -14617,7 +15113,7 @@ function registerSplitioCommands(program2) {
14617
15113
  flags.command("set-default").description("Update the fallback default rule for traffic matching no targeting rule").requiredOption("--workspace <id>", "Workspace ID").requiredOption("--flag <name>", "Feature flag name").requiredOption("--environment <id>", "Environment ID").requiredOption("--treatment <name>", "Treatment to assign as the default").requiredOption("--mnemonic <text>", "Short Change Request title (mnemonic)").requiredOption("--description <text>", "Change Request description").requiredOption("--change-number <CHG>", "Change request number (e.g. CHG0001234)").option("--approvers <ids>", "Comma-separated Split user IDs required to approve this CR").option("--yes", "Skip interactive confirmation prompt").action(async (opts) => {
14618
15114
  const start = Date.now();
14619
15115
  try {
14620
- const http = getHttp4(program2);
15116
+ const http = getHttp5(program2);
14621
15117
  const current = await http.splitio(
14622
15118
  `/internal/api/v2/splits/ws/${encodeURIComponent(opts.workspace)}/name/${encodeURIComponent(opts.flag)}/environment/${encodeURIComponent(opts.environment)}`
14623
15119
  );
@@ -14650,7 +15146,7 @@ function registerSplitioCommands(program2) {
14650
15146
  const enable = opts.enabled.toLowerCase() !== "false" && opts.enabled !== "0";
14651
15147
  const opType = enable ? "RESTORE_SPLIT" : "KILL_SPLIT";
14652
15148
  await confirmWrite(opts, `${opType} ${opts.flag} in ${opts.environment}`);
14653
- const http = getHttp4(program2);
15149
+ const http = getHttp5(program2);
14654
15150
  const body = {
14655
15151
  ...buildCrBase(opts),
14656
15152
  operationType: opType,
@@ -14673,7 +15169,7 @@ function registerSplitioCommands(program2) {
14673
15169
  const entries = loadInputFile(opts.inputFile);
14674
15170
  if (!Array.isArray(entries)) throw new PncliError("Input file must be a JSON array of {flag, enabled} objects");
14675
15171
  await confirmWrite(opts, `BATCH_TOGGLE ${entries.length} flags in ${opts.environment}`);
14676
- const http = getHttp4(program2);
15172
+ const http = getHttp5(program2);
14677
15173
  const results = [];
14678
15174
  for (const entry of entries) {
14679
15175
  try {
@@ -14707,7 +15203,7 @@ function registerSplitioCommands(program2) {
14707
15203
  const start = Date.now();
14708
15204
  try {
14709
15205
  await confirmWrite(opts, `KILL ${opts.flag} in ${opts.environment}`);
14710
- const http = getHttp4(program2);
15206
+ const http = getHttp5(program2);
14711
15207
  const body = {
14712
15208
  ...buildCrBase(opts),
14713
15209
  operationType: "KILL_SPLIT",
@@ -14727,7 +15223,7 @@ function registerSplitioCommands(program2) {
14727
15223
  const start = Date.now();
14728
15224
  try {
14729
15225
  await confirmWrite(opts, `RESTORE ${opts.flag} in ${opts.environment}`);
14730
- const http = getHttp4(program2);
15226
+ const http = getHttp5(program2);
14731
15227
  const body = {
14732
15228
  ...buildCrBase(opts),
14733
15229
  operationType: "RESTORE_SPLIT",
@@ -14747,7 +15243,7 @@ function registerSplitioCommands(program2) {
14747
15243
  const start = Date.now();
14748
15244
  try {
14749
15245
  await confirmWrite(opts, `ARCHIVE ${opts.flag}`);
14750
- const http = getHttp4(program2);
15246
+ const http = getHttp5(program2);
14751
15247
  const body = {
14752
15248
  ...buildCrBase(opts),
14753
15249
  operationType: "ARCHIVE_SPLIT",
@@ -14767,7 +15263,7 @@ function registerSplitioCommands(program2) {
14767
15263
  changeRequests.command("list").description("List Change Requests for a workspace").requiredOption("--workspace <id>", "Workspace ID").option("--environment <id>", "Filter by environment ID").option("--status <status>", "Filter by status (REQUESTED, APPROVED, REJECTED, PUBLISHED)").option("--limit <n>", "Maximum number of results", "25").option("--offset <n>", "Pagination offset", "0").action(async (opts) => {
14768
15264
  const start = Date.now();
14769
15265
  try {
14770
- const http = getHttp4(program2);
15266
+ const http = getHttp5(program2);
14771
15267
  const params = {
14772
15268
  limit: parseInt(opts.limit, 10),
14773
15269
  offset: parseInt(opts.offset, 10)
@@ -14791,7 +15287,7 @@ function registerSplitioCommands(program2) {
14791
15287
  changeRequests.command("get").description("Get a Change Request by ID").requiredOption("--id <id>", "Change Request ID").action(async (opts) => {
14792
15288
  const start = Date.now();
14793
15289
  try {
14794
- const http = getHttp4(program2);
15290
+ const http = getHttp5(program2);
14795
15291
  const data = await http.splitio(
14796
15292
  `/internal/api/v2/changeRequests/${encodeURIComponent(opts.id)}`
14797
15293
  );
@@ -14843,7 +15339,7 @@ function findNodeIdInUrl(url) {
14843
15339
  if (!new URL(url).searchParams.has("node-id")) return void 0;
14844
15340
  return parseNodeId(url);
14845
15341
  }
14846
- function getHttp5(program2) {
15342
+ function getHttp6(program2) {
14847
15343
  const opts = program2.optsWithGlobals();
14848
15344
  return createHttpClient(
14849
15345
  loadConfig({ configPath: opts.config }),
@@ -14859,7 +15355,7 @@ function registerFigmaCommands(program2) {
14859
15355
  const fileKey = parseFileKey(fileKeyOrUrl);
14860
15356
  const nodeId = opts.nodeId ? parseNodeId(opts.nodeId) : isUrl(fileKeyOrUrl) ? findNodeIdInUrl(fileKeyOrUrl) : void 0;
14861
15357
  if (nodeId) {
14862
- const data2 = await getHttp5(program2).figma(
15358
+ const data2 = await getHttp6(program2).figma(
14863
15359
  `/v1/files/${encodeURIComponent(fileKey)}/nodes`,
14864
15360
  { params: { ids: nodeId } }
14865
15361
  );
@@ -14888,7 +15384,7 @@ function registerFigmaCommands(program2) {
14888
15384
  if (!opts.document) {
14889
15385
  params["depth"] = 1;
14890
15386
  }
14891
- const data = await getHttp5(program2).figma(`/v1/files/${encodeURIComponent(fileKey)}`, { params });
15387
+ const data = await getHttp6(program2).figma(`/v1/files/${encodeURIComponent(fileKey)}`, { params });
14892
15388
  success2(
14893
15389
  {
14894
15390
  fileKey,
@@ -14918,7 +15414,7 @@ function registerFigmaCommands(program2) {
14918
15414
  const fileKey = parseFileKey(fileKeyOrUrl);
14919
15415
  const params = {};
14920
15416
  if (opts.asOf) params["as_of"] = opts.asOf;
14921
- const data = await getHttp5(program2).figma(`/v1/files/${encodeURIComponent(fileKey)}/comments`, { params });
15417
+ const data = await getHttp6(program2).figma(`/v1/files/${encodeURIComponent(fileKey)}/comments`, { params });
14922
15418
  success2(
14923
15419
  { fileKey, count: data.comments.length, comments: data.comments },
14924
15420
  "figma",
@@ -14934,7 +15430,7 @@ function registerFigmaCommands(program2) {
14934
15430
  const { success: success2, fail: fail2 } = await import("./output-HDUIIXUI.js");
14935
15431
  try {
14936
15432
  const fileKey = parseFileKey(fileKeyOrUrl);
14937
- const data = await getHttp5(program2).figma(`/v1/files/${encodeURIComponent(fileKey)}/versions`);
15433
+ const data = await getHttp6(program2).figma(`/v1/files/${encodeURIComponent(fileKey)}/versions`);
14938
15434
  success2(
14939
15435
  { fileKey, count: data.versions.length, versions: data.versions },
14940
15436
  "figma",
@@ -14949,7 +15445,7 @@ function registerFigmaCommands(program2) {
14949
15445
  const start = Date.now();
14950
15446
  const { success: success2, fail: fail2 } = await import("./output-HDUIIXUI.js");
14951
15447
  try {
14952
- const data = await getHttp5(program2).figma(`/v1/projects/${encodeURIComponent(projectId)}/files`);
15448
+ const data = await getHttp6(program2).figma(`/v1/projects/${encodeURIComponent(projectId)}/files`);
14953
15449
  success2(
14954
15450
  { projectId, count: data.files.length, files: data.files },
14955
15451
  "figma",
@@ -14964,7 +15460,7 @@ function registerFigmaCommands(program2) {
14964
15460
  const start = Date.now();
14965
15461
  const { success: success2, fail: fail2 } = await import("./output-HDUIIXUI.js");
14966
15462
  try {
14967
- const data = await getHttp5(program2).figma("/v1/me");
15463
+ const data = await getHttp6(program2).figma("/v1/me");
14968
15464
  success2(data, "figma", "me", start);
14969
15465
  } catch (err) {
14970
15466
  fail2(err, "figma", "me", start);
@@ -14975,7 +15471,7 @@ function registerFigmaCommands(program2) {
14975
15471
  // src/services/alation/commands.ts
14976
15472
  import fs12 from "fs";
14977
15473
  var MAX_PAGE = 1e3;
14978
- function parseIntOption(value, flag) {
15474
+ function parseIntOption2(value, flag) {
14979
15475
  if (value === void 0) return void 0;
14980
15476
  const trimmed = value.trim();
14981
15477
  if (!/^\d+$/.test(trimmed)) {
@@ -14991,8 +15487,8 @@ function parsePositiveId(value, what) {
14991
15487
  return Number(trimmed);
14992
15488
  }
14993
15489
  function pageParams(opts) {
14994
- const limit = parseIntOption(opts.limit, "--limit") ?? 100;
14995
- const skip = parseIntOption(opts.skip, "--skip") ?? 0;
15490
+ const limit = parseIntOption2(opts.limit, "--limit") ?? 100;
15491
+ const skip = parseIntOption2(opts.skip, "--skip") ?? 0;
14996
15492
  if (limit < 1 || limit > MAX_PAGE) {
14997
15493
  throw new PncliError(`--limit must be between 1 and ${MAX_PAGE} (Alation's per-request cap).`, 1);
14998
15494
  }
@@ -15079,17 +15575,17 @@ var ALATION_DOCUMENT_INPUT_FILE_EXAMPLE = {
15079
15575
  template: 7,
15080
15576
  fields: { "10001": "Team Data Platform" }
15081
15577
  };
15082
- function getHttp6(program2) {
15578
+ function getHttp7(program2) {
15083
15579
  const opts = program2.optsWithGlobals();
15084
15580
  return createHttpClient(
15085
15581
  loadConfig({ configPath: opts.config }),
15086
15582
  Boolean(opts.dryRun)
15087
15583
  );
15088
15584
  }
15089
- async function getOne(http, path14, id, what) {
15090
- const rows = await http.alation(path14, { params: { id, limit: 1 } });
15091
- const row = Array.isArray(rows) ? rows.find((r) => r.id === id) ?? rows[0] : void 0;
15092
- if (!row) throw new PncliError(`${what} ${id} not found`, 404);
15585
+ async function getOne(http, path14, id2, what) {
15586
+ const rows = await http.alation(path14, { params: { id: id2, limit: 1 } });
15587
+ const row = Array.isArray(rows) ? rows.find((r) => r.id === id2) ?? rows[0] : void 0;
15588
+ if (!row) throw new PncliError(`${what} ${id2} not found`, 404);
15093
15589
  return row;
15094
15590
  }
15095
15591
  function buildDocumentPayload(opts) {
@@ -15155,17 +15651,17 @@ function registerAlationCommands(program2) {
15155
15651
  const params = {};
15156
15652
  if (opts.includeUndeployed) params.include_undeployed = true;
15157
15653
  if (opts.includeHidden) params.include_hidden = true;
15158
- const data = await getHttp6(program2).alation("integration/v1/datasource/", { params });
15654
+ const data = await getHttp7(program2).alation("integration/v1/datasource/", { params });
15159
15655
  success({ count: data.length, datasources: data }, "alation", "datasource-list", start);
15160
15656
  } catch (err) {
15161
15657
  fail(err, "alation", "datasource-list", start);
15162
15658
  }
15163
15659
  });
15164
- datasource.command("get").description("Get a data source by ID").argument("<id>", "Data source ID").action(async (id) => {
15660
+ datasource.command("get").description("Get a data source by ID").argument("<id>", "Data source ID").action(async (id2) => {
15165
15661
  const start = Date.now();
15166
15662
  try {
15167
- const dsId = parsePositiveId(id, "data source ID");
15168
- const data = await getHttp6(program2).alation(`integration/v1/datasource/${dsId}/`);
15663
+ const dsId = parsePositiveId(id2, "data source ID");
15664
+ const data = await getHttp7(program2).alation(`integration/v1/datasource/${dsId}/`);
15169
15665
  success(data, "alation", "datasource-get", start);
15170
15666
  } catch (err) {
15171
15667
  fail(err, "alation", "datasource-get", start);
@@ -15178,16 +15674,16 @@ function registerAlationCommands(program2) {
15178
15674
  const params = { ...pageParams(opts) };
15179
15675
  if (opts.ds) params.ds_id = parsePositiveId(opts.ds, "--ds");
15180
15676
  if (opts.name) params.name = opts.name;
15181
- const data = await getHttp6(program2).alation("integration/v2/schema/", { params });
15677
+ const data = await getHttp7(program2).alation("integration/v2/schema/", { params });
15182
15678
  success({ count: data.length, limit: params.limit, skip: params.skip, schemas: data }, "alation", "schema-list", start);
15183
15679
  } catch (err) {
15184
15680
  fail(err, "alation", "schema-list", start);
15185
15681
  }
15186
15682
  });
15187
- schema.command("get").description("Get a schema by ID").argument("<id>", "Schema ID").action(async (id) => {
15683
+ schema.command("get").description("Get a schema by ID").argument("<id>", "Schema ID").action(async (id2) => {
15188
15684
  const start = Date.now();
15189
15685
  try {
15190
- const data = await getOne(getHttp6(program2), "integration/v2/schema/", parsePositiveId(id, "schema ID"), "Schema");
15686
+ const data = await getOne(getHttp7(program2), "integration/v2/schema/", parsePositiveId(id2, "schema ID"), "Schema");
15191
15687
  success(data, "alation", "schema-get", start);
15192
15688
  } catch (err) {
15193
15689
  fail(err, "alation", "schema-get", start);
@@ -15203,17 +15699,17 @@ function registerAlationCommands(program2) {
15203
15699
  if (opts.schemaName) params.schema_name = opts.schemaName;
15204
15700
  if (opts.name) params.name = opts.name;
15205
15701
  if (opts.search) params.name__icontains = opts.search;
15206
- const data = await getHttp6(program2).alation("integration/v2/table/", { params });
15702
+ const data = await getHttp7(program2).alation("integration/v2/table/", { params });
15207
15703
  success({ count: data.length, limit: params.limit, skip: params.skip, tables: data }, "alation", "table-list", start);
15208
15704
  } catch (err) {
15209
15705
  fail(err, "alation", "table-list", start);
15210
15706
  }
15211
15707
  });
15212
- table.command("get").description("Get a table by ID, optionally with its columns").argument("<id>", "Table ID").option("--columns", "Also fetch the table's columns (up to 1000)").action(async (id, opts) => {
15708
+ table.command("get").description("Get a table by ID, optionally with its columns").argument("<id>", "Table ID").option("--columns", "Also fetch the table's columns (up to 1000)").action(async (id2, opts) => {
15213
15709
  const start = Date.now();
15214
15710
  try {
15215
- const http = getHttp6(program2);
15216
- const tableId = parsePositiveId(id, "table ID");
15711
+ const http = getHttp7(program2);
15712
+ const tableId = parsePositiveId(id2, "table ID");
15217
15713
  const data = await getOne(http, "integration/v2/table/", tableId, "Table");
15218
15714
  if (opts.columns) {
15219
15715
  const columns = await http.alation("integration/v2/column/", {
@@ -15241,16 +15737,16 @@ function registerAlationCommands(program2) {
15241
15737
  if (opts.ds) params.ds_id = parsePositiveId(opts.ds, "--ds");
15242
15738
  if (opts.name) params.name = opts.name;
15243
15739
  if (opts.search) params.name__icontains = opts.search;
15244
- const data = await getHttp6(program2).alation("integration/v2/column/", { params });
15740
+ const data = await getHttp7(program2).alation("integration/v2/column/", { params });
15245
15741
  success({ count: data.length, limit: params.limit, skip: params.skip, columns: data }, "alation", "column-list", start);
15246
15742
  } catch (err) {
15247
15743
  fail(err, "alation", "column-list", start);
15248
15744
  }
15249
15745
  });
15250
- column.command("get").description("Get a column by ID").argument("<id>", "Column ID").action(async (id) => {
15746
+ column.command("get").description("Get a column by ID").argument("<id>", "Column ID").action(async (id2) => {
15251
15747
  const start = Date.now();
15252
15748
  try {
15253
- const data = await getOne(getHttp6(program2), "integration/v2/column/", parsePositiveId(id, "column ID"), "Column");
15749
+ const data = await getOne(getHttp7(program2), "integration/v2/column/", parsePositiveId(id2, "column ID"), "Column");
15254
15750
  success(data, "alation", "column-get", start);
15255
15751
  } catch (err) {
15256
15752
  fail(err, "alation", "column-get", start);
@@ -15259,13 +15755,13 @@ function registerAlationCommands(program2) {
15259
15755
  alation.command("search").description("Full-text search across the catalog").argument("<query>", "Search text").option("--otype <types>", `Comma-separated object types to restrict to (e.g. table,column). Valid: ${SEARCH_OTYPES.join(", ")}`).option("--limit <n>", "Maximum results (default 20, max 10000)", "20").option("--offset <n>", "Index of the first result", "0").action(async (query, opts) => {
15260
15756
  const start = Date.now();
15261
15757
  try {
15262
- const limit = parseIntOption(opts.limit, "--limit") ?? 20;
15263
- const offset = parseIntOption(opts.offset, "--offset") ?? 0;
15758
+ const limit = parseIntOption2(opts.limit, "--limit") ?? 20;
15759
+ const offset = parseIntOption2(opts.offset, "--offset") ?? 0;
15264
15760
  if (limit > 1e4) throw new PncliError("--limit must be at most 10000", 1);
15265
15761
  const params = { q: query, limit, offset };
15266
15762
  const otypes = parseOtypes(opts.otype);
15267
15763
  if (otypes) params.filters = JSON.stringify({ otypes });
15268
- const data = await getHttp6(program2).alation("integration/v1/search/", { params });
15764
+ const data = await getHttp7(program2).alation("integration/v1/search/", { params });
15269
15765
  success(
15270
15766
  { query, total: data.total, limit: data.limit, offset: data.offset, fullSearchUrl: data.full_search_url, results: data.results },
15271
15767
  "alation",
@@ -15284,16 +15780,16 @@ function registerAlationCommands(program2) {
15284
15780
  if (opts.hub) params.document_hub_id = parsePositiveId(opts.hub, "--hub");
15285
15781
  if (opts.parent) params.parent_folder_id = parsePositiveId(opts.parent, "--parent");
15286
15782
  if (opts.search) params.search = opts.search;
15287
- const data = await getHttp6(program2).alation("integration/v2/folder/", { params });
15783
+ const data = await getHttp7(program2).alation("integration/v2/folder/", { params });
15288
15784
  success({ count: data.length, limit: params.limit, skip: params.skip, folders: data }, "alation", "folder-list", start);
15289
15785
  } catch (err) {
15290
15786
  fail(err, "alation", "folder-list", start);
15291
15787
  }
15292
15788
  });
15293
- folder.command("get").description("Get a Document Hub folder by ID").argument("<id>", "Folder ID").action(async (id) => {
15789
+ folder.command("get").description("Get a Document Hub folder by ID").argument("<id>", "Folder ID").action(async (id2) => {
15294
15790
  const start = Date.now();
15295
15791
  try {
15296
- const data = await getOne(getHttp6(program2), "integration/v2/folder/", parsePositiveId(id, "folder ID"), "Folder");
15792
+ const data = await getOne(getHttp7(program2), "integration/v2/folder/", parsePositiveId(id2, "folder ID"), "Folder");
15297
15793
  success(data, "alation", "folder-get", start);
15298
15794
  } catch (err) {
15299
15795
  fail(err, "alation", "folder-get", start);
@@ -15309,16 +15805,16 @@ function registerAlationCommands(program2) {
15309
15805
  if (opts.parentDocument) params.parent_document_id = parsePositiveId(opts.parentDocument, "--parent-document");
15310
15806
  if (opts.search) params.search = opts.search;
15311
15807
  if (opts.deleted) params.deleted = true;
15312
- const data = await getHttp6(program2).alation("integration/v2/document/", { params });
15808
+ const data = await getHttp7(program2).alation("integration/v2/document/", { params });
15313
15809
  success({ count: data.length, limit: params.limit, skip: params.skip, documents: data }, "alation", "document-list", start);
15314
15810
  } catch (err) {
15315
15811
  fail(err, "alation", "document-list", start);
15316
15812
  }
15317
15813
  });
15318
- document.command("get").description("Get a document by ID, including its custom field values").argument("<id>", "Document ID").action(async (id) => {
15814
+ document.command("get").description("Get a document by ID, including its custom field values").argument("<id>", "Document ID").action(async (id2) => {
15319
15815
  const start = Date.now();
15320
15816
  try {
15321
- const data = await getOne(getHttp6(program2), "integration/v2/document/", parsePositiveId(id, "document ID"), "Document");
15817
+ const data = await getOne(getHttp7(program2), "integration/v2/document/", parsePositiveId(id2, "document ID"), "Document");
15322
15818
  success(data, "alation", "document-get", start);
15323
15819
  } catch (err) {
15324
15820
  fail(err, "alation", "document-get", start);
@@ -15339,16 +15835,16 @@ function registerAlationCommands(program2) {
15339
15835
  for (const [key, flag] of [["title", "--title"], ["document_hub_id", "--hub"], ["parent_folder_id", "--folder"]]) {
15340
15836
  if (payload[key] === void 0) throw new PncliError(`${flag} is required to create a document (or set it in --input-file)`, 1);
15341
15837
  }
15342
- const data = await getHttp6(program2).alation("integration/v2/document/", { method: "POST", body: [payload] });
15838
+ const data = await getHttp7(program2).alation("integration/v2/document/", { method: "POST", body: [payload] });
15343
15839
  success({ jobId: data.job_id, document: payload, ...overrides.length ? { overrides } : {} }, "alation", "document-create", start);
15344
15840
  } catch (err) {
15345
15841
  fail(err, "alation", "document-create", start);
15346
15842
  }
15347
15843
  });
15348
- document.command("update").description("Update a document (async \u2014 returns a job ID; custom field values replace existing values per field)").argument("<id>", "Document ID").option("--title <title>", "New title").option("--description <text>", "New summary").option("--folder <id>", "Move to this parent folder").option("--template <id>", "Template ID (required when custom fields are set)").option("--input-file <path>", 'JSON file with title/description/folder/template/fields (see `alation document schema`); "-" reads stdin').action(async (id, opts) => {
15844
+ document.command("update").description("Update a document (async \u2014 returns a job ID; custom field values replace existing values per field)").argument("<id>", "Document ID").option("--title <title>", "New title").option("--description <text>", "New summary").option("--folder <id>", "Move to this parent folder").option("--template <id>", "Template ID (required when custom fields are set)").option("--input-file <path>", 'JSON file with title/description/folder/template/fields (see `alation document schema`); "-" reads stdin').action(async (id2, opts) => {
15349
15845
  const start = Date.now();
15350
15846
  try {
15351
- const documentId = parsePositiveId(id, "document ID");
15847
+ const documentId = parsePositiveId(id2, "document ID");
15352
15848
  const { payload, overrides } = buildDocumentPayload(opts);
15353
15849
  if (payload.document_hub_id !== void 0) {
15354
15850
  throw new PncliError('A document cannot change hubs; remove "hub" from --input-file.', 1);
@@ -15357,18 +15853,18 @@ function registerAlationCommands(program2) {
15357
15853
  throw new PncliError("Nothing to update. Pass --title, --description, --folder, or --input-file.", 1);
15358
15854
  }
15359
15855
  const body = [{ id: documentId, ...payload }];
15360
- const data = await getHttp6(program2).alation("integration/v2/document/", { method: "PUT", body });
15856
+ const data = await getHttp7(program2).alation("integration/v2/document/", { method: "PUT", body });
15361
15857
  success({ jobId: data.job_id, document: body[0], ...overrides.length ? { overrides } : {} }, "alation", "document-update", start);
15362
15858
  } catch (err) {
15363
15859
  fail(err, "alation", "document-update", start);
15364
15860
  }
15365
15861
  });
15366
15862
  const job = alation.command("job").description("Background job operations");
15367
- job.command("get").description("Get the status of a bulk job (returned by document create/update)").argument("<id>", "Job ID").action(async (id) => {
15863
+ job.command("get").description("Get the status of a bulk job (returned by document create/update)").argument("<id>", "Job ID").action(async (id2) => {
15368
15864
  const start = Date.now();
15369
15865
  try {
15370
- const jobId = parsePositiveId(id, "job ID");
15371
- const data = await getHttp6(program2).alation("api/v1/bulk_metadata/job/", { params: { id: jobId } });
15866
+ const jobId = parsePositiveId(id2, "job ID");
15867
+ const data = await getHttp7(program2).alation("api/v1/bulk_metadata/job/", { params: { id: jobId } });
15372
15868
  success({ jobId, ...data }, "alation", "job-get", start);
15373
15869
  } catch (err) {
15374
15870
  fail(err, "alation", "job-get", start);
@@ -15377,9 +15873,7 @@ function registerAlationCommands(program2) {
15377
15873
  }
15378
15874
 
15379
15875
  // src/cli.ts
15380
- if (!process.env.PNCLI_VERIFY_TLS) {
15381
- process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
15382
- }
15876
+ configureTls();
15383
15877
  await configureProxy();
15384
15878
  var TAGLINE = "Connectivity without MCP.";
15385
15879
  var program = new Command3();
@@ -15422,6 +15916,7 @@ registerLogscaleCommands(program);
15422
15916
  registerSplitioCommands(program);
15423
15917
  registerFigmaCommands(program);
15424
15918
  registerAlationCommands(program);
15919
+ registerSauceLabsCommands(program);
15425
15920
  program.addHelpText("after", `
15426
15921
  Services:
15427
15922
  git Local git operations (status, diff, log, branch)
@@ -15444,6 +15939,7 @@ Services:
15444
15939
  splitio Split.IO (feature flags, Change Requests)
15445
15940
  figma Figma (files, comments, version history)
15446
15941
  alation Alation (data sources, schemas, tables, columns, search, Document Hubs)
15942
+ saucelabs Sauce Labs (jobs, builds, real devices and device sessions, tunnels, platforms)
15447
15943
  config Manage pncli configuration (and OS keychain storage for credentials)
15448
15944
  doctor Diagnose pncli setup (config, credentials, keychain, git auth, agent skills, marketplaces)
15449
15945
  skills Install and manage agent skills (Codex, GitHub Copilot, Claude Code)