@kolatts/pncli 5.4.0 → 6.1.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-GHZMFNSX.js";
32
32
  import {
33
+ configureTls,
33
34
  createHttpClient,
35
+ describeFetchError,
34
36
  validateAlationAccessToken
35
- } from "./chunk-2J2HAFKO.js";
37
+ } from "./chunk-Y3PO2RTO.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
  }
@@ -714,8 +716,8 @@ var BitbucketClient = class {
714
716
  }
715
717
  async resolveComment(project, repo, prId, commentId, version) {
716
718
  await this.http.bitbucket(
717
- `${API}/projects/${project}/repos/${repo}/pull-requests/${prId}/comments/${commentId}/resolve`,
718
- { method: "PUT", params: { version } }
719
+ `${API}/projects/${project}/repos/${repo}/pull-requests/${prId}/comments/${commentId}`,
720
+ { method: "PUT", body: { version, threadResolved: true } }
719
721
  );
720
722
  }
721
723
  async deleteComment(project, repo, prId, commentId, version) {
@@ -788,6 +790,12 @@ var BitbucketClient = class {
788
790
  const pr = await this.getPR(project, repo, prId);
789
791
  return pr.reviewers;
790
792
  }
793
+ async listDefaultReviewers(project, repo) {
794
+ const result = await this.http.bitbucket(
795
+ `/rest/default-reviewers/1.0/projects/${encodeURIComponent(project)}/repos/${encodeURIComponent(repo)}/conditions`
796
+ );
797
+ return Array.isArray(result) ? result : [];
798
+ }
791
799
  async addReviewer(project, repo, prId, username) {
792
800
  return this.http.bitbucket(
793
801
  `${API}/projects/${project}/repos/${repo}/pull-requests/${prId}/participants`,
@@ -2885,9 +2893,9 @@ function parseFieldsFile(filePath, fieldMap) {
2885
2893
  }
2886
2894
  function translateFieldErrors(err, fieldMap) {
2887
2895
  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;
2896
+ const translated = err.message.replace(/\bcustomfield_\d+\b/g, (id2) => {
2897
+ const def = fieldMap.byId.get(id2);
2898
+ return def ? `${def.name} (${id2})` : id2;
2891
2899
  });
2892
2900
  if (translated === err.message) return err;
2893
2901
  return new PncliError(translated, err.status, err.url);
@@ -3096,7 +3104,7 @@ function registerBitbucketCommands(program2) {
3096
3104
  fail(err, "bitbucket", "reply-comment", start);
3097
3105
  }
3098
3106
  });
3099
- bb.command("resolve-comment").description("Resolve a comment on a pull request").requiredOption("--pr <pr-id>", "Pull request ID").requiredOption("--comment-id <id>", "Comment ID").option("--comment-version <n>", "Comment version, as reported by list-comments", "0").action(async (opts) => {
3107
+ bb.command("resolve-comment").description("Resolve a comment thread on a pull request (use the root comment ID, not a reply)").requiredOption("--pr <pr-id>", "Pull request ID").requiredOption("--comment-id <id>", "Comment ID").option("--comment-version <n>", "Comment version, as reported by list-comments", "0").action(async (opts) => {
3100
3108
  const start = Date.now();
3101
3109
  try {
3102
3110
  const { client, project, repo } = getClient2(bb);
@@ -3177,6 +3185,16 @@ function registerBitbucketCommands(program2) {
3177
3185
  fail(err, "bitbucket", "list-reviewers", start);
3178
3186
  }
3179
3187
  });
3188
+ bb.command("list-default-reviewers").description("List the default-reviewer conditions configured for a repository").action(async () => {
3189
+ const start = Date.now();
3190
+ try {
3191
+ const { client, project, repo } = getClient2(bb);
3192
+ const data = await client.listDefaultReviewers(project, repo);
3193
+ success(data, "bitbucket", "list-default-reviewers", start);
3194
+ } catch (err) {
3195
+ fail(err, "bitbucket", "list-default-reviewers", start);
3196
+ }
3197
+ });
3180
3198
  bb.command("add-reviewer").description("Add a reviewer to a pull request").requiredOption("--pr <pr-id>", "Pull request ID").requiredOption("--user <username>", "Username to add as a reviewer").action(async (opts) => {
3181
3199
  const start = Date.now();
3182
3200
  try {
@@ -4255,8 +4273,8 @@ var ConfluenceClient = class {
4255
4273
  this.http = http;
4256
4274
  }
4257
4275
  http;
4258
- async getPage(id, expand = "body.storage,version,space,ancestors") {
4259
- return this.http.confluence(`${API3}/content/${id}`, {
4276
+ async getPage(id2, expand = "body.storage,version,space,ancestors") {
4277
+ return this.http.confluence(`${API3}/content/${id2}`, {
4260
4278
  params: { expand }
4261
4279
  });
4262
4280
  }
@@ -4274,16 +4292,16 @@ var ConfluenceClient = class {
4274
4292
  });
4275
4293
  }, opts.limit);
4276
4294
  }
4277
- async getPageChildren(id) {
4295
+ async getPageChildren(id2) {
4278
4296
  return this.http.confluencePaginate(async (start, limit) => {
4279
- return this.http.confluence(`${API3}/content/${id}/child/page`, {
4297
+ return this.http.confluence(`${API3}/content/${id2}/child/page`, {
4280
4298
  params: { expand: "version", start, limit }
4281
4299
  });
4282
4300
  });
4283
4301
  }
4284
- async getLabels(id) {
4302
+ async getLabels(id2) {
4285
4303
  return this.http.confluencePaginate(async (start, limit) => {
4286
- return this.http.confluence(`${API3}/content/${id}/label`, {
4304
+ return this.http.confluence(`${API3}/content/${id2}/label`, {
4287
4305
  params: { start, limit }
4288
4306
  });
4289
4307
  });
@@ -4316,7 +4334,7 @@ var ConfluenceClient = class {
4316
4334
  body
4317
4335
  });
4318
4336
  }
4319
- async updatePage(id, opts) {
4337
+ async updatePage(id2, opts) {
4320
4338
  const body = {
4321
4339
  version: { number: opts.version },
4322
4340
  type: "page",
@@ -4331,13 +4349,13 @@ var ConfluenceClient = class {
4331
4349
  }
4332
4350
  };
4333
4351
  }
4334
- return this.http.confluence(`${API3}/content/${id}`, {
4352
+ return this.http.confluence(`${API3}/content/${id2}`, {
4335
4353
  method: "PUT",
4336
4354
  body
4337
4355
  });
4338
4356
  }
4339
- async deletePage(id) {
4340
- return this.http.confluence(`${API3}/content/${id}`, { method: "DELETE" });
4357
+ async deletePage(id2) {
4358
+ return this.http.confluence(`${API3}/content/${id2}`, { method: "DELETE" });
4341
4359
  }
4342
4360
  async listComments(pageId) {
4343
4361
  return this.http.confluencePaginate(async (start, limit) => {
@@ -4422,8 +4440,8 @@ var ConfluenceClient = class {
4422
4440
  async deleteAttachment(attachmentId) {
4423
4441
  return this.http.confluence(`${API3}/content/${attachmentId}`, { method: "DELETE" });
4424
4442
  }
4425
- async getPageHistory(id) {
4426
- return this.http.confluence(`${API3}/content/${id}/history`);
4443
+ async getPageHistory(id2) {
4444
+ return this.http.confluence(`${API3}/content/${id2}/history`);
4427
4445
  }
4428
4446
  async convertToStorage(value, fromRepresentation = "markdown") {
4429
4447
  const result = await this.http.confluence(
@@ -5115,9 +5133,9 @@ function resolveProject2(config, cliProject) {
5115
5133
  const project = cliProject ?? config.defaults.sde.project;
5116
5134
  if (!project) throw new PncliError("No project specified. Use --project or set defaults.sde.project in config.");
5117
5135
  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;
5136
+ const id2 = parseInt(project, 10);
5137
+ if (id2 <= 0) throw new PncliError(`Invalid project ID: "${project}". SDElements project IDs must be positive integers.`);
5138
+ return id2;
5121
5139
  }
5122
5140
  function parsePage(val, flag) {
5123
5141
  const n = parseInt(val, 10);
@@ -6045,7 +6063,7 @@ async function checkOsvConnectivity() {
6045
6063
  } catch (err) {
6046
6064
  return {
6047
6065
  reachable: false,
6048
- error: err instanceof Error ? err.message : String(err)
6066
+ error: describeFetchError(err)
6049
6067
  };
6050
6068
  }
6051
6069
  }
@@ -6250,7 +6268,7 @@ async function checkSonatypeConnectivity() {
6250
6268
  } catch (err) {
6251
6269
  return {
6252
6270
  reachable: false,
6253
- error: err instanceof Error ? err.message : String(err)
6271
+ error: describeFetchError(err)
6254
6272
  };
6255
6273
  }
6256
6274
  }
@@ -6314,10 +6332,10 @@ function mapViolationToVuln(violation) {
6314
6332
  const cveRefs = violation.constraintViolations.flatMap(
6315
6333
  (cv) => cv.reasons.filter((r) => r.reference?.type === "VULNERABILITY").map((r) => r.reference.value)
6316
6334
  );
6317
- const id = cveRefs[0] ?? violation.policyId;
6335
+ const id2 = cveRefs[0] ?? violation.policyId;
6318
6336
  const severity = threatLevelToSeverity(violation.policyThreatLevel);
6319
6337
  return {
6320
- id,
6338
+ id: id2,
6321
6339
  summary: violation.policyName,
6322
6340
  severity,
6323
6341
  cvss: null,
@@ -6473,7 +6491,7 @@ async function checkSonatypeIqConnectivity(baseUrl, userCode, passcode) {
6473
6491
  return {
6474
6492
  reachable: false,
6475
6493
  authenticated: false,
6476
- error: err instanceof Error ? err.message : String(err)
6494
+ error: describeFetchError(err)
6477
6495
  };
6478
6496
  }
6479
6497
  }
@@ -6533,7 +6551,7 @@ async function checkArtifactoryConnectivity(config) {
6533
6551
  reachable: false,
6534
6552
  authenticated: false,
6535
6553
  configured: true,
6536
- error: err instanceof Error ? err.message : String(err)
6554
+ error: describeFetchError(err)
6537
6555
  };
6538
6556
  }
6539
6557
  }
@@ -7080,9 +7098,9 @@ var AdoWorkClient = class {
7080
7098
  this.http = http;
7081
7099
  }
7082
7100
  http;
7083
- async getWorkItem(collection, id) {
7101
+ async getWorkItem(collection, id2) {
7084
7102
  return this.http.ado(
7085
- `/${encodeURIComponent(collection)}/_apis/wit/workitems/${id}?api-version=${API6}&$expand=all`
7103
+ `/${encodeURIComponent(collection)}/_apis/wit/workitems/${id2}?api-version=${API6}&$expand=all`
7086
7104
  );
7087
7105
  }
7088
7106
  async createWorkItem(collection, project, type, patch) {
@@ -7091,9 +7109,9 @@ var AdoWorkClient = class {
7091
7109
  { method: "POST", body: patch, headers: { "Content-Type": "application/json-patch+json" } }
7092
7110
  );
7093
7111
  }
7094
- async updateWorkItem(collection, id, patch) {
7112
+ async updateWorkItem(collection, id2, patch) {
7095
7113
  return this.http.ado(
7096
- `/${encodeURIComponent(collection)}/_apis/wit/workitems/${id}?api-version=${API6}`,
7114
+ `/${encodeURIComponent(collection)}/_apis/wit/workitems/${id2}?api-version=${API6}`,
7097
7115
  { method: "PATCH", body: patch, headers: { "Content-Type": "application/json-patch+json" } }
7098
7116
  );
7099
7117
  }
@@ -7115,23 +7133,23 @@ var AdoWorkClient = class {
7115
7133
  { method: "POST", body: { text } }
7116
7134
  );
7117
7135
  }
7118
- async addTags(collection, id, tags) {
7119
- const item = await this.getWorkItem(collection, id);
7136
+ async addTags(collection, id2, tags) {
7137
+ const item = await this.getWorkItem(collection, id2);
7120
7138
  const existing = item.fields["System.Tags"] ?? "";
7121
7139
  const current = existing.split(";").map((t) => t.trim()).filter(Boolean);
7122
7140
  const lowerSet = new Set(current.map((t) => t.toLowerCase()));
7123
7141
  const toAdd = tags.filter((t) => !lowerSet.has(t.toLowerCase()));
7124
7142
  const merged = [...current, ...toAdd];
7125
7143
  const patch = [{ op: "add", path: "/fields/System.Tags", value: merged.join("; ") }];
7126
- return this.updateWorkItem(collection, id, patch);
7144
+ return this.updateWorkItem(collection, id2, patch);
7127
7145
  }
7128
- async removeTags(collection, id, tags) {
7129
- const item = await this.getWorkItem(collection, id);
7146
+ async removeTags(collection, id2, tags) {
7147
+ const item = await this.getWorkItem(collection, id2);
7130
7148
  const existing = item.fields["System.Tags"] ?? "";
7131
7149
  const lower = new Set(tags.map((t) => t.toLowerCase()));
7132
7150
  const remaining = existing.split(";").map((t) => t.trim()).filter((t) => t && !lower.has(t.toLowerCase()));
7133
7151
  const patch = [{ op: "add", path: "/fields/System.Tags", value: remaining.join("; ") }];
7134
- return this.updateWorkItem(collection, id, patch);
7152
+ return this.updateWorkItem(collection, id2, patch);
7135
7153
  }
7136
7154
  async listWorkItemTypes(collection, project) {
7137
7155
  const result = await this.http.ado(
@@ -7272,6 +7290,422 @@ function buildDefaultAliases(fields) {
7272
7290
  return aliases;
7273
7291
  }
7274
7292
 
7293
+ // src/services/saucelabs/commands.ts
7294
+ var SERVICE = "saucelabs";
7295
+ var RDA = "/rdc/v2";
7296
+ var TEAM_MGMT = "/team-management/v1";
7297
+ var BUILD_SOURCES = ["vdc", "rdc"];
7298
+ var BUILD_STATUSES = ["running", "error", "failed", "complete", "success"];
7299
+ var JOB_VISIBILITY = ["public", "public restricted", "share", "team", "private"];
7300
+ var PLATFORM_APIS = ["all", "appium", "webdriver"];
7301
+ var DEVICE_OS = ["ANDROID", "IOS"];
7302
+ var DEVICE_TYPES = ["PHONE", "TABLET"];
7303
+ var DEVICE_STATES = ["AVAILABLE", "IN_USE", "CLEANING", "MAINTENANCE", "REBOOTING", "OFFLINE"];
7304
+ var SESSION_STATES = ["PENDING", "CREATING", "ACTIVE", "CLOSING", "CLOSED", "ERRORED"];
7305
+ var ORIENTATIONS = ["PORTRAIT", "LANDSCAPE"];
7306
+ var STORAGE_KINDS = ["android", "ios"];
7307
+ function getHttp(program2) {
7308
+ const opts = program2.optsWithGlobals();
7309
+ return createHttpClient(
7310
+ loadConfig({ configPath: opts.config }),
7311
+ Boolean(opts.dryRun)
7312
+ );
7313
+ }
7314
+ async function verifySauceLabsCredentials(http, timeoutMs) {
7315
+ return http.saucelabs(
7316
+ `/rest/v1.2/users/${encodeURIComponent(http.saucelabsUsername())}/concurrency`,
7317
+ timeoutMs !== void 0 ? { timeoutMs } : {}
7318
+ );
7319
+ }
7320
+ function parseIntOption(value, flag) {
7321
+ if (value === void 0) return void 0;
7322
+ const trimmed = value.trim();
7323
+ if (!/^\d+$/.test(trimmed)) {
7324
+ throw new PncliError(`Invalid ${flag} "${value}". Expected a non-negative integer.`, 1);
7325
+ }
7326
+ return Number(trimmed);
7327
+ }
7328
+ function parseUnixTime(value, flag) {
7329
+ if (value === void 0) return void 0;
7330
+ const trimmed = value.trim();
7331
+ if (/^\d+$/.test(trimmed)) return Number(trimmed);
7332
+ const ms = Date.parse(trimmed);
7333
+ if (!Number.isFinite(ms)) {
7334
+ throw new PncliError(`Invalid ${flag} "${value}". Expected Unix seconds or an ISO 8601 date such as 2026-09-01T00:00:00Z.`, 1);
7335
+ }
7336
+ return Math.floor(ms / 1e3);
7337
+ }
7338
+ function parseChoice(value, choices, flag) {
7339
+ if (value === void 0) return void 0;
7340
+ const match = choices.find((c) => c.toLowerCase() === value.trim().toLowerCase());
7341
+ if (!match) {
7342
+ throw new PncliError(`Invalid ${flag} "${value}". Expected one of: ${choices.join(", ")}.`, 1);
7343
+ }
7344
+ return match;
7345
+ }
7346
+ function collectList(value, previous = []) {
7347
+ return [...previous, ...value.split(",").map((v) => v.trim()).filter(Boolean)];
7348
+ }
7349
+ function withRepeatedParam(path14, key, values) {
7350
+ if (!values || values.length === 0) return path14;
7351
+ const qs = values.map((v) => `${encodeURIComponent(key)}=${encodeURIComponent(v)}`).join("&");
7352
+ return `${path14}${path14.includes("?") ? "&" : "?"}${qs}`;
7353
+ }
7354
+ function passedFlag(opts) {
7355
+ if (opts.passed && opts.failed) throw new PncliError("Pass only one of --passed or --failed.", 1);
7356
+ if (opts.passed) return true;
7357
+ if (opts.failed) return false;
7358
+ return void 0;
7359
+ }
7360
+ function requireBody(body, hint) {
7361
+ if (Object.keys(body).length === 0) throw new PncliError(`Nothing to update. ${hint}`, 1);
7362
+ return body;
7363
+ }
7364
+ function id(value) {
7365
+ return encodeURIComponent(value.trim());
7366
+ }
7367
+ function countOf(data, key) {
7368
+ const list = key ? data?.[key] : data;
7369
+ return Array.isArray(list) ? list.length : void 0;
7370
+ }
7371
+ function registerSauceLabsCommands(program2) {
7372
+ function run(action, fn) {
7373
+ return async (...args) => {
7374
+ const start = Date.now();
7375
+ const { success: success2, fail: fail2 } = await import("./output-HDUIIXUI.js");
7376
+ try {
7377
+ success2(await fn(getHttp(program2), ...args), SERVICE, action, start);
7378
+ } catch (err) {
7379
+ fail2(err, SERVICE, action, start);
7380
+ }
7381
+ };
7382
+ }
7383
+ const sauce = program2.command("saucelabs").description("Sauce Labs (jobs, builds, real devices and device sessions, tunnels, platforms, app storage, teams)");
7384
+ sauce.command("status").description("Sauce Labs service status (operational flag, wait time)").action(run("status", (http) => http.saucelabs("/rest/v1/info/status")));
7385
+ sauce.command("concurrency").description("Concurrency allowed and in use for your organization and team (also verifies credentials)").action(run("concurrency", (http) => verifySauceLabsCredentials(http)));
7386
+ sauce.command("platforms").description("Supported OS/browser/device platforms").option("--api <api>", "Automation API: all, appium, webdriver", "all").action(run("platforms", async (http, opts) => {
7387
+ const api = parseChoice(opts.api, PLATFORM_APIS, "--api");
7388
+ const data = await http.saucelabs(`/rest/v1/info/platforms/${api}`);
7389
+ return { api, count: countOf(data), platforms: data };
7390
+ }));
7391
+ const job = sauce.command("job").description("Test jobs on Sauce virtual devices and desktop browsers (VDC)");
7392
+ 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) => {
7393
+ const params = {
7394
+ limit: parseIntOption(opts.limit, "--limit"),
7395
+ skip: parseIntOption(opts.skip, "--skip"),
7396
+ from: parseUnixTime(opts.from, "--from"),
7397
+ to: parseUnixTime(opts.to, "--to")
7398
+ };
7399
+ const data = await http.saucelabs(`/rest/v1/${id(http.saucelabsUsername())}/jobs`, { params });
7400
+ return { count: countOf(data), jobs: data };
7401
+ }));
7402
+ 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)}`)));
7403
+ 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) => {
7404
+ const body = {};
7405
+ if (opts.name !== void 0) body["name"] = opts.name;
7406
+ if (opts.build !== void 0) body["build"] = opts.build;
7407
+ if (opts.tags !== void 0) body["tags"] = opts.tags;
7408
+ const passed = passedFlag(opts);
7409
+ if (passed !== void 0) body["passed"] = passed;
7410
+ const visibility = parseChoice(opts.public, JOB_VISIBILITY, "--public");
7411
+ if (visibility !== void 0) body["public"] = visibility;
7412
+ return http.saucelabs(`/rest/v1/${id(http.saucelabsUsername())}/jobs/${id(jobId)}`, {
7413
+ method: "PUT",
7414
+ body: requireBody(body, "Pass at least one of --name, --build, --tags, --passed/--failed, --public.")
7415
+ });
7416
+ }));
7417
+ 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" })));
7418
+ job.command("delete").description("Delete a job and all of its assets").argument("<job-id>", "Job ID").action(run("job-delete", async (http, jobId) => {
7419
+ await http.saucelabs(`/rest/v1/${id(http.saucelabsUsername())}/jobs/${id(jobId)}`, { method: "DELETE" });
7420
+ return { jobId, deleted: true };
7421
+ }));
7422
+ 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) => {
7423
+ const data = await http.saucelabs(
7424
+ `/rest/v1/${id(http.saucelabsUsername())}/jobs/${id(jobId)}/assets`
7425
+ );
7426
+ return { jobId, assets: data };
7427
+ }));
7428
+ const rdcJob = sauce.command("rdc-job").description("Test jobs that ran on real devices");
7429
+ 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", {
7430
+ params: {
7431
+ limit: parseIntOption(opts.limit, "--limit"),
7432
+ offset: parseIntOption(opts.offset, "--offset"),
7433
+ type: opts.live ? "LIVE" : void 0
7434
+ }
7435
+ })));
7436
+ 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)}`)));
7437
+ 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) => {
7438
+ const body = {};
7439
+ if (opts.name !== void 0) body["name"] = opts.name;
7440
+ if (opts.build !== void 0) body["build"] = opts.build;
7441
+ if (opts.tags !== void 0) body["tags"] = opts.tags;
7442
+ const passed = passedFlag(opts);
7443
+ if (passed !== void 0) body["passed"] = passed;
7444
+ return http.saucelabs(`/v1/rdc/jobs/${id(jobId)}`, {
7445
+ method: "PUT",
7446
+ body: requireBody(body, "Pass at least one of --name, --build, --tags, --passed/--failed.")
7447
+ });
7448
+ }));
7449
+ rdcJob.command("stop").description("Stop a running real-device job").argument("<job-id>", "Job ID").action(run("rdc-job-stop", async (http, jobId) => {
7450
+ await http.saucelabs(`/v1/rdc/jobs/${id(jobId)}/stop`, { method: "PUT" });
7451
+ return { jobId, stopped: true };
7452
+ }));
7453
+ 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) => {
7454
+ await http.saucelabs(`/v1/rdc/jobs/${id(jobId)}`, { method: "DELETE" });
7455
+ return { jobId, deleted: true };
7456
+ }));
7457
+ const build = sauce.command("build").description("Builds (groups of jobs) on virtual (vdc) or real (rdc) devices");
7458
+ 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) => {
7459
+ const source = parseChoice(opts.source, BUILD_SOURCES, "--source");
7460
+ const statuses = opts.status?.map((s) => parseChoice(s, BUILD_STATUSES, "--status"));
7461
+ const data = await http.saucelabs(withRepeatedParam(`/v2/builds/${source}/`, "status", statuses), {
7462
+ params: {
7463
+ name: opts.name,
7464
+ start: parseUnixTime(opts.start, "--start"),
7465
+ end: parseUnixTime(opts.end, "--end"),
7466
+ limit: parseIntOption(opts.limit, "--limit"),
7467
+ offset: parseIntOption(opts.offset, "--offset"),
7468
+ sort: parseChoice(opts.sort, ["asc", "desc"], "--sort")
7469
+ }
7470
+ });
7471
+ return { source, count: countOf(data, "builds"), builds: data?.builds ?? [] };
7472
+ }));
7473
+ 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) => {
7474
+ const source = parseChoice(opts.source, BUILD_SOURCES, "--source");
7475
+ return http.saucelabs(`/v2/builds/${source}/${id(buildId)}/`);
7476
+ }));
7477
+ 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) => {
7478
+ const source = parseChoice(opts.source, BUILD_SOURCES, "--source");
7479
+ const params = { modified_since: opts["modifiedSince"] };
7480
+ for (const flag of ["failed", "errored", "passed", "running", "queued", "completed", "finished", "faulty"]) {
7481
+ if (opts[flag]) params[flag] = true;
7482
+ }
7483
+ const data = await http.saucelabs(`/v2/builds/${source}/${id(buildId)}/jobs/`, { params });
7484
+ return { buildId, source, count: countOf(data, "jobs"), jobs: data?.jobs ?? [] };
7485
+ }));
7486
+ 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) => {
7487
+ const source = parseChoice(opts.source, BUILD_SOURCES, "--source");
7488
+ return http.saucelabs(`/v2/builds/${source}/jobs/${id(jobId)}/build/`);
7489
+ }));
7490
+ const device = sauce.command("device").description("Real-device catalog and live availability");
7491
+ 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) => {
7492
+ const data = await http.saucelabs(`${RDA}/devices`, {
7493
+ params: {
7494
+ deviceName: opts.name,
7495
+ os: parseChoice(opts.os, DEVICE_OS, "--os"),
7496
+ osVersion: opts.osVersion,
7497
+ deviceType: parseChoice(opts.type, DEVICE_TYPES, "--type")
7498
+ }
7499
+ });
7500
+ return { count: countOf(data), devices: data };
7501
+ }));
7502
+ 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)}`)));
7503
+ 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) => {
7504
+ const data = await http.saucelabs(`${RDA}/devices/status`, {
7505
+ params: {
7506
+ state: parseChoice(opts.state, DEVICE_STATES, "--state"),
7507
+ deviceName: opts.name,
7508
+ privateOnly: opts.privateOnly ? true : void 0
7509
+ }
7510
+ });
7511
+ return { count: countOf(data, "devices"), devices: data?.devices ?? [] };
7512
+ }));
7513
+ const session = sauce.command("session").description("Reserve a real device and drive it over the API (open URLs, install apps, ADB shell, tests)");
7514
+ const sessionPath = (sessionId) => `${RDA}/sessions/${id(sessionId)}`;
7515
+ const deviceAction = (sessionId, action) => `${sessionPath(sessionId)}/device/${action}`;
7516
+ 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) => {
7517
+ const data = await http.saucelabs(`${RDA}/sessions`, {
7518
+ params: { state: parseChoice(opts.state, SESSION_STATES, "--state"), deviceName: opts.deviceName }
7519
+ });
7520
+ return { count: countOf(data, "sessions"), sessions: data?.sessions ?? [] };
7521
+ }));
7522
+ 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) => {
7523
+ if (opts.tunnelOwner && !opts.tunnelName) throw new PncliError("--tunnel-owner requires --tunnel-name.", 1);
7524
+ const timeoutSeconds = parseIntOption(opts.timeout, "--timeout") ?? 300;
7525
+ const deviceQuery = {};
7526
+ if (opts.deviceName) deviceQuery["deviceName"] = opts.deviceName;
7527
+ const os4 = parseChoice(opts.os, ["android", "ios"], "--os");
7528
+ if (os4) deviceQuery["os"] = os4;
7529
+ const configuration = {};
7530
+ if (opts.tunnelName) {
7531
+ configuration["tunnel"] = { name: opts.tunnelName, ...opts.tunnelOwner ? { owner: opts.tunnelOwner } : {} };
7532
+ }
7533
+ if (opts.duration) configuration["sessionDuration"] = opts.duration;
7534
+ const body = { device: deviceQuery };
7535
+ if (Object.keys(configuration).length > 0) body["configuration"] = configuration;
7536
+ const created = await http.saucelabs(`${RDA}/sessions`, { method: "POST", body });
7537
+ if (!opts.wait) return created;
7538
+ return waitForSession(http, created.id, timeoutSeconds);
7539
+ }));
7540
+ 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))));
7541
+ 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), {
7542
+ method: "DELETE",
7543
+ params: { rebootDevice: opts.reboot ? true : void 0 }
7544
+ })));
7545
+ 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) => {
7546
+ await http.saucelabs(deviceAction(sessionId, "openUrl"), { method: "POST", body: { url } });
7547
+ return { sessionId, url, opened: true };
7548
+ }));
7549
+ 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) => {
7550
+ const data = await http.saucelabs(deviceAction(sessionId, "executeShellCommand"), {
7551
+ method: "POST",
7552
+ body: { adbShellCommand: command },
7553
+ timeoutMs: 12e4
7554
+ });
7555
+ return { sessionId, command, stdout: data?.stdout ?? "" };
7556
+ }));
7557
+ 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"), {
7558
+ method: "POST",
7559
+ body: { app, enableInstrumentation: opts.instrumentation, launchAfterInstall: Boolean(opts.launch) }
7560
+ })));
7561
+ 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" })));
7562
+ const appIdentity = (opts) => {
7563
+ if (Boolean(opts.bundleId) === Boolean(opts.packageName)) {
7564
+ throw new PncliError("Pass exactly one of --bundle-id (iOS) or --package-name (Android).", 1);
7565
+ }
7566
+ return opts.bundleId ? { bundleId: opts.bundleId } : { packageName: opts.packageName };
7567
+ };
7568
+ 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) => {
7569
+ if (opts.activity && !opts.packageName) throw new PncliError("--activity requires --package-name.", 1);
7570
+ const body = { ...appIdentity(opts), ...opts.activity ? { activityName: opts.activity } : {} };
7571
+ await http.saucelabs(deviceAction(sessionId, "launchApp"), { method: "POST", body });
7572
+ return { sessionId, ...body, launched: true };
7573
+ }));
7574
+ 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) => {
7575
+ const body = appIdentity(opts);
7576
+ await http.saucelabs(deviceAction(sessionId, "uninstallApp"), { method: "POST", body });
7577
+ return { sessionId, ...body, uninstalled: true };
7578
+ }));
7579
+ 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) => {
7580
+ const body = {};
7581
+ const orientation = parseChoice(opts.orientation, ORIENTATIONS, "--orientation");
7582
+ if (orientation) body["orientation"] = orientation;
7583
+ if (opts.locale) body["locale"] = opts.locale;
7584
+ const animations = parseChoice(opts.animations, ["on", "off"], "--animations");
7585
+ if (animations) body["animations"] = animations === "on";
7586
+ await http.saucelabs(deviceAction(sessionId, "applySettings"), {
7587
+ method: "POST",
7588
+ body: requireBody(body, "Pass at least one of --orientation, --locale, --animations.")
7589
+ });
7590
+ return { sessionId, applied: body };
7591
+ }));
7592
+ 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) => {
7593
+ const body = {};
7594
+ if (opts.name) body["testName"] = opts.name;
7595
+ if (opts.build) body["build"] = opts.build;
7596
+ if (opts.tags) body["tags"] = opts.tags;
7597
+ const artifacts = {
7598
+ video: Boolean(opts.video),
7599
+ deviceLogs: Boolean(opts.deviceLogs),
7600
+ networkCapture: Boolean(opts.networkCapture),
7601
+ screenshots: Boolean(opts.screenshots),
7602
+ appiumLogs: Boolean(opts.appiumLogs)
7603
+ };
7604
+ if (Object.values(artifacts).some(Boolean)) body["artifacts"] = artifacts;
7605
+ return http.saucelabs(`${sessionPath(sessionId)}/startTest`, { method: "POST", body });
7606
+ }));
7607
+ 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) => {
7608
+ const passed = passedFlag(opts);
7609
+ return http.saucelabs(`${sessionPath(sessionId)}/endTest`, {
7610
+ method: "POST",
7611
+ body: passed === void 0 ? {} : { status: passed ? "passed" : "failed" }
7612
+ });
7613
+ }));
7614
+ 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`)));
7615
+ 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`)));
7616
+ 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) => {
7617
+ const conditions = {};
7618
+ const set = (key, value, flag, max) => {
7619
+ const n = parseIntOption(value, flag);
7620
+ if (n === void 0) return;
7621
+ if (n > max) throw new PncliError(`${flag} must be between 0 and ${max}.`, 1);
7622
+ conditions[key] = n;
7623
+ };
7624
+ set("downloadSpeed", opts.download, "--download", 5e4);
7625
+ set("uploadSpeed", opts.upload, "--upload", 5e4);
7626
+ set("latency", opts.latency, "--latency", 3e3);
7627
+ set("loss", opts.loss, "--loss", 100);
7628
+ const modes = [Boolean(opts.reset), Boolean(opts.profile), Object.keys(conditions).length > 0].filter(Boolean).length;
7629
+ if (modes !== 1) {
7630
+ throw new PncliError("Pass exactly one of --profile, --reset, or explicit conditions (--download/--upload/--latency/--loss).", 1);
7631
+ }
7632
+ if (opts.reset) {
7633
+ await http.saucelabs(`${sessionPath(sessionId)}/network/condition`, { method: "DELETE" });
7634
+ return { sessionId, reset: true };
7635
+ }
7636
+ if (opts.profile) {
7637
+ return http.saucelabs(`${sessionPath(sessionId)}/network/profile`, { method: "POST", body: { networkProfile: opts.profile } });
7638
+ }
7639
+ return http.saucelabs(`${sessionPath(sessionId)}/network/condition`, { method: "POST", body: { networkConditions: conditions } });
7640
+ }));
7641
+ 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) => {
7642
+ if (opts.appiumVersion && !opts.start) throw new PncliError("--appium-version requires --start.", 1);
7643
+ if (!opts.start) return http.saucelabs(`${sessionPath(sessionId)}/appiumserver`);
7644
+ return http.saucelabs(`${sessionPath(sessionId)}/appiumserver`, {
7645
+ method: "POST",
7646
+ body: opts.appiumVersion ? { appiumVersion: opts.appiumVersion } : {}
7647
+ });
7648
+ }));
7649
+ 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`)));
7650
+ const tunnel = sauce.command("tunnel").description("Sauce Connect tunnels");
7651
+ tunnel.command("list").description("List your tunnels with full details").option("--all", "Include tunnels shared with you").action(run("tunnel-list", async (http, opts) => {
7652
+ const data = await http.saucelabs(`/rest/v1/${id(http.saucelabsUsername())}/tunnels`, {
7653
+ params: { full: true, all: opts.all ? true : void 0 }
7654
+ });
7655
+ return { count: countOf(data), tunnels: data };
7656
+ }));
7657
+ 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)}`)));
7658
+ 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`)));
7659
+ 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" })));
7660
+ const storage = sauce.command("storage").description("App storage metadata (no upload or download)");
7661
+ 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", {
7662
+ params: {
7663
+ q: opts.query,
7664
+ kind: parseChoice(opts.kind, STORAGE_KINDS, "--kind"),
7665
+ page: parseIntOption(opts.page, "--page"),
7666
+ per_page: parseIntOption(opts.perPage, "--per-page")
7667
+ }
7668
+ })));
7669
+ 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", {
7670
+ params: {
7671
+ q: opts.query,
7672
+ kind: parseChoice(opts.kind, STORAGE_KINDS, "--kind"),
7673
+ page: parseIntOption(opts.page, "--page"),
7674
+ per_page: parseIntOption(opts.perPage, "--per-page")
7675
+ }
7676
+ })));
7677
+ const team = sauce.command("team").description("Teams in your organization");
7678
+ 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 } })));
7679
+ 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)}/`)));
7680
+ 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/`)));
7681
+ 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/`, {
7682
+ params: {
7683
+ username: opts.username,
7684
+ phrase: opts.phrase,
7685
+ limit: parseIntOption(opts.limit, "--limit"),
7686
+ offset: parseIntOption(opts.offset, "--offset")
7687
+ }
7688
+ })));
7689
+ }
7690
+ var TERMINAL_SESSION_STATES = /* @__PURE__ */ new Set(["CLOSING", "CLOSED", "ERRORED"]);
7691
+ async function waitForSession(http, sessionId, timeoutSeconds, pollMs = 5e3) {
7692
+ const deadline = Date.now() + timeoutSeconds * 1e3;
7693
+ for (; ; ) {
7694
+ const current = await http.saucelabs(`${RDA}/sessions/${id(sessionId)}`);
7695
+ if (current.state === "ACTIVE") return current;
7696
+ if (TERMINAL_SESSION_STATES.has(current.state)) {
7697
+ throw new PncliError(`Session ${sessionId} ended in state ${current.state} before becoming ACTIVE.`, 1);
7698
+ }
7699
+ if (Date.now() + pollMs > deadline) {
7700
+ throw new PncliError(
7701
+ `Session ${sessionId} still ${current.state} after ${timeoutSeconds}s. It keeps starting in the background \u2014 check it with: pncli saucelabs session get ${sessionId}`,
7702
+ 1
7703
+ );
7704
+ }
7705
+ await new Promise((resolve) => setTimeout(resolve, pollMs));
7706
+ }
7707
+ }
7708
+
7275
7709
  // src/services/config/check.ts
7276
7710
  async function runCredentialChecks(cfg, http) {
7277
7711
  const results = {};
@@ -7564,6 +7998,18 @@ async function runCredentialChecks(cfg, http) {
7564
7998
  results.logscale = categorize(err);
7565
7999
  }
7566
8000
  }
8001
+ if (!cfg.elasticsearch.apiKey) {
8002
+ results.elasticsearch = { status: "blank", message: "not configured" };
8003
+ } else if (!cfg.elasticsearch.baseUrl) {
8004
+ results.elasticsearch = { status: "error", message: "baseUrl not configured" };
8005
+ } else {
8006
+ try {
8007
+ await http.elasticsearch("/_cluster/health", { timeoutMs: 1e4 });
8008
+ results.elasticsearch = { status: "valid", message: "ok" };
8009
+ } catch (err) {
8010
+ results.elasticsearch = categorize(err);
8011
+ }
8012
+ }
7567
8013
  if (!cfg.splitio.adminApiKey) {
7568
8014
  results.splitio = { status: "blank", message: "not configured" };
7569
8015
  } else if (!cfg.splitio.baseUrl) {
@@ -7602,6 +8048,20 @@ async function runCredentialChecks(cfg, http) {
7602
8048
  results.alation = categorize(err);
7603
8049
  }
7604
8050
  }
8051
+ if (!cfg.saucelabs.accessKey) {
8052
+ results.saucelabs = { status: "blank", message: "not configured" };
8053
+ } else if (!cfg.saucelabs.baseUrl) {
8054
+ results.saucelabs = { status: "error", message: "baseUrl not configured" };
8055
+ } else if (!cfg.saucelabs.username) {
8056
+ results.saucelabs = { status: "error", message: "username not configured" };
8057
+ } else {
8058
+ try {
8059
+ await verifySauceLabsCredentials(http, 1e4);
8060
+ results.saucelabs = { status: "valid", message: "ok" };
8061
+ } catch (err) {
8062
+ results.saucelabs = categorize(err);
8063
+ }
8064
+ }
7605
8065
  return results;
7606
8066
  }
7607
8067
 
@@ -8985,7 +9445,7 @@ function pullMarketplace(marketplacePath, m, marketplaceName) {
8985
9445
  }
8986
9446
  const updated = !pullOutput.includes("Already up to date");
8987
9447
  if (pullOutput.trim() && updated) warn(pullOutput.trim());
8988
- warn(updated ? `"${marketplaceName}" updated.` : `"${marketplaceName}" already up to date \u2014 no changes to sync.`);
9448
+ warn(updated ? `"${marketplaceName}" updated.` : `"${marketplaceName}" already up to date \u2014 refreshing installed plugins from the local clone.`);
8989
9449
  return { updated };
8990
9450
  }
8991
9451
  function installPluginsForMarketplace(marketplacePath, marketplaceName, repoUrl, pluginNames, targetDir) {
@@ -9035,13 +9495,6 @@ function installMarketplaceToTarget(m, marketplacePath, pluginChoices, pluginFil
9035
9495
  } else {
9036
9496
  requested = [pluginFilter];
9037
9497
  }
9038
- const missing = requested.filter((name) => !installedHere.includes(name));
9039
- if (!opts.updated && !opts.force) {
9040
- if (missing.length === 0) {
9041
- return { ...base, plugins: {}, total: 0, skipped: true, message: "No changes detected \u2014 skipping install. Use --force to reinstall anyway." };
9042
- }
9043
- requested = missing;
9044
- }
9045
9498
  const { results, totalInstalled } = installPluginsForMarketplace(marketplacePath, marketplaceName, m.repoUrl, requested, target.target);
9046
9499
  return { ...base, plugins: results, total: totalInstalled };
9047
9500
  }
@@ -9675,7 +10128,7 @@ Plugin skills always install at user scope. --agent picks the host (default: ${D
9675
10128
  fail(err, "skills", "marketplace-remove", start);
9676
10129
  }
9677
10130
  });
9678
- marketplace.command("sync").description("With no arguments, refreshes every already-installed plugin from every marketplace (same as --marketplace all --installed-only). Pass --force, a plugin, or --marketplace to get the interactive picker instead.").argument("[plugin]", 'Plugin name to install, or "all" to install every plugin (skips interactive selection)').option("--marketplace <name>", 'Marketplace name to sync, or "all" to sync every registered marketplace (skips interactive selection)').option("--agent <agent>", `Target agent host: ${AGENT_CHOICES} (default: ${DEFAULT_AGENT})`).option("--claude", "Shorthand for --agent claude-code").option("--all-agents", "Install to every supported agent host in one run").option("--force", 'Reinstall even if a marketplace has no new changes (applies to single-plugin and "all" installs alike)').option("--installed-only", "Only sync plugins that are already installed \u2014 skip plugins newly added to the marketplace").option("--no-instructions", "Do not apply the shipped AGENTS.md / CLAUDE.md to user-level instructions files").action(async (plugin, opts) => {
10131
+ marketplace.command("sync").description("With no arguments, refreshes every already-installed plugin from every marketplace (same as --marketplace all --installed-only). Pass --force, a plugin, or --marketplace to get the interactive picker instead.").argument("[plugin]", 'Plugin name to install, or "all" to install every plugin (skips interactive selection)').option("--marketplace <name>", 'Marketplace name to sync, or "all" to sync every registered marketplace (skips interactive selection)').option("--agent <agent>", `Target agent host: ${AGENT_CHOICES} (default: ${DEFAULT_AGENT})`).option("--claude", "Shorthand for --agent claude-code").option("--all-agents", "Install to every supported agent host in one run").option("--force", "Opt into the interactive picker instead of the installed-only shorthand (plugins are always refreshed from the local clone, with or without new upstream changes)").option("--installed-only", "Only sync plugins that are already installed \u2014 skip plugins newly added to the marketplace").option("--no-instructions", "Do not apply the shipped AGENTS.md / CLAUDE.md to user-level instructions files").action(async (plugin, opts) => {
9679
10132
  const start = Date.now();
9680
10133
  try {
9681
10134
  const configPath = getGlobalConfigPath();
@@ -10168,19 +10621,31 @@ function copyPluginSkills(skillsSrc, targetDir, meta) {
10168
10621
  (name) => fs6.statSync(path11.join(skillsSrc, name)).isDirectory()
10169
10622
  );
10170
10623
  const installed = [];
10624
+ const active = [];
10171
10625
  const failed = [];
10626
+ const disabledHere = meta ? readInstalledMeta(targetDir).disabled ?? {} : {};
10172
10627
  for (const skillName of skillNames) {
10173
10628
  const dest = path11.resolve(targetDir, skillName);
10174
10629
  if (!dest.startsWith(resolvedTarget + path11.sep)) {
10175
10630
  failed.push(skillName);
10176
10631
  continue;
10177
10632
  }
10633
+ const stashed = disabledHere[skillName];
10634
+ if (meta && stashed && stashed.plugin === meta.plugin) {
10635
+ const stashDest = path11.join(targetDir, DISABLED_SUBDIR, skillName);
10636
+ fs6.mkdirSync(path11.dirname(stashDest), { recursive: true });
10637
+ fs6.rmSync(stashDest, { recursive: true, force: true });
10638
+ fs6.cpSync(path11.join(skillsSrc, skillName), stashDest, { recursive: true });
10639
+ installed.push(skillName);
10640
+ continue;
10641
+ }
10178
10642
  fs6.rmSync(dest, { recursive: true, force: true });
10179
10643
  fs6.cpSync(path11.join(skillsSrc, skillName), dest, { recursive: true });
10180
10644
  installed.push(skillName);
10645
+ active.push(skillName);
10181
10646
  }
10182
10647
  if (meta) {
10183
- recordInstalledSkills(targetDir, installed, {
10648
+ recordInstalledSkills(targetDir, active, {
10184
10649
  source: "marketplace",
10185
10650
  marketplace: meta.marketplace,
10186
10651
  plugin: meta.plugin,
@@ -10479,6 +10944,16 @@ function registerConfigCommands(program2) {
10479
10944
  } else {
10480
10945
  results.logscale = { ok: null, message: "not configured" };
10481
10946
  }
10947
+ if (cfg.elasticsearch.baseUrl && cfg.elasticsearch.apiKey) {
10948
+ try {
10949
+ await http.elasticsearch("/_cluster/health");
10950
+ results.elasticsearch = { ok: true, message: "connected" };
10951
+ } catch (err) {
10952
+ results.elasticsearch = { ok: false, message: err instanceof Error ? err.message : String(err) };
10953
+ }
10954
+ } else {
10955
+ results.elasticsearch = { ok: null, message: "not configured" };
10956
+ }
10482
10957
  if (cfg.splitio.baseUrl && cfg.splitio.adminApiKey) {
10483
10958
  try {
10484
10959
  await http.splitio("/internal/api/v2/workspaces");
@@ -10509,6 +10984,20 @@ function registerConfigCommands(program2) {
10509
10984
  } else {
10510
10985
  results.alation = { ok: null, message: "not configured" };
10511
10986
  }
10987
+ if (cfg.saucelabs.accessKey && !cfg.saucelabs.baseUrl) {
10988
+ results.saucelabs = { ok: false, message: "baseUrl not configured" };
10989
+ } else if (cfg.saucelabs.accessKey && !cfg.saucelabs.username) {
10990
+ results.saucelabs = { ok: false, message: "username not configured" };
10991
+ } else if (cfg.saucelabs.accessKey) {
10992
+ try {
10993
+ await verifySauceLabsCredentials(http);
10994
+ results.saucelabs = { ok: true, message: "connected" };
10995
+ } catch (err) {
10996
+ results.saucelabs = { ok: false, message: err instanceof Error ? err.message : String(err) };
10997
+ }
10998
+ } else {
10999
+ results.saucelabs = { ok: null, message: "not configured" };
11000
+ }
10512
11001
  success(results, "config", "test", start);
10513
11002
  } catch (err) {
10514
11003
  fail(err, "config", "test", start);
@@ -10549,9 +11038,11 @@ function registerConfigCommands(program2) {
10549
11038
  "dynatrace_platform",
10550
11039
  ...dynamicEnvKeys,
10551
11040
  "logscale",
11041
+ "elasticsearch",
10552
11042
  "splitio",
10553
11043
  "figma",
10554
- "alation"
11044
+ "alation",
11045
+ "saucelabs"
10555
11046
  ];
10556
11047
  if (cmdOpts.output === "table") {
10557
11048
  const labelWidth = Math.max(14, ...allServices.map((s) => s.length + 2));
@@ -11103,6 +11594,40 @@ async function initGlobalConfig(start) {
11103
11594
  }
11104
11595
  }
11105
11596
  }
11597
+ process.stderr.write("\n\u2500\u2500 Elasticsearch \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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");
11598
+ const useElasticsearch = await confirm({
11599
+ message: "Configure Elasticsearch for log and document searches?",
11600
+ default: false
11601
+ });
11602
+ let elasticsearchBaseUrl = "";
11603
+ let elasticsearchApiKey = "";
11604
+ if (useElasticsearch) {
11605
+ elasticsearchBaseUrl = await input2({
11606
+ message: "Elasticsearch base URL (e.g. https://elasticsearch.imagile.dev:9200):",
11607
+ default: ""
11608
+ });
11609
+ elasticsearchApiKey = await password2({
11610
+ message: 'Elasticsearch API key (the base64 "encoded" value from Kibana \u2192 Stack Management \u2192 API keys):'
11611
+ });
11612
+ if (elasticsearchBaseUrl && elasticsearchApiKey) {
11613
+ process.stderr.write("\n Verifying connection...\n");
11614
+ try {
11615
+ const tempConfig = {
11616
+ ...loadConfig(),
11617
+ elasticsearch: {
11618
+ baseUrl: normalizeBaseUrl(elasticsearchBaseUrl),
11619
+ apiKey: elasticsearchApiKey
11620
+ }
11621
+ };
11622
+ const tempHttp = createHttpClient(tempConfig);
11623
+ await tempHttp.elasticsearch("/_cluster/health");
11624
+ process.stderr.write(" Connected.\n");
11625
+ } catch (err) {
11626
+ warn(`Could not connect to Elasticsearch: ${err instanceof Error ? err.message : String(err)}`);
11627
+ warn("Config will be saved anyway. Check your URL and API key and re-run pncli config init or pncli config test.");
11628
+ }
11629
+ }
11630
+ }
11106
11631
  process.stderr.write("\n\u2500\u2500 Split.IO \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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");
11107
11632
  const useSplitio = await confirm({
11108
11633
  message: "Configure Split.IO for feature flag administration?",
@@ -11208,6 +11733,48 @@ async function initGlobalConfig(start) {
11208
11733
  }
11209
11734
  }
11210
11735
  }
11736
+ 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");
11737
+ const useSaucelabs = await confirm({
11738
+ message: "Configure Sauce Labs for jobs, builds, and real-device access?",
11739
+ default: false
11740
+ });
11741
+ let saucelabsBaseUrl = "";
11742
+ let saucelabsUsername = "";
11743
+ let saucelabsAccessKey = "";
11744
+ if (useSaucelabs) {
11745
+ process.stderr.write(" Find your username and access key in Sauce Labs under Account \u2192 User Settings.\n");
11746
+ process.stderr.write(" The API host depends on your data center: https://api.us-west-1.saucelabs.com,\n");
11747
+ process.stderr.write(" https://api.eu-central-1.saucelabs.com, or https://api.us-east-4.saucelabs.com.\n");
11748
+ saucelabsBaseUrl = await input2({
11749
+ message: "Sauce Labs API base URL:",
11750
+ validate: (v) => v.trim().length > 0 || "Required"
11751
+ });
11752
+ saucelabsUsername = await input2({
11753
+ message: "Sauce Labs username:",
11754
+ validate: (v) => v.trim().length > 0 || "Required"
11755
+ });
11756
+ saucelabsAccessKey = await password2({
11757
+ message: "Sauce Labs access key:"
11758
+ });
11759
+ if (saucelabsBaseUrl && saucelabsUsername && saucelabsAccessKey) {
11760
+ process.stderr.write("\n Verifying connection...\n");
11761
+ try {
11762
+ const tempConfig = {
11763
+ ...loadConfig(),
11764
+ saucelabs: {
11765
+ baseUrl: normalizeBaseUrl(saucelabsBaseUrl),
11766
+ username: saucelabsUsername.trim(),
11767
+ accessKey: saucelabsAccessKey
11768
+ }
11769
+ };
11770
+ await verifySauceLabsCredentials(createHttpClient(tempConfig));
11771
+ process.stderr.write(" Connected.\n");
11772
+ } catch (err) {
11773
+ warn(`Could not connect to Sauce Labs: ${err instanceof Error ? err.message : String(err)}`);
11774
+ 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.");
11775
+ }
11776
+ }
11777
+ }
11211
11778
  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
11779
  const jiraProject = await input2({
11213
11780
  message: "Default Jira project key (optional):",
@@ -11358,6 +11925,12 @@ async function initGlobalConfig(start) {
11358
11925
  token: logscaleToken || void 0
11359
11926
  }
11360
11927
  } : {},
11928
+ ...useElasticsearch && elasticsearchBaseUrl ? {
11929
+ elasticsearch: {
11930
+ baseUrl: normalizeBaseUrl(elasticsearchBaseUrl),
11931
+ apiKey: elasticsearchApiKey || void 0
11932
+ }
11933
+ } : {},
11361
11934
  ...useSplitio && splitioBaseUrl ? {
11362
11935
  splitio: {
11363
11936
  baseUrl: normalizeBaseUrl(splitioBaseUrl),
@@ -11377,6 +11950,13 @@ async function initGlobalConfig(start) {
11377
11950
  userId: alationUserId.trim() || void 0
11378
11951
  }
11379
11952
  } : {},
11953
+ ...useSaucelabs && saucelabsBaseUrl ? {
11954
+ saucelabs: {
11955
+ baseUrl: normalizeBaseUrl(saucelabsBaseUrl),
11956
+ username: saucelabsUsername.trim() || void 0,
11957
+ accessKey: saucelabsAccessKey || void 0
11958
+ }
11959
+ } : {},
11380
11960
  defaults: {
11381
11961
  jira: {
11382
11962
  project: jiraProject || void 0
@@ -11818,6 +12398,7 @@ function registerDoctorCommands(program2) {
11818
12398
 
11819
12399
  // src/services/ado/client/git.ts
11820
12400
  var API7 = "7.1";
12401
+ var REQUIRED_REVIEWERS_POLICY_TYPE = "fd2167ab-b0be-447a-8ec8-39368250530e";
11821
12402
  var AdoGitClient = class {
11822
12403
  constructor(http) {
11823
12404
  this.http = http;
@@ -11913,6 +12494,20 @@ var AdoGitClient = class {
11913
12494
  const pr = await this.getPR(collection, project, repo, prId);
11914
12495
  return pr.reviewers;
11915
12496
  }
12497
+ /**
12498
+ * Default reviewers in Azure DevOps are "Required reviewers" branch policies.
12499
+ * Returns the policy configurations of that type whose scope covers the repo
12500
+ * (a scope entry with a null repositoryId applies project-wide).
12501
+ */
12502
+ async listDefaultReviewers(collection, project, repo) {
12503
+ const repoInfo = await this.getRepo(collection, project, repo);
12504
+ const result = await this.http.ado(
12505
+ `/${encodeURIComponent(collection)}/${encodeURIComponent(project)}/_apis/policy/configurations?api-version=${API7}`
12506
+ );
12507
+ return (result.value ?? []).filter(
12508
+ (c) => c.type?.id === REQUIRED_REVIEWERS_POLICY_TYPE && (c.settings?.scope ?? []).some((s) => !s.repositoryId || s.repositoryId === repoInfo.id)
12509
+ );
12510
+ }
11916
12511
  // ── Threads / Comments ────────────────────────────────────────────
11917
12512
  async listThreads(collection, project, repo, prId) {
11918
12513
  const result = await this.http.ado(
@@ -11993,9 +12588,9 @@ var AdoBuildClient = class {
11993
12588
  );
11994
12589
  });
11995
12590
  }
11996
- async getDefinition(collection, project, id) {
12591
+ async getDefinition(collection, project, id2) {
11997
12592
  return this.http.ado(
11998
- `/${encodeURIComponent(collection)}/${encodeURIComponent(project)}/_apis/build/definitions/${id}?api-version=${API8}`
12593
+ `/${encodeURIComponent(collection)}/${encodeURIComponent(project)}/_apis/build/definitions/${id2}?api-version=${API8}`
11999
12594
  );
12000
12595
  }
12001
12596
  // ── Builds (runs) ─────────────────────────────────────────────────
@@ -12605,7 +13200,7 @@ function registerAdoRepoCommands(ado) {
12605
13200
  const start = Date.now();
12606
13201
  try {
12607
13202
  const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
12608
- const reviewers = opts.reviewers ? opts.reviewers.split(",").map((id) => ({ id: id.trim() })) : [];
13203
+ const reviewers = opts.reviewers ? opts.reviewers.split(",").map((id2) => ({ id: id2.trim() })) : [];
12609
13204
  const data = await gitClient.createPR(collection, project, repo2, {
12610
13205
  title: opts.title,
12611
13206
  description: opts.description,
@@ -12623,7 +13218,7 @@ function registerAdoRepoCommands(ado) {
12623
13218
  const start = Date.now();
12624
13219
  try {
12625
13220
  const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
12626
- const reviewers = opts.reviewers ? opts.reviewers.split(",").map((id) => ({ id: id.trim() })) : void 0;
13221
+ const reviewers = opts.reviewers ? opts.reviewers.split(",").map((id2) => ({ id: id2.trim() })) : void 0;
12627
13222
  const body = {};
12628
13223
  if (opts.title) body.title = opts.title;
12629
13224
  if (opts.description !== void 0) body.description = opts.description;
@@ -12805,16 +13400,26 @@ function registerAdoRepoCommands(ado) {
12805
13400
  fail(err, "ado", "repo-list-reviewers", start);
12806
13401
  }
12807
13402
  });
13403
+ repo.command("list-default-reviewers").description("List required-reviewer branch policies (default reviewers) that apply to the repository").action(async () => {
13404
+ const start = Date.now();
13405
+ try {
13406
+ const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
13407
+ const data = await gitClient.listDefaultReviewers(collection, project, repo2);
13408
+ success(data, "ado", "repo-list-default-reviewers", start);
13409
+ } catch (err) {
13410
+ fail(err, "ado", "repo-list-default-reviewers", start);
13411
+ }
13412
+ });
12808
13413
  const voteCmd = (name, description, vote, action) => {
12809
13414
  repo.command(name).description(description).requiredOption("--pr <n>", "Pull request ID").action(async (opts) => {
12810
13415
  const start = Date.now();
12811
13416
  try {
12812
13417
  const globalOpts = ado.optsWithGlobals();
12813
- const { loadConfig: lc } = await import("./config-OH2O5E3P.js");
13418
+ const { loadConfig: lc } = await import("./config-DYIXSPLA.js");
12814
13419
  const config = lc({ configPath: globalOpts.config });
12815
13420
  const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
12816
13421
  const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
12817
- const { createHttpClient: createHttpClient2 } = await import("./http-V7PZD7LE.js");
13422
+ const { createHttpClient: createHttpClient2 } = await import("./http-3FAKF3X4.js");
12818
13423
  const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
12819
13424
  const coreClient = new AdoCoreClient2(http);
12820
13425
  const me = await coreClient.getConnectionData(collection);
@@ -13489,8 +14094,8 @@ var CheckmarxClient = class {
13489
14094
  }
13490
14095
  return projects;
13491
14096
  }
13492
- async getProject(id) {
13493
- return this.http.checkmarx(`projects/${id}`);
14097
+ async getProject(id2) {
14098
+ return this.http.checkmarx(`projects/${id2}`);
13494
14099
  }
13495
14100
  async listScans(opts = {}) {
13496
14101
  const params = { limit: opts.last ?? 100 };
@@ -13503,8 +14108,8 @@ var CheckmarxClient = class {
13503
14108
  }
13504
14109
  return scans;
13505
14110
  }
13506
- async getScan(id) {
13507
- return this.http.checkmarx(`scans/${id}`);
14111
+ async getScan(id2) {
14112
+ return this.http.checkmarx(`scans/${id2}`);
13508
14113
  }
13509
14114
  async getScanResultsStatistics(scanId) {
13510
14115
  return this.http.checkmarx("results/summary", {
@@ -13820,7 +14425,7 @@ function resolveCluster(config, env, instance) {
13820
14425
  }
13821
14426
  return { baseUrl: config.openshift.baseUrl, token: config.openshift.token };
13822
14427
  }
13823
- function getHttp(oc) {
14428
+ function getHttp2(oc) {
13824
14429
  const opts = oc.optsWithGlobals();
13825
14430
  const config = loadConfig({ configPath: opts.config });
13826
14431
  const cluster = resolveCluster(config, opts.env, opts.instance);
@@ -13896,7 +14501,7 @@ function registerOpenShiftCommands(program2) {
13896
14501
  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
14502
  const start = Date.now();
13898
14503
  try {
13899
- const http = getHttp(oc);
14504
+ const http = getHttp2(oc);
13900
14505
  const params = {};
13901
14506
  if (opts.labelSelector) params["labelSelector"] = opts.labelSelector;
13902
14507
  const podList = await http.openshift(
@@ -14002,10 +14607,59 @@ function registerOpenShiftCommands(program2) {
14002
14607
  fail(err, "openshift", "pods", start);
14003
14608
  }
14004
14609
  });
14610
+ oc.command("deployments").description("List Deployments (or OpenShift DeploymentConfigs with --deployment-configs) for a namespace, read-only. Use --raw to return the full unmodified objects.").requiredOption("--namespace <ns>", "Kubernetes namespace").option("--label-selector <selector>", "Label selector (e.g. app=my-app)").option("--deployment-configs", "Read OpenShift DeploymentConfigs (apps.openshift.io/v1) instead of Deployments (apps/v1)", false).option("--raw", "Return full objects without summarization (includes pod template, env vars, strategy, etc.)", false).action(async (opts) => {
14611
+ const start = Date.now();
14612
+ try {
14613
+ const http = getHttp2(oc);
14614
+ const params = {};
14615
+ if (opts.labelSelector) params["labelSelector"] = opts.labelSelector;
14616
+ const kind = opts.deploymentConfigs ? "DeploymentConfig" : "Deployment";
14617
+ const path14 = opts.deploymentConfigs ? `/apis/apps.openshift.io/v1/namespaces/${encodeURIComponent(opts.namespace)}/deploymentconfigs` : `/apis/apps/v1/namespaces/${encodeURIComponent(opts.namespace)}/deployments`;
14618
+ const list = await http.openshift(path14, { params });
14619
+ if (opts.raw) {
14620
+ success(
14621
+ { namespace: opts.namespace, kind, count: list.items.length, deployments: list.items },
14622
+ "openshift",
14623
+ "deployments",
14624
+ start
14625
+ );
14626
+ return;
14627
+ }
14628
+ const deployments = list.items.map((d) => {
14629
+ const desired = d.spec.replicas ?? 0;
14630
+ const status = d.status ?? {};
14631
+ const entry = {
14632
+ name: d.metadata.name,
14633
+ ready: `${status.readyReplicas ?? 0}/${desired}`,
14634
+ desiredReplicas: desired,
14635
+ readyReplicas: status.readyReplicas ?? 0,
14636
+ updatedReplicas: status.updatedReplicas ?? 0,
14637
+ availableReplicas: status.availableReplicas ?? 0,
14638
+ unavailableReplicas: status.unavailableReplicas ?? 0,
14639
+ age: podAge(d.metadata.creationTimestamp),
14640
+ images: (d.spec.template?.spec?.containers ?? []).map((c) => ({ name: c.name, image: c.image }))
14641
+ };
14642
+ if (d.spec.strategy?.type) entry["strategy"] = d.spec.strategy.type;
14643
+ if (status.latestVersion !== void 0) entry["latestVersion"] = status.latestVersion;
14644
+ const problems = (status.conditions ?? []).filter(
14645
+ (c) => c.type === "Available" && c.status === "False" || c.type === "Progressing" && c.status === "False" || c.type === "ReplicaFailure"
14646
+ );
14647
+ if (problems.length > 0) {
14648
+ entry["conditions"] = problems.map((c) => ({ type: c.type, status: c.status, reason: c.reason, message: c.message }));
14649
+ }
14650
+ if (d.metadata.labels && Object.keys(d.metadata.labels).length > 0) entry["labels"] = d.metadata.labels;
14651
+ return entry;
14652
+ });
14653
+ deployments.sort((a, b) => (b["unavailableReplicas"] ?? 0) - (a["unavailableReplicas"] ?? 0));
14654
+ success({ namespace: opts.namespace, kind, count: deployments.length, deployments }, "openshift", "deployments", start);
14655
+ } catch (err) {
14656
+ fail(err, "openshift", "deployments", start);
14657
+ }
14658
+ });
14005
14659
  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
14660
  const start = Date.now();
14007
14661
  try {
14008
- const http = getHttp(oc);
14662
+ const http = getHttp2(oc);
14009
14663
  const params = {};
14010
14664
  const selectors = [];
14011
14665
  if (!opts.all) selectors.push("type=Warning");
@@ -14039,7 +14693,7 @@ function registerOpenShiftCommands(program2) {
14039
14693
  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
14694
  const start = Date.now();
14041
14695
  try {
14042
- const http = getHttp(oc);
14696
+ const http = getHttp2(oc);
14043
14697
  const lines = opts.lines ? parseInt(opts.lines, 10) : 100;
14044
14698
  const logText = await http.openshiftText(
14045
14699
  `/api/v1/namespaces/${encodeURIComponent(opts.namespace)}/pods/${encodeURIComponent(opts.pod)}/log`,
@@ -14071,7 +14725,7 @@ function registerOpenShiftCommands(program2) {
14071
14725
  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
14726
  const start = Date.now();
14073
14727
  try {
14074
- const http = getHttp(oc);
14728
+ const http = getHttp2(oc);
14075
14729
  const metricsList = await http.openshift(
14076
14730
  `/apis/metrics.k8s.io/v1beta1/namespaces/${encodeURIComponent(opts.namespace)}/pods`
14077
14731
  );
@@ -14091,7 +14745,7 @@ function registerOpenShiftCommands(program2) {
14091
14745
  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
14746
  const start = Date.now();
14093
14747
  try {
14094
- const http = getHttp(oc);
14748
+ const http = getHttp2(oc);
14095
14749
  const nsEncoded = encodeURIComponent(opts.namespace);
14096
14750
  const podParams = {};
14097
14751
  if (opts.labelSelector) podParams["labelSelector"] = opts.labelSelector;
@@ -14156,7 +14810,7 @@ function registerOpenShiftCommands(program2) {
14156
14810
  }
14157
14811
 
14158
14812
  // src/services/dynatrace/commands.ts
14159
- function getHttp2(program2, envName) {
14813
+ function getHttp3(program2, envName) {
14160
14814
  const opts = program2.optsWithGlobals();
14161
14815
  const cfg = loadConfig({ configPath: opts.config });
14162
14816
  if (envName) {
@@ -14280,7 +14934,7 @@ function registerDynatraceCommands(program2) {
14280
14934
  const start = Date.now();
14281
14935
  try {
14282
14936
  const envName = dynatrace.opts().env;
14283
- const data = await listEntities(getHttp2(program2, envName), opts.selector, opts);
14937
+ const data = await listEntities(getHttp3(program2, envName), opts.selector, opts);
14284
14938
  success({ totalCount: data.totalCount, entities: data.items }, "dynatrace", "entities list", start);
14285
14939
  } catch (err) {
14286
14940
  fail(err, "dynatrace", "entities list", start);
@@ -14290,7 +14944,7 @@ function registerDynatraceCommands(program2) {
14290
14944
  const start = Date.now();
14291
14945
  try {
14292
14946
  const envName = dynatrace.opts().env;
14293
- const data = await getHttp2(program2, envName).dynatrace(
14947
+ const data = await getHttp3(program2, envName).dynatrace(
14294
14948
  `/api/v2/entities/${encodeURIComponent(opts.id)}`,
14295
14949
  { params: { from: opts.from, to: opts.to, fields: opts.fields } }
14296
14950
  );
@@ -14303,7 +14957,7 @@ function registerDynatraceCommands(program2) {
14303
14957
  const start = Date.now();
14304
14958
  try {
14305
14959
  const envName = dynatrace.opts().env;
14306
- const data = await listEntities(getHttp2(program2, envName), 'type("SERVICE")', opts);
14960
+ const data = await listEntities(getHttp3(program2, envName), 'type("SERVICE")', opts);
14307
14961
  success({ totalCount: data.totalCount, services: data.items }, "dynatrace", "services", start);
14308
14962
  } catch (err) {
14309
14963
  fail(err, "dynatrace", "services", start);
@@ -14314,7 +14968,7 @@ function registerDynatraceCommands(program2) {
14314
14968
  try {
14315
14969
  const envName = dynatrace.opts().env;
14316
14970
  const selector = 'type("CLOUD_APPLICATION")';
14317
- const data = await listEntities(getHttp2(program2, envName), selector, opts);
14971
+ const data = await listEntities(getHttp3(program2, envName), selector, opts);
14318
14972
  success({ totalCount: data.totalCount, workloads: data.items }, "dynatrace", "workloads", start);
14319
14973
  } catch (err) {
14320
14974
  fail(err, "dynatrace", "workloads", start);
@@ -14326,7 +14980,7 @@ function registerDynatraceCommands(program2) {
14326
14980
  try {
14327
14981
  const envName = dynatrace.opts().env;
14328
14982
  const data = await allPages(async (nextPageKey) => {
14329
- const page = await getHttp2(program2, envName).dynatrace("/api/v2/problems", {
14983
+ const page = await getHttp3(program2, envName).dynatrace("/api/v2/problems", {
14330
14984
  params: nextPageKey ? { nextPageKey } : {
14331
14985
  from: opts.from ?? "now-2h",
14332
14986
  to: opts.to ?? "now",
@@ -14346,7 +15000,7 @@ function registerDynatraceCommands(program2) {
14346
15000
  const start = Date.now();
14347
15001
  try {
14348
15002
  const envName = dynatrace.opts().env;
14349
- const data = await getHttp2(program2, envName).dynatrace(
15003
+ const data = await getHttp3(program2, envName).dynatrace(
14350
15004
  `/api/v2/problems/${encodeURIComponent(opts.id)}`,
14351
15005
  { params: { fields: opts.fields } }
14352
15006
  );
@@ -14359,7 +15013,7 @@ function registerDynatraceCommands(program2) {
14359
15013
  const start = Date.now();
14360
15014
  try {
14361
15015
  const envName = dynatrace.opts().env;
14362
- const result = await runTraceQuery(getHttp2(program2, envName), opts.id);
15016
+ const result = await runTraceQuery(getHttp3(program2, envName), opts.id);
14363
15017
  success(result, "dynatrace", "trace", start);
14364
15018
  } catch (err) {
14365
15019
  fail(err, "dynatrace", "trace", start);
@@ -14368,7 +15022,7 @@ function registerDynatraceCommands(program2) {
14368
15022
  }
14369
15023
 
14370
15024
  // src/services/logscale/commands.ts
14371
- function getHttp3(program2) {
15025
+ function getHttp4(program2) {
14372
15026
  const opts = program2.optsWithGlobals();
14373
15027
  return createHttpClient(
14374
15028
  loadConfig({ configPath: opts.config }),
@@ -14381,7 +15035,7 @@ function registerLogscaleCommands(program2) {
14381
15035
  repositories.command("list").description("List available repositories").action(async () => {
14382
15036
  const start = Date.now();
14383
15037
  try {
14384
- const data = await getHttp3(program2).logscale("/api/v1/repositories");
15038
+ const data = await getHttp4(program2).logscale("/api/v1/repositories");
14385
15039
  success({ count: data.length, repositories: data }, "logscale", "repositories list", start);
14386
15040
  } catch (err) {
14387
15041
  fail(err, "logscale", "repositories list", start);
@@ -14398,7 +15052,7 @@ function registerLogscaleCommands(program2) {
14398
15052
  showQueryEventDistribution: false,
14399
15053
  isLive: false
14400
15054
  };
14401
- const data = await getHttp3(program2).logscale(
15055
+ const data = await getHttp4(program2).logscale(
14402
15056
  `/api/v1/repositories/${encodeURIComponent(opts.repository)}/query`,
14403
15057
  {
14404
15058
  method: "POST",
@@ -14426,10 +15080,121 @@ function registerLogscaleCommands(program2) {
14426
15080
  });
14427
15081
  }
14428
15082
 
15083
+ // src/services/elasticsearch/commands.ts
15084
+ function getHttp5(program2) {
15085
+ const opts = program2.optsWithGlobals();
15086
+ return createHttpClient(
15087
+ loadConfig({ configPath: opts.config }),
15088
+ Boolean(opts.dryRun)
15089
+ );
15090
+ }
15091
+ function parseJsonOption(raw, flag) {
15092
+ let parsed;
15093
+ try {
15094
+ parsed = JSON.parse(raw);
15095
+ } catch {
15096
+ throw new PncliError(`${flag} must be valid JSON. Example: ${flag} '{"match_all":{}}'`);
15097
+ }
15098
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
15099
+ throw new PncliError(`${flag} must be a JSON object.`);
15100
+ }
15101
+ return parsed;
15102
+ }
15103
+ function parsePositiveInt(raw, flag) {
15104
+ const n = Number(raw);
15105
+ if (!Number.isInteger(n) || n < 0) throw new PncliError(`${flag} must be a non-negative integer.`);
15106
+ return n;
15107
+ }
15108
+ function indexPath(index) {
15109
+ return index.split(",").map((s) => encodeURIComponent(s.trim())).join(",");
15110
+ }
15111
+ function buildQuery(opts) {
15112
+ if (opts.query && opts.q) throw new PncliError("Use either --query or --q, not both.");
15113
+ if (opts.query) return parseJsonOption(opts.query, "--query");
15114
+ if (opts.q) return { query_string: { query: opts.q } };
15115
+ return { match_all: {} };
15116
+ }
15117
+ function registerElasticsearchCommands(program2) {
15118
+ const es = program2.command("elasticsearch").description("Elasticsearch search operations");
15119
+ const cluster = es.command("cluster").description("Cluster operations");
15120
+ cluster.command("health").description("Show cluster health (status, node and shard counts)").action(async () => {
15121
+ const start = Date.now();
15122
+ try {
15123
+ const data = await getHttp5(program2).elasticsearch("/_cluster/health");
15124
+ success(data, "elasticsearch", "cluster health", start);
15125
+ } catch (err) {
15126
+ fail(err, "elasticsearch", "cluster health", start);
15127
+ }
15128
+ });
15129
+ const indices = es.command("indices").description("Index operations");
15130
+ indices.command("list").description("List indices with health, document count and size").option("--pattern <pattern>", 'Index name or wildcard pattern (e.g. "logs-*")').action(async (opts) => {
15131
+ const start = Date.now();
15132
+ try {
15133
+ const path14 = opts.pattern ? `/_cat/indices/${indexPath(opts.pattern)}` : "/_cat/indices";
15134
+ const data = await getHttp5(program2).elasticsearch(path14, {
15135
+ params: { format: "json", h: "index,health,status,docs.count,store.size,pri,rep" }
15136
+ });
15137
+ success({ count: data.length, indices: data }, "elasticsearch", "indices list", start);
15138
+ } catch (err) {
15139
+ fail(err, "elasticsearch", "indices list", start);
15140
+ }
15141
+ });
15142
+ es.command("search").description("Search one or more indices with Query DSL JSON or a query string").requiredOption("--index <name>", "Index, data stream, alias or wildcard pattern; comma-separate several").option("--query <json>", `Query DSL clause as JSON (e.g. '{"match":{"message":"error"}}')`).option("--q <query-string>", 'Lucene query string (e.g. "level:error AND service:api")').option("--sort <field:order>", 'Sort, e.g. "@timestamp:desc"').option("--source <fields>", "Comma-separated fields to return (_source filtering)").option("--size <n>", "Maximum number of hits to return", "100").option("--from <n>", "Offset of the first hit", "0").option("--timeout-ms <ms>", "Client-side HTTP timeout in milliseconds", "30000").action(async (opts) => {
15143
+ const ts = Date.now();
15144
+ try {
15145
+ const body = {
15146
+ query: buildQuery(opts),
15147
+ size: parsePositiveInt(opts.size, "--size"),
15148
+ from: parsePositiveInt(opts.from, "--from")
15149
+ };
15150
+ if (opts.sort) {
15151
+ const [field, order] = opts.sort.split(":");
15152
+ body.sort = [{ [field]: { order: order ?? "asc" } }];
15153
+ }
15154
+ if (opts.source) body._source = opts.source.split(",").map((s) => s.trim()).filter(Boolean);
15155
+ const data = await getHttp5(program2).elasticsearch(
15156
+ `/${indexPath(opts.index)}/_search`,
15157
+ { method: "POST", body, timeoutMs: parsePositiveInt(opts.timeoutMs, "--timeout-ms") }
15158
+ );
15159
+ const total = data.hits?.total;
15160
+ const hits = data.hits?.hits ?? [];
15161
+ success(
15162
+ {
15163
+ index: opts.index,
15164
+ took: data.took,
15165
+ timedOut: data.timed_out ?? false,
15166
+ total: typeof total === "object" ? total?.value : total,
15167
+ totalRelation: typeof total === "object" ? total?.relation : void 0,
15168
+ hitCount: hits.length,
15169
+ hits,
15170
+ aggregations: data.aggregations
15171
+ },
15172
+ "elasticsearch",
15173
+ "search",
15174
+ ts
15175
+ );
15176
+ } catch (err) {
15177
+ fail(err, "elasticsearch", "search", ts);
15178
+ }
15179
+ });
15180
+ es.command("count").description("Count documents matching a query").requiredOption("--index <name>", "Index, data stream, alias or wildcard pattern; comma-separate several").option("--query <json>", "Query DSL clause as JSON").option("--q <query-string>", "Lucene query string").action(async (opts) => {
15181
+ const start = Date.now();
15182
+ try {
15183
+ const data = await getHttp5(program2).elasticsearch(
15184
+ `/${indexPath(opts.index)}/_count`,
15185
+ { method: "POST", body: { query: buildQuery(opts) } }
15186
+ );
15187
+ success({ index: opts.index, count: data.count }, "elasticsearch", "count", start);
15188
+ } catch (err) {
15189
+ fail(err, "elasticsearch", "count", start);
15190
+ }
15191
+ });
15192
+ }
15193
+
14429
15194
  // src/services/splitio/commands.ts
14430
15195
  import fs11 from "fs";
14431
15196
  import confirm2 from "@inquirer/confirm";
14432
- function getHttp4(program2) {
15197
+ function getHttp6(program2) {
14433
15198
  const opts = program2.optsWithGlobals();
14434
15199
  return createHttpClient(
14435
15200
  loadConfig({ configPath: opts.config }),
@@ -14464,7 +15229,7 @@ function buildCrBase(opts) {
14464
15229
  crNumber: opts.changeNumber
14465
15230
  };
14466
15231
  if (opts.approvers) {
14467
- base["approvers"] = opts.approvers.split(",").map((s) => s.trim()).filter(Boolean).map((id) => ({ id }));
15232
+ base["approvers"] = opts.approvers.split(",").map((s) => s.trim()).filter(Boolean).map((id2) => ({ id: id2 }));
14468
15233
  }
14469
15234
  return base;
14470
15235
  }
@@ -14474,7 +15239,7 @@ function registerSplitioCommands(program2) {
14474
15239
  workspaces.command("list").description("List all Split.IO workspaces").action(async () => {
14475
15240
  const start = Date.now();
14476
15241
  try {
14477
- const http = getHttp4(program2);
15242
+ const http = getHttp6(program2);
14478
15243
  const data = await http.splitio("/internal/api/v2/workspaces");
14479
15244
  success(
14480
15245
  { count: data.objects.length, workspaces: data.objects },
@@ -14490,7 +15255,7 @@ function registerSplitioCommands(program2) {
14490
15255
  environments.command("list").description("List environments in a workspace").requiredOption("--workspace <id>", "Workspace ID").action(async (opts) => {
14491
15256
  const start = Date.now();
14492
15257
  try {
14493
- const http = getHttp4(program2);
15258
+ const http = getHttp6(program2);
14494
15259
  const data = await http.splitio(
14495
15260
  `/internal/api/v2/environments/ws/${encodeURIComponent(opts.workspace)}`
14496
15261
  );
@@ -14508,7 +15273,7 @@ function registerSplitioCommands(program2) {
14508
15273
  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
15274
  const start = Date.now();
14510
15275
  try {
14511
- const http = getHttp4(program2);
15276
+ const http = getHttp6(program2);
14512
15277
  const params = {
14513
15278
  limit: parseInt(opts.limit, 10),
14514
15279
  offset: parseInt(opts.offset, 10)
@@ -14531,7 +15296,7 @@ function registerSplitioCommands(program2) {
14531
15296
  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
15297
  const start = Date.now();
14533
15298
  try {
14534
- const http = getHttp4(program2);
15299
+ const http = getHttp6(program2);
14535
15300
  if (opts.environment) {
14536
15301
  const data = await http.splitio(
14537
15302
  `/internal/api/v2/splits/ws/${encodeURIComponent(opts.workspace)}/name/${encodeURIComponent(opts.flag)}/environment/${encodeURIComponent(opts.environment)}`
@@ -14553,7 +15318,7 @@ function registerSplitioCommands(program2) {
14553
15318
  if (!opts.inputFile) throw new PncliError("--input-file is required for flags update");
14554
15319
  const definition = loadInputFile(opts.inputFile);
14555
15320
  await confirmWrite(opts, `UPDATE ${opts.flag} in ${opts.environment}`);
14556
- const http = getHttp4(program2);
15321
+ const http = getHttp6(program2);
14557
15322
  const body = {
14558
15323
  ...buildCrBase(opts),
14559
15324
  operationType: "UPDATE_SPLIT_DEFINITION",
@@ -14579,7 +15344,7 @@ function registerSplitioCommands(program2) {
14579
15344
  if (!opts.treatment && !opts.weights) {
14580
15345
  throw new PncliError("Either --treatment or --weights must be specified");
14581
15346
  }
14582
- const http = getHttp4(program2);
15347
+ const http = getHttp6(program2);
14583
15348
  const current = await http.splitio(
14584
15349
  `/internal/api/v2/splits/ws/${encodeURIComponent(opts.workspace)}/name/${encodeURIComponent(opts.flag)}/environment/${encodeURIComponent(opts.environment)}`
14585
15350
  );
@@ -14617,7 +15382,7 @@ function registerSplitioCommands(program2) {
14617
15382
  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
15383
  const start = Date.now();
14619
15384
  try {
14620
- const http = getHttp4(program2);
15385
+ const http = getHttp6(program2);
14621
15386
  const current = await http.splitio(
14622
15387
  `/internal/api/v2/splits/ws/${encodeURIComponent(opts.workspace)}/name/${encodeURIComponent(opts.flag)}/environment/${encodeURIComponent(opts.environment)}`
14623
15388
  );
@@ -14650,7 +15415,7 @@ function registerSplitioCommands(program2) {
14650
15415
  const enable = opts.enabled.toLowerCase() !== "false" && opts.enabled !== "0";
14651
15416
  const opType = enable ? "RESTORE_SPLIT" : "KILL_SPLIT";
14652
15417
  await confirmWrite(opts, `${opType} ${opts.flag} in ${opts.environment}`);
14653
- const http = getHttp4(program2);
15418
+ const http = getHttp6(program2);
14654
15419
  const body = {
14655
15420
  ...buildCrBase(opts),
14656
15421
  operationType: opType,
@@ -14673,7 +15438,7 @@ function registerSplitioCommands(program2) {
14673
15438
  const entries = loadInputFile(opts.inputFile);
14674
15439
  if (!Array.isArray(entries)) throw new PncliError("Input file must be a JSON array of {flag, enabled} objects");
14675
15440
  await confirmWrite(opts, `BATCH_TOGGLE ${entries.length} flags in ${opts.environment}`);
14676
- const http = getHttp4(program2);
15441
+ const http = getHttp6(program2);
14677
15442
  const results = [];
14678
15443
  for (const entry of entries) {
14679
15444
  try {
@@ -14707,7 +15472,7 @@ function registerSplitioCommands(program2) {
14707
15472
  const start = Date.now();
14708
15473
  try {
14709
15474
  await confirmWrite(opts, `KILL ${opts.flag} in ${opts.environment}`);
14710
- const http = getHttp4(program2);
15475
+ const http = getHttp6(program2);
14711
15476
  const body = {
14712
15477
  ...buildCrBase(opts),
14713
15478
  operationType: "KILL_SPLIT",
@@ -14727,7 +15492,7 @@ function registerSplitioCommands(program2) {
14727
15492
  const start = Date.now();
14728
15493
  try {
14729
15494
  await confirmWrite(opts, `RESTORE ${opts.flag} in ${opts.environment}`);
14730
- const http = getHttp4(program2);
15495
+ const http = getHttp6(program2);
14731
15496
  const body = {
14732
15497
  ...buildCrBase(opts),
14733
15498
  operationType: "RESTORE_SPLIT",
@@ -14747,7 +15512,7 @@ function registerSplitioCommands(program2) {
14747
15512
  const start = Date.now();
14748
15513
  try {
14749
15514
  await confirmWrite(opts, `ARCHIVE ${opts.flag}`);
14750
- const http = getHttp4(program2);
15515
+ const http = getHttp6(program2);
14751
15516
  const body = {
14752
15517
  ...buildCrBase(opts),
14753
15518
  operationType: "ARCHIVE_SPLIT",
@@ -14767,7 +15532,7 @@ function registerSplitioCommands(program2) {
14767
15532
  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
15533
  const start = Date.now();
14769
15534
  try {
14770
- const http = getHttp4(program2);
15535
+ const http = getHttp6(program2);
14771
15536
  const params = {
14772
15537
  limit: parseInt(opts.limit, 10),
14773
15538
  offset: parseInt(opts.offset, 10)
@@ -14791,7 +15556,7 @@ function registerSplitioCommands(program2) {
14791
15556
  changeRequests.command("get").description("Get a Change Request by ID").requiredOption("--id <id>", "Change Request ID").action(async (opts) => {
14792
15557
  const start = Date.now();
14793
15558
  try {
14794
- const http = getHttp4(program2);
15559
+ const http = getHttp6(program2);
14795
15560
  const data = await http.splitio(
14796
15561
  `/internal/api/v2/changeRequests/${encodeURIComponent(opts.id)}`
14797
15562
  );
@@ -14843,7 +15608,7 @@ function findNodeIdInUrl(url) {
14843
15608
  if (!new URL(url).searchParams.has("node-id")) return void 0;
14844
15609
  return parseNodeId(url);
14845
15610
  }
14846
- function getHttp5(program2) {
15611
+ function getHttp7(program2) {
14847
15612
  const opts = program2.optsWithGlobals();
14848
15613
  return createHttpClient(
14849
15614
  loadConfig({ configPath: opts.config }),
@@ -14859,7 +15624,7 @@ function registerFigmaCommands(program2) {
14859
15624
  const fileKey = parseFileKey(fileKeyOrUrl);
14860
15625
  const nodeId = opts.nodeId ? parseNodeId(opts.nodeId) : isUrl(fileKeyOrUrl) ? findNodeIdInUrl(fileKeyOrUrl) : void 0;
14861
15626
  if (nodeId) {
14862
- const data2 = await getHttp5(program2).figma(
15627
+ const data2 = await getHttp7(program2).figma(
14863
15628
  `/v1/files/${encodeURIComponent(fileKey)}/nodes`,
14864
15629
  { params: { ids: nodeId } }
14865
15630
  );
@@ -14888,7 +15653,7 @@ function registerFigmaCommands(program2) {
14888
15653
  if (!opts.document) {
14889
15654
  params["depth"] = 1;
14890
15655
  }
14891
- const data = await getHttp5(program2).figma(`/v1/files/${encodeURIComponent(fileKey)}`, { params });
15656
+ const data = await getHttp7(program2).figma(`/v1/files/${encodeURIComponent(fileKey)}`, { params });
14892
15657
  success2(
14893
15658
  {
14894
15659
  fileKey,
@@ -14918,7 +15683,7 @@ function registerFigmaCommands(program2) {
14918
15683
  const fileKey = parseFileKey(fileKeyOrUrl);
14919
15684
  const params = {};
14920
15685
  if (opts.asOf) params["as_of"] = opts.asOf;
14921
- const data = await getHttp5(program2).figma(`/v1/files/${encodeURIComponent(fileKey)}/comments`, { params });
15686
+ const data = await getHttp7(program2).figma(`/v1/files/${encodeURIComponent(fileKey)}/comments`, { params });
14922
15687
  success2(
14923
15688
  { fileKey, count: data.comments.length, comments: data.comments },
14924
15689
  "figma",
@@ -14934,7 +15699,7 @@ function registerFigmaCommands(program2) {
14934
15699
  const { success: success2, fail: fail2 } = await import("./output-HDUIIXUI.js");
14935
15700
  try {
14936
15701
  const fileKey = parseFileKey(fileKeyOrUrl);
14937
- const data = await getHttp5(program2).figma(`/v1/files/${encodeURIComponent(fileKey)}/versions`);
15702
+ const data = await getHttp7(program2).figma(`/v1/files/${encodeURIComponent(fileKey)}/versions`);
14938
15703
  success2(
14939
15704
  { fileKey, count: data.versions.length, versions: data.versions },
14940
15705
  "figma",
@@ -14949,7 +15714,7 @@ function registerFigmaCommands(program2) {
14949
15714
  const start = Date.now();
14950
15715
  const { success: success2, fail: fail2 } = await import("./output-HDUIIXUI.js");
14951
15716
  try {
14952
- const data = await getHttp5(program2).figma(`/v1/projects/${encodeURIComponent(projectId)}/files`);
15717
+ const data = await getHttp7(program2).figma(`/v1/projects/${encodeURIComponent(projectId)}/files`);
14953
15718
  success2(
14954
15719
  { projectId, count: data.files.length, files: data.files },
14955
15720
  "figma",
@@ -14964,7 +15729,7 @@ function registerFigmaCommands(program2) {
14964
15729
  const start = Date.now();
14965
15730
  const { success: success2, fail: fail2 } = await import("./output-HDUIIXUI.js");
14966
15731
  try {
14967
- const data = await getHttp5(program2).figma("/v1/me");
15732
+ const data = await getHttp7(program2).figma("/v1/me");
14968
15733
  success2(data, "figma", "me", start);
14969
15734
  } catch (err) {
14970
15735
  fail2(err, "figma", "me", start);
@@ -14975,7 +15740,7 @@ function registerFigmaCommands(program2) {
14975
15740
  // src/services/alation/commands.ts
14976
15741
  import fs12 from "fs";
14977
15742
  var MAX_PAGE = 1e3;
14978
- function parseIntOption(value, flag) {
15743
+ function parseIntOption2(value, flag) {
14979
15744
  if (value === void 0) return void 0;
14980
15745
  const trimmed = value.trim();
14981
15746
  if (!/^\d+$/.test(trimmed)) {
@@ -14991,8 +15756,8 @@ function parsePositiveId(value, what) {
14991
15756
  return Number(trimmed);
14992
15757
  }
14993
15758
  function pageParams(opts) {
14994
- const limit = parseIntOption(opts.limit, "--limit") ?? 100;
14995
- const skip = parseIntOption(opts.skip, "--skip") ?? 0;
15759
+ const limit = parseIntOption2(opts.limit, "--limit") ?? 100;
15760
+ const skip = parseIntOption2(opts.skip, "--skip") ?? 0;
14996
15761
  if (limit < 1 || limit > MAX_PAGE) {
14997
15762
  throw new PncliError(`--limit must be between 1 and ${MAX_PAGE} (Alation's per-request cap).`, 1);
14998
15763
  }
@@ -15079,17 +15844,17 @@ var ALATION_DOCUMENT_INPUT_FILE_EXAMPLE = {
15079
15844
  template: 7,
15080
15845
  fields: { "10001": "Team Data Platform" }
15081
15846
  };
15082
- function getHttp6(program2) {
15847
+ function getHttp8(program2) {
15083
15848
  const opts = program2.optsWithGlobals();
15084
15849
  return createHttpClient(
15085
15850
  loadConfig({ configPath: opts.config }),
15086
15851
  Boolean(opts.dryRun)
15087
15852
  );
15088
15853
  }
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);
15854
+ async function getOne(http, path14, id2, what) {
15855
+ const rows = await http.alation(path14, { params: { id: id2, limit: 1 } });
15856
+ const row = Array.isArray(rows) ? rows.find((r) => r.id === id2) ?? rows[0] : void 0;
15857
+ if (!row) throw new PncliError(`${what} ${id2} not found`, 404);
15093
15858
  return row;
15094
15859
  }
15095
15860
  function buildDocumentPayload(opts) {
@@ -15155,17 +15920,17 @@ function registerAlationCommands(program2) {
15155
15920
  const params = {};
15156
15921
  if (opts.includeUndeployed) params.include_undeployed = true;
15157
15922
  if (opts.includeHidden) params.include_hidden = true;
15158
- const data = await getHttp6(program2).alation("integration/v1/datasource/", { params });
15923
+ const data = await getHttp8(program2).alation("integration/v1/datasource/", { params });
15159
15924
  success({ count: data.length, datasources: data }, "alation", "datasource-list", start);
15160
15925
  } catch (err) {
15161
15926
  fail(err, "alation", "datasource-list", start);
15162
15927
  }
15163
15928
  });
15164
- datasource.command("get").description("Get a data source by ID").argument("<id>", "Data source ID").action(async (id) => {
15929
+ datasource.command("get").description("Get a data source by ID").argument("<id>", "Data source ID").action(async (id2) => {
15165
15930
  const start = Date.now();
15166
15931
  try {
15167
- const dsId = parsePositiveId(id, "data source ID");
15168
- const data = await getHttp6(program2).alation(`integration/v1/datasource/${dsId}/`);
15932
+ const dsId = parsePositiveId(id2, "data source ID");
15933
+ const data = await getHttp8(program2).alation(`integration/v1/datasource/${dsId}/`);
15169
15934
  success(data, "alation", "datasource-get", start);
15170
15935
  } catch (err) {
15171
15936
  fail(err, "alation", "datasource-get", start);
@@ -15178,16 +15943,16 @@ function registerAlationCommands(program2) {
15178
15943
  const params = { ...pageParams(opts) };
15179
15944
  if (opts.ds) params.ds_id = parsePositiveId(opts.ds, "--ds");
15180
15945
  if (opts.name) params.name = opts.name;
15181
- const data = await getHttp6(program2).alation("integration/v2/schema/", { params });
15946
+ const data = await getHttp8(program2).alation("integration/v2/schema/", { params });
15182
15947
  success({ count: data.length, limit: params.limit, skip: params.skip, schemas: data }, "alation", "schema-list", start);
15183
15948
  } catch (err) {
15184
15949
  fail(err, "alation", "schema-list", start);
15185
15950
  }
15186
15951
  });
15187
- schema.command("get").description("Get a schema by ID").argument("<id>", "Schema ID").action(async (id) => {
15952
+ schema.command("get").description("Get a schema by ID").argument("<id>", "Schema ID").action(async (id2) => {
15188
15953
  const start = Date.now();
15189
15954
  try {
15190
- const data = await getOne(getHttp6(program2), "integration/v2/schema/", parsePositiveId(id, "schema ID"), "Schema");
15955
+ const data = await getOne(getHttp8(program2), "integration/v2/schema/", parsePositiveId(id2, "schema ID"), "Schema");
15191
15956
  success(data, "alation", "schema-get", start);
15192
15957
  } catch (err) {
15193
15958
  fail(err, "alation", "schema-get", start);
@@ -15203,17 +15968,17 @@ function registerAlationCommands(program2) {
15203
15968
  if (opts.schemaName) params.schema_name = opts.schemaName;
15204
15969
  if (opts.name) params.name = opts.name;
15205
15970
  if (opts.search) params.name__icontains = opts.search;
15206
- const data = await getHttp6(program2).alation("integration/v2/table/", { params });
15971
+ const data = await getHttp8(program2).alation("integration/v2/table/", { params });
15207
15972
  success({ count: data.length, limit: params.limit, skip: params.skip, tables: data }, "alation", "table-list", start);
15208
15973
  } catch (err) {
15209
15974
  fail(err, "alation", "table-list", start);
15210
15975
  }
15211
15976
  });
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) => {
15977
+ 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
15978
  const start = Date.now();
15214
15979
  try {
15215
- const http = getHttp6(program2);
15216
- const tableId = parsePositiveId(id, "table ID");
15980
+ const http = getHttp8(program2);
15981
+ const tableId = parsePositiveId(id2, "table ID");
15217
15982
  const data = await getOne(http, "integration/v2/table/", tableId, "Table");
15218
15983
  if (opts.columns) {
15219
15984
  const columns = await http.alation("integration/v2/column/", {
@@ -15241,16 +16006,16 @@ function registerAlationCommands(program2) {
15241
16006
  if (opts.ds) params.ds_id = parsePositiveId(opts.ds, "--ds");
15242
16007
  if (opts.name) params.name = opts.name;
15243
16008
  if (opts.search) params.name__icontains = opts.search;
15244
- const data = await getHttp6(program2).alation("integration/v2/column/", { params });
16009
+ const data = await getHttp8(program2).alation("integration/v2/column/", { params });
15245
16010
  success({ count: data.length, limit: params.limit, skip: params.skip, columns: data }, "alation", "column-list", start);
15246
16011
  } catch (err) {
15247
16012
  fail(err, "alation", "column-list", start);
15248
16013
  }
15249
16014
  });
15250
- column.command("get").description("Get a column by ID").argument("<id>", "Column ID").action(async (id) => {
16015
+ column.command("get").description("Get a column by ID").argument("<id>", "Column ID").action(async (id2) => {
15251
16016
  const start = Date.now();
15252
16017
  try {
15253
- const data = await getOne(getHttp6(program2), "integration/v2/column/", parsePositiveId(id, "column ID"), "Column");
16018
+ const data = await getOne(getHttp8(program2), "integration/v2/column/", parsePositiveId(id2, "column ID"), "Column");
15254
16019
  success(data, "alation", "column-get", start);
15255
16020
  } catch (err) {
15256
16021
  fail(err, "alation", "column-get", start);
@@ -15259,13 +16024,13 @@ function registerAlationCommands(program2) {
15259
16024
  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
16025
  const start = Date.now();
15261
16026
  try {
15262
- const limit = parseIntOption(opts.limit, "--limit") ?? 20;
15263
- const offset = parseIntOption(opts.offset, "--offset") ?? 0;
16027
+ const limit = parseIntOption2(opts.limit, "--limit") ?? 20;
16028
+ const offset = parseIntOption2(opts.offset, "--offset") ?? 0;
15264
16029
  if (limit > 1e4) throw new PncliError("--limit must be at most 10000", 1);
15265
16030
  const params = { q: query, limit, offset };
15266
16031
  const otypes = parseOtypes(opts.otype);
15267
16032
  if (otypes) params.filters = JSON.stringify({ otypes });
15268
- const data = await getHttp6(program2).alation("integration/v1/search/", { params });
16033
+ const data = await getHttp8(program2).alation("integration/v1/search/", { params });
15269
16034
  success(
15270
16035
  { query, total: data.total, limit: data.limit, offset: data.offset, fullSearchUrl: data.full_search_url, results: data.results },
15271
16036
  "alation",
@@ -15284,16 +16049,16 @@ function registerAlationCommands(program2) {
15284
16049
  if (opts.hub) params.document_hub_id = parsePositiveId(opts.hub, "--hub");
15285
16050
  if (opts.parent) params.parent_folder_id = parsePositiveId(opts.parent, "--parent");
15286
16051
  if (opts.search) params.search = opts.search;
15287
- const data = await getHttp6(program2).alation("integration/v2/folder/", { params });
16052
+ const data = await getHttp8(program2).alation("integration/v2/folder/", { params });
15288
16053
  success({ count: data.length, limit: params.limit, skip: params.skip, folders: data }, "alation", "folder-list", start);
15289
16054
  } catch (err) {
15290
16055
  fail(err, "alation", "folder-list", start);
15291
16056
  }
15292
16057
  });
15293
- folder.command("get").description("Get a Document Hub folder by ID").argument("<id>", "Folder ID").action(async (id) => {
16058
+ folder.command("get").description("Get a Document Hub folder by ID").argument("<id>", "Folder ID").action(async (id2) => {
15294
16059
  const start = Date.now();
15295
16060
  try {
15296
- const data = await getOne(getHttp6(program2), "integration/v2/folder/", parsePositiveId(id, "folder ID"), "Folder");
16061
+ const data = await getOne(getHttp8(program2), "integration/v2/folder/", parsePositiveId(id2, "folder ID"), "Folder");
15297
16062
  success(data, "alation", "folder-get", start);
15298
16063
  } catch (err) {
15299
16064
  fail(err, "alation", "folder-get", start);
@@ -15309,16 +16074,16 @@ function registerAlationCommands(program2) {
15309
16074
  if (opts.parentDocument) params.parent_document_id = parsePositiveId(opts.parentDocument, "--parent-document");
15310
16075
  if (opts.search) params.search = opts.search;
15311
16076
  if (opts.deleted) params.deleted = true;
15312
- const data = await getHttp6(program2).alation("integration/v2/document/", { params });
16077
+ const data = await getHttp8(program2).alation("integration/v2/document/", { params });
15313
16078
  success({ count: data.length, limit: params.limit, skip: params.skip, documents: data }, "alation", "document-list", start);
15314
16079
  } catch (err) {
15315
16080
  fail(err, "alation", "document-list", start);
15316
16081
  }
15317
16082
  });
15318
- document.command("get").description("Get a document by ID, including its custom field values").argument("<id>", "Document ID").action(async (id) => {
16083
+ document.command("get").description("Get a document by ID, including its custom field values").argument("<id>", "Document ID").action(async (id2) => {
15319
16084
  const start = Date.now();
15320
16085
  try {
15321
- const data = await getOne(getHttp6(program2), "integration/v2/document/", parsePositiveId(id, "document ID"), "Document");
16086
+ const data = await getOne(getHttp8(program2), "integration/v2/document/", parsePositiveId(id2, "document ID"), "Document");
15322
16087
  success(data, "alation", "document-get", start);
15323
16088
  } catch (err) {
15324
16089
  fail(err, "alation", "document-get", start);
@@ -15339,16 +16104,16 @@ function registerAlationCommands(program2) {
15339
16104
  for (const [key, flag] of [["title", "--title"], ["document_hub_id", "--hub"], ["parent_folder_id", "--folder"]]) {
15340
16105
  if (payload[key] === void 0) throw new PncliError(`${flag} is required to create a document (or set it in --input-file)`, 1);
15341
16106
  }
15342
- const data = await getHttp6(program2).alation("integration/v2/document/", { method: "POST", body: [payload] });
16107
+ const data = await getHttp8(program2).alation("integration/v2/document/", { method: "POST", body: [payload] });
15343
16108
  success({ jobId: data.job_id, document: payload, ...overrides.length ? { overrides } : {} }, "alation", "document-create", start);
15344
16109
  } catch (err) {
15345
16110
  fail(err, "alation", "document-create", start);
15346
16111
  }
15347
16112
  });
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) => {
16113
+ 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
16114
  const start = Date.now();
15350
16115
  try {
15351
- const documentId = parsePositiveId(id, "document ID");
16116
+ const documentId = parsePositiveId(id2, "document ID");
15352
16117
  const { payload, overrides } = buildDocumentPayload(opts);
15353
16118
  if (payload.document_hub_id !== void 0) {
15354
16119
  throw new PncliError('A document cannot change hubs; remove "hub" from --input-file.', 1);
@@ -15357,18 +16122,18 @@ function registerAlationCommands(program2) {
15357
16122
  throw new PncliError("Nothing to update. Pass --title, --description, --folder, or --input-file.", 1);
15358
16123
  }
15359
16124
  const body = [{ id: documentId, ...payload }];
15360
- const data = await getHttp6(program2).alation("integration/v2/document/", { method: "PUT", body });
16125
+ const data = await getHttp8(program2).alation("integration/v2/document/", { method: "PUT", body });
15361
16126
  success({ jobId: data.job_id, document: body[0], ...overrides.length ? { overrides } : {} }, "alation", "document-update", start);
15362
16127
  } catch (err) {
15363
16128
  fail(err, "alation", "document-update", start);
15364
16129
  }
15365
16130
  });
15366
16131
  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) => {
16132
+ job.command("get").description("Get the status of a bulk job (returned by document create/update)").argument("<id>", "Job ID").action(async (id2) => {
15368
16133
  const start = Date.now();
15369
16134
  try {
15370
- const jobId = parsePositiveId(id, "job ID");
15371
- const data = await getHttp6(program2).alation("api/v1/bulk_metadata/job/", { params: { id: jobId } });
16135
+ const jobId = parsePositiveId(id2, "job ID");
16136
+ const data = await getHttp8(program2).alation("api/v1/bulk_metadata/job/", { params: { id: jobId } });
15372
16137
  success({ jobId, ...data }, "alation", "job-get", start);
15373
16138
  } catch (err) {
15374
16139
  fail(err, "alation", "job-get", start);
@@ -15377,9 +16142,7 @@ function registerAlationCommands(program2) {
15377
16142
  }
15378
16143
 
15379
16144
  // src/cli.ts
15380
- if (!process.env.PNCLI_VERIFY_TLS) {
15381
- process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
15382
- }
16145
+ configureTls();
15383
16146
  await configureProxy();
15384
16147
  var TAGLINE = "Connectivity without MCP.";
15385
16148
  var program = new Command3();
@@ -15419,9 +16182,11 @@ registerJwtCommands(program);
15419
16182
  registerOpenShiftCommands(program);
15420
16183
  registerDynatraceCommands(program);
15421
16184
  registerLogscaleCommands(program);
16185
+ registerElasticsearchCommands(program);
15422
16186
  registerSplitioCommands(program);
15423
16187
  registerFigmaCommands(program);
15424
16188
  registerAlationCommands(program);
16189
+ registerSauceLabsCommands(program);
15425
16190
  program.addHelpText("after", `
15426
16191
  Services:
15427
16192
  git Local git operations (status, diff, log, branch)
@@ -15438,12 +16203,14 @@ Services:
15438
16203
  checkmarx Checkmarx One (projects, scans, scan statistics)
15439
16204
  contrast Contrast IAST (applications, vulnerability findings, libraries)
15440
16205
  sonatypeiq Sonatype IQ Server (applications, organizations, policies)
15441
- openshift OpenShift / Kubernetes (pods, events, logs, metrics)
16206
+ openshift OpenShift / Kubernetes (pods, deployments, events, logs, metrics)
15442
16207
  dynatrace Dynatrace (services, entities, problems, traces, Kubernetes workloads)
15443
16208
  logscale LogScale (log query, repositories)
16209
+ elasticsearch Elasticsearch (search, count, indices, cluster health)
15444
16210
  splitio Split.IO (feature flags, Change Requests)
15445
16211
  figma Figma (files, comments, version history)
15446
16212
  alation Alation (data sources, schemas, tables, columns, search, Document Hubs)
16213
+ saucelabs Sauce Labs (jobs, builds, real devices and device sessions, tunnels, platforms)
15447
16214
  config Manage pncli configuration (and OS keychain storage for credentials)
15448
16215
  doctor Diagnose pncli setup (config, credentials, keychain, git auth, agent skills, marketplaces)
15449
16216
  skills Install and manage agent skills (Codex, GitHub Copilot, Claude Code)