@kolatts/pncli 1.26.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -4,15 +4,18 @@ import {
4
4
  loadConfig,
5
5
  loadJsonFile,
6
6
  maskConfig,
7
+ normalizeBaseUrl,
7
8
  setConfigValue,
8
9
  setRepoConfigValue,
9
10
  writeGlobalConfig,
10
11
  writeRepoConfig
11
- } from "./chunk-JOAWUILY.js";
12
+ } from "./chunk-PXKUQPPF.js";
13
+ import {
14
+ createHttpClient
15
+ } from "./chunk-ZDJYOI3W.js";
12
16
  import {
13
17
  ExitCode,
14
18
  PncliError,
15
- createHttpClient,
16
19
  fail,
17
20
  log,
18
21
  setGlobalOptions,
@@ -20,7 +23,7 @@ import {
20
23
  success,
21
24
  warn,
22
25
  writeRawOutput
23
- } from "./chunk-AGELHQKO.js";
26
+ } from "./chunk-HZF6WQPU.js";
24
27
  import {
25
28
  AdoCoreClient
26
29
  } from "./chunk-VDL32PSG.js";
@@ -6311,16 +6314,6 @@ function registerConfigCommands(program2) {
6311
6314
  } else {
6312
6315
  results.artifactory = { ok: null, message: "not configured" };
6313
6316
  }
6314
- if (cfg.udeploy.baseUrl && (cfg.udeploy.pat || cfg.udeploy.username && cfg.udeploy.password)) {
6315
- try {
6316
- await http.udeploy("/cli/application");
6317
- results.udeploy = { ok: true, message: "connected" };
6318
- } catch (err) {
6319
- results.udeploy = { ok: false, message: err instanceof Error ? err.message : String(err) };
6320
- }
6321
- } else {
6322
- results.udeploy = { ok: null, message: "not configured" };
6323
- }
6324
6317
  if (cfg.checkmarx.baseUrl && cfg.checkmarx.tenantName && (cfg.checkmarx.apiKey || cfg.checkmarx.clientId && cfg.checkmarx.clientSecret)) {
6325
6318
  try {
6326
6319
  await http.checkmarx("projects", { params: { limit: 1 } });
@@ -6413,6 +6406,26 @@ function registerConfigCommands(program2) {
6413
6406
  } else {
6414
6407
  results.logscale = { ok: null, message: "not configured" };
6415
6408
  }
6409
+ if (cfg.splitio.baseUrl && cfg.splitio.adminApiKey) {
6410
+ try {
6411
+ await http.splitio("/internal/api/v2/workspaces");
6412
+ results.splitio = { ok: true, message: "connected" };
6413
+ } catch (err) {
6414
+ results.splitio = { ok: false, message: err instanceof Error ? err.message : String(err) };
6415
+ }
6416
+ } else {
6417
+ results.splitio = { ok: null, message: "not configured" };
6418
+ }
6419
+ if (cfg.figma.baseUrl && cfg.figma.token) {
6420
+ try {
6421
+ await http.figma("/v1/me");
6422
+ results.figma = { ok: true, message: "connected" };
6423
+ } catch (err) {
6424
+ results.figma = { ok: false, message: err instanceof Error ? err.message : String(err) };
6425
+ }
6426
+ } else {
6427
+ results.figma = { ok: null, message: "not configured" };
6428
+ }
6416
6429
  success(results, "config", "test", start);
6417
6430
  } catch (err) {
6418
6431
  fail(err, "config", "test", start);
@@ -6537,18 +6550,6 @@ function registerConfigCommands(program2) {
6537
6550
  results.jenkins = categorize2(err);
6538
6551
  }
6539
6552
  }
6540
- if (!cfg.udeploy.pat && !(cfg.udeploy.username && cfg.udeploy.password)) {
6541
- results.udeploy = { status: "blank", message: "not configured" };
6542
- } else if (!cfg.udeploy.baseUrl) {
6543
- results.udeploy = { status: "error", message: "baseUrl not configured" };
6544
- } else {
6545
- try {
6546
- await http.udeploy("/cli/application", { timeoutMs: 1e4 });
6547
- results.udeploy = { status: "valid", message: "ok" };
6548
- } catch (err) {
6549
- results.udeploy = categorize2(err);
6550
- }
6551
- }
6552
6553
  const artResult = await checkArtifactoryConnectivity(cfg.artifactory);
6553
6554
  if (!artResult.configured) {
6554
6555
  results.artifactory = { status: "blank", message: artResult.error ?? "not configured" };
@@ -6669,11 +6670,35 @@ function registerConfigCommands(program2) {
6669
6670
  results.logscale = categorize2(err);
6670
6671
  }
6671
6672
  }
6673
+ if (!cfg.splitio.adminApiKey) {
6674
+ results.splitio = { status: "blank", message: "not configured" };
6675
+ } else if (!cfg.splitio.baseUrl) {
6676
+ results.splitio = { status: "error", message: "baseUrl not configured" };
6677
+ } else {
6678
+ try {
6679
+ await http.splitio("/internal/api/v2/workspaces", { timeoutMs: 1e4 });
6680
+ results.splitio = { status: "valid", message: "ok" };
6681
+ } catch (err) {
6682
+ results.splitio = categorize2(err);
6683
+ }
6684
+ }
6685
+ if (!cfg.figma.token) {
6686
+ results.figma = { status: "blank", message: "not configured" };
6687
+ } else if (!cfg.figma.baseUrl) {
6688
+ results.figma = { status: "error", message: "baseUrl not configured" };
6689
+ } else {
6690
+ try {
6691
+ await http.figma("/v1/me", { timeoutMs: 1e4 });
6692
+ results.figma = { status: "valid", message: "ok" };
6693
+ } catch (err) {
6694
+ results.figma = categorize2(err);
6695
+ }
6696
+ }
6672
6697
  const statuses = Object.values(results).map((r) => r.status);
6673
6698
  if (statuses.includes("invalid")) process.exitCode = ExitCode.AUTH_ERROR;
6674
6699
  else if (statuses.includes("error")) process.exitCode = ExitCode.NETWORK_ERROR;
6675
6700
  if (cmdOpts.output === "table") {
6676
- const services = ["jira", "bitbucket", "github", "confluence", "sonar", "sde", "ado", "jenkins", "udeploy", "artifactory", "checkmarx", "servicenow", "contrast", "sonatypeiq", "openshift", "dynatrace", "dynatrace_platform", "logscale"];
6701
+ const services = ["jira", "bitbucket", "github", "confluence", "sonar", "sde", "ado", "jenkins", "artifactory", "checkmarx", "servicenow", "contrast", "sonatypeiq", "openshift", "dynatrace", "dynatrace_platform", "logscale", "splitio", "figma"];
6677
6702
  const labelWidth = 14;
6678
6703
  const statusWidth = 9;
6679
6704
  for (const svc of services) {
@@ -6687,7 +6712,7 @@ function registerConfigCommands(program2) {
6687
6712
  process.stdout.write("\n");
6688
6713
  } else {
6689
6714
  if (opts.pretty) {
6690
- const services = ["jira", "bitbucket", "github", "confluence", "sonar", "sde", "ado", "jenkins", "udeploy", "artifactory", "checkmarx", "servicenow", "contrast", "sonatypeiq", "openshift", "dynatrace", "dynatrace_platform", "logscale"];
6715
+ const services = ["jira", "bitbucket", "github", "confluence", "sonar", "sde", "ado", "jenkins", "artifactory", "checkmarx", "servicenow", "contrast", "sonatypeiq", "openshift", "dynatrace", "dynatrace_platform", "logscale", "splitio", "figma"];
6691
6716
  const labelWidth = 14;
6692
6717
  const statusWidth = 9;
6693
6718
  for (const svc of services) {
@@ -6707,11 +6732,6 @@ function registerConfigCommands(program2) {
6707
6732
  }
6708
6733
  });
6709
6734
  }
6710
- function normalizeBaseUrl(raw) {
6711
- const trimmed = raw.trim();
6712
- if (!trimmed) return trimmed;
6713
- return /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
6714
- }
6715
6735
  async function initGlobalConfig(start) {
6716
6736
  process.stderr.write("pncli config init \u2014 Global configuration\n\n");
6717
6737
  process.stderr.write("\u2500\u2500 Identity \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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");
@@ -6939,7 +6959,7 @@ async function initGlobalConfig(start) {
6939
6959
  }
6940
6960
  process.stderr.write("\n\u2500\u2500 Jenkins \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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");
6941
6961
  const useJenkins = await confirm({
6942
- message: "Configure Jenkins Data Center for pipeline operations?",
6962
+ message: "Configure Jenkins for pipeline operations?",
6943
6963
  default: false
6944
6964
  });
6945
6965
  let jenkinsBaseUrl = "";
@@ -6958,66 +6978,6 @@ async function initGlobalConfig(start) {
6958
6978
  message: "Jenkins API token:"
6959
6979
  });
6960
6980
  }
6961
- process.stderr.write("\n\u2500\u2500 IBM UrbanCode Deploy \u2500\u2500\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");
6962
- const useUdeploy = await confirm({
6963
- message: "Configure IBM UrbanCode Deploy for component imports and deployment processes?",
6964
- default: false
6965
- });
6966
- let udeployBaseUrl = "";
6967
- let udeployPat = "";
6968
- let udeployUsername = "";
6969
- let udeployPassword = "";
6970
- let udeployDefaultApp = "";
6971
- let udeployDefaultEnv = "";
6972
- if (useUdeploy) {
6973
- udeployBaseUrl = await input({
6974
- message: "UDeploy base URL (e.g. ucd.imagile.dev:8443):",
6975
- default: ""
6976
- });
6977
- udeployUsername = await input({
6978
- message: "UDeploy username (leave blank to use a PAT instead):",
6979
- default: ""
6980
- });
6981
- if (udeployUsername) {
6982
- udeployPassword = await password({
6983
- message: "UDeploy password:",
6984
- validate: (v) => v.length > 0 || "Password cannot be blank"
6985
- });
6986
- } else {
6987
- udeployPat = await password({
6988
- message: "UDeploy personal access token:"
6989
- });
6990
- }
6991
- udeployDefaultApp = await input({
6992
- message: "Default application name (optional):",
6993
- default: ""
6994
- });
6995
- udeployDefaultEnv = await input({
6996
- message: "Default environment name (optional):",
6997
- default: ""
6998
- });
6999
- const hasCredentials = udeployBaseUrl && (udeployPat || udeployUsername && udeployPassword);
7000
- if (hasCredentials) {
7001
- process.stderr.write("\n Verifying connection...\n");
7002
- try {
7003
- const tempConfig = {
7004
- ...loadConfig(),
7005
- udeploy: {
7006
- baseUrl: normalizeBaseUrl(udeployBaseUrl),
7007
- pat: udeployPat || void 0,
7008
- username: udeployUsername || void 0,
7009
- password: udeployPassword || void 0
7010
- }
7011
- };
7012
- const tempHttp = createHttpClient(tempConfig);
7013
- await tempHttp.udeploy("/cli/application");
7014
- process.stderr.write(" Connected.\n");
7015
- } catch (err) {
7016
- warn(`Could not connect to UDeploy: ${err instanceof Error ? err.message : String(err)}`);
7017
- warn("Config will be saved anyway. Check your URL and credentials and re-run pncli config init or pncli config test.");
7018
- }
7019
- }
7020
- }
7021
6981
  process.stderr.write("\n\u2500\u2500 Checkmarx \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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");
7022
6982
  const useCheckmarx = await confirm({
7023
6983
  message: "Configure Checkmarx One for vulnerability scanning?",
@@ -7344,6 +7304,70 @@ async function initGlobalConfig(start) {
7344
7304
  }
7345
7305
  }
7346
7306
  }
7307
+ 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");
7308
+ const useSplitio = await confirm({
7309
+ message: "Configure Split.IO for feature flag administration?",
7310
+ default: false
7311
+ });
7312
+ let splitioBaseUrl = "";
7313
+ let splitioAdminApiKey = "";
7314
+ if (useSplitio) {
7315
+ splitioBaseUrl = await input({
7316
+ message: "Split.IO Admin API base URL (e.g. https://api.split.io):",
7317
+ default: ""
7318
+ });
7319
+ splitioAdminApiKey = await password({
7320
+ message: "Split.IO Admin API key:"
7321
+ });
7322
+ if (splitioBaseUrl && splitioAdminApiKey) {
7323
+ process.stderr.write("\n Verifying connection...\n");
7324
+ try {
7325
+ const tempConfig = {
7326
+ ...loadConfig(),
7327
+ splitio: {
7328
+ baseUrl: normalizeBaseUrl(splitioBaseUrl),
7329
+ adminApiKey: splitioAdminApiKey
7330
+ }
7331
+ };
7332
+ const tempHttp = createHttpClient(tempConfig);
7333
+ await tempHttp.splitio("/internal/api/v2/workspaces");
7334
+ process.stderr.write(" Connected.\n");
7335
+ } catch (err) {
7336
+ warn(`Could not connect to Split.IO: ${err instanceof Error ? err.message : String(err)}`);
7337
+ warn("Config will be saved anyway. Check your URL and API key and re-run pncli config init or pncli config test.");
7338
+ }
7339
+ }
7340
+ }
7341
+ process.stderr.write("\n\u2500\u2500 Figma \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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");
7342
+ const useFigma = await confirm({
7343
+ message: "Configure Figma for reading design files and comments?",
7344
+ default: false
7345
+ });
7346
+ let figmaToken = "";
7347
+ if (useFigma) {
7348
+ process.stderr.write(" Generate a personal access token in Figma under Account Settings \u2192 Personal access tokens.\n");
7349
+ figmaToken = await password({
7350
+ message: "Figma personal access token:"
7351
+ });
7352
+ if (figmaToken) {
7353
+ process.stderr.write("\n Verifying connection...\n");
7354
+ try {
7355
+ const tempConfig = {
7356
+ ...loadConfig(),
7357
+ figma: {
7358
+ baseUrl: "https://api.figma.com",
7359
+ token: figmaToken
7360
+ }
7361
+ };
7362
+ const tempHttp = createHttpClient(tempConfig);
7363
+ await tempHttp.figma("/v1/me");
7364
+ process.stderr.write(" Connected.\n");
7365
+ } catch (err) {
7366
+ warn(`Could not connect to Figma: ${err instanceof Error ? err.message : String(err)}`);
7367
+ warn("Config will be saved anyway. Check your token and re-run pncli config init or pncli config test.");
7368
+ }
7369
+ }
7370
+ }
7347
7371
  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");
7348
7372
  const jiraProject = await input({
7349
7373
  message: "Default Jira project key (optional):",
@@ -7367,7 +7391,10 @@ async function initGlobalConfig(start) {
7367
7391
  process.exitCode = ExitCode.SUCCESS;
7368
7392
  return;
7369
7393
  }
7394
+ const existingGlobal = loadJsonFile(getGlobalConfigPath());
7395
+ const existingInstances = Array.isArray(existingGlobal?.jenkinsInstances) ? existingGlobal.jenkinsInstances : [];
7370
7396
  writeGlobalConfig({
7397
+ jenkinsInstances: existingInstances,
7371
7398
  user: {
7372
7399
  email: userEmail || void 0,
7373
7400
  userId: userId || void 0
@@ -7428,14 +7455,6 @@ async function initGlobalConfig(start) {
7428
7455
  ...jenkinsApiToken ? { apiToken: jenkinsApiToken } : {}
7429
7456
  }
7430
7457
  } : {},
7431
- ...useUdeploy && udeployBaseUrl ? {
7432
- udeploy: {
7433
- baseUrl: normalizeBaseUrl(udeployBaseUrl),
7434
- ...udeployPat ? { pat: udeployPat } : {},
7435
- ...udeployUsername ? { username: udeployUsername } : {},
7436
- ...udeployPassword ? { password: udeployPassword } : {}
7437
- }
7438
- } : {},
7439
7458
  ...useCheckmarx && checkmarxBaseUrl ? {
7440
7459
  checkmarx: {
7441
7460
  baseUrl: normalizeBaseUrl(checkmarxBaseUrl),
@@ -7489,6 +7508,18 @@ async function initGlobalConfig(start) {
7489
7508
  token: logscaleToken || void 0
7490
7509
  }
7491
7510
  } : {},
7511
+ ...useSplitio && splitioBaseUrl ? {
7512
+ splitio: {
7513
+ baseUrl: normalizeBaseUrl(splitioBaseUrl),
7514
+ adminApiKey: splitioAdminApiKey || void 0
7515
+ }
7516
+ } : {},
7517
+ ...useFigma && figmaToken ? {
7518
+ figma: {
7519
+ baseUrl: "https://api.figma.com",
7520
+ token: figmaToken
7521
+ }
7522
+ } : {},
7492
7523
  defaults: {
7493
7524
  jira: {
7494
7525
  project: jiraProject || void 0
@@ -7508,12 +7539,6 @@ async function initGlobalConfig(start) {
7508
7539
  collection: adoCollection || void 0,
7509
7540
  project: adoProject || void 0
7510
7541
  }
7511
- } : {},
7512
- ...useUdeploy && (udeployDefaultApp || udeployDefaultEnv) ? {
7513
- udeploy: {
7514
- application: udeployDefaultApp || void 0,
7515
- environment: udeployDefaultEnv || void 0
7516
- }
7517
7542
  } : {}
7518
7543
  }
7519
7544
  });
@@ -8245,10 +8270,10 @@ function registerAdoWorkCommands(ado) {
8245
8270
  const types = await discoverTypes(workClient, collection, project);
8246
8271
  if (opts.save) {
8247
8272
  const globalConfigPath = getGlobalConfigPath(globalOpts.config);
8248
- const fs5 = await import("fs");
8249
- const existing = JSON.parse(fs5.readFileSync(globalConfigPath, "utf8") || "{}");
8273
+ const fs6 = await import("fs");
8274
+ const existing = JSON.parse(fs6.readFileSync(globalConfigPath, "utf8") || "{}");
8250
8275
  existing.ado = { ...existing.ado ?? {}, discoveredTypes: types };
8251
- fs5.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
8276
+ fs6.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
8252
8277
  warn(`Saved ${types.length} types to ${globalConfigPath}`);
8253
8278
  }
8254
8279
  success(types, "ado", "work-types", start);
@@ -8277,11 +8302,11 @@ function registerAdoWorkCommands(ado) {
8277
8302
  ) : allFields;
8278
8303
  if (opts.save) {
8279
8304
  const globalConfigPath = getGlobalConfigPath(globalOpts.config);
8280
- const fs5 = await import("fs");
8281
- const existing = JSON.parse(fs5.readFileSync(globalConfigPath, "utf8") || "{}");
8305
+ const fs6 = await import("fs");
8306
+ const existing = JSON.parse(fs6.readFileSync(globalConfigPath, "utf8") || "{}");
8282
8307
  const aliases = buildDefaultAliases(allFields);
8283
8308
  existing.ado = { ...existing.ado ?? {}, discoveredFields: allFields, fieldAliases: aliases };
8284
- fs5.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
8309
+ fs6.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
8285
8310
  warn(`Saved ${allFields.length} fields and ${Object.keys(aliases).length} aliases to ${globalConfigPath}`);
8286
8311
  }
8287
8312
  success(fields, "ado", "work-fields", start);
@@ -8578,11 +8603,11 @@ function registerAdoRepoCommands(ado) {
8578
8603
  const start = Date.now();
8579
8604
  try {
8580
8605
  const globalOpts = ado.optsWithGlobals();
8581
- const { loadConfig: lc } = await import("./config-SQ3YHJHI.js");
8606
+ const { loadConfig: lc } = await import("./config-YHYQTGKT.js");
8582
8607
  const config = lc({ configPath: globalOpts.config });
8583
8608
  const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
8584
8609
  const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
8585
- const { createHttpClient: createHttpClient2 } = await import("./http-WCDXXCKQ.js");
8610
+ const { createHttpClient: createHttpClient2 } = await import("./http-6QJU2SWP.js");
8586
8611
  const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
8587
8612
  const coreClient = new AdoCoreClient2(http);
8588
8613
  const me = await coreClient.getConnectionData(collection);
@@ -8840,11 +8865,22 @@ var JenkinsClient = class {
8840
8865
  };
8841
8866
 
8842
8867
  // src/services/jenkins/commands.ts
8843
- function getClient7(program2) {
8844
- const opts = program2.optsWithGlobals();
8868
+ function getClient7(cmd) {
8869
+ const opts = cmd.optsWithGlobals();
8845
8870
  const config = loadConfig({ configPath: opts.config });
8846
- if (!config.jenkins.baseUrl) throw new PncliError("Jenkins not configured. Run: pncli config init");
8847
- const http = createHttpClient(config, Boolean(opts.dryRun));
8871
+ let jenkins = config.jenkins;
8872
+ if (opts.instance) {
8873
+ const inst = config.jenkinsInstances.find((i) => i.name === opts.instance);
8874
+ if (!inst) throw new PncliError(`Jenkins instance "${opts.instance}" not found. Add it to your global config using pncli config set jenkinsInstances.`);
8875
+ jenkins = { baseUrl: inst.baseUrl, username: inst.username, apiToken: inst.apiToken };
8876
+ }
8877
+ if (!jenkins.baseUrl) {
8878
+ if (opts.instance) {
8879
+ throw new PncliError(`Jenkins instance "${opts.instance}" has no baseUrl configured. Add a baseUrl to that entry in jenkinsInstances.`);
8880
+ }
8881
+ throw new PncliError("Jenkins not configured. Run: pncli config init");
8882
+ }
8883
+ const http = createHttpClient({ ...config, jenkins }, Boolean(opts.dryRun));
8848
8884
  return new JenkinsClient(http);
8849
8885
  }
8850
8886
  async function pollQueueItem(client, queueItemId, pollMs, timeoutMs) {
@@ -8869,12 +8905,70 @@ async function pollBuildComplete(client, jobName, buildNumber, pollMs, timeoutMs
8869
8905
  throw new PncliError(`Timed out waiting for build #${buildNumber} to complete after ${timeoutMs / 1e3}s`, 1);
8870
8906
  }
8871
8907
  function registerJenkinsCommands(program2) {
8872
- const jenkins = program2.command("jenkins").description("Jenkins Data Center operations");
8908
+ const jenkins = program2.command("jenkins").description("Jenkins operations").option("--instance <name>", "Named Jenkins instance from global config (overrides default jenkins config)");
8873
8909
  const pipeline = jenkins.command("pipeline").description("Pipeline (job/build) operations");
8910
+ const instance = jenkins.command("instance").description("Manage named Jenkins instances in the global config");
8911
+ instance.command("list").description("List configured Jenkins instances (API tokens masked)").action(() => {
8912
+ const start = Date.now();
8913
+ try {
8914
+ const opts = jenkins.optsWithGlobals();
8915
+ const config = loadConfig({ configPath: opts.config });
8916
+ const instances = config.jenkinsInstances.map((i) => ({
8917
+ ...i,
8918
+ apiToken: i.apiToken ? "***" : void 0
8919
+ }));
8920
+ success({ instances }, "jenkins", "instance-list", start);
8921
+ } catch (err) {
8922
+ fail(err, "jenkins", "instance-list", start);
8923
+ }
8924
+ });
8925
+ instance.command("add").description("Add a named Jenkins instance to the global config (appends \u2014 existing instances are preserved)").requiredOption("--name <name>", "Instance name used with --instance").requiredOption("--base-url <url>", "Jenkins base URL (e.g. jenkins.imagile.dev)").option("--username <user>", "Jenkins username").option("--api-token <token>", "Jenkins API token (prompted for when omitted on an interactive terminal, keeping it out of shell history)").option("--force", "Overwrite an instance that already uses this name").action(async (o) => {
8926
+ const start = Date.now();
8927
+ try {
8928
+ const opts = jenkins.optsWithGlobals();
8929
+ const configPath = opts.config;
8930
+ const existing = loadConfig({ configPath }).jenkinsInstances;
8931
+ const idx = existing.findIndex((i) => i.name === o.name);
8932
+ if (idx >= 0 && !o.force) {
8933
+ throw new PncliError(`Jenkins instance "${o.name}" already exists. Pass --force to overwrite it, or run: pncli jenkins instance remove --name ${o.name}`, 1);
8934
+ }
8935
+ let apiToken = o.apiToken;
8936
+ if (!apiToken && process.stdin.isTTY) {
8937
+ const { default: password2 } = await import("@inquirer/password");
8938
+ apiToken = await password2({ message: `API token for "${o.name}" (leave blank to skip):` });
8939
+ }
8940
+ const entry = {
8941
+ name: o.name,
8942
+ baseUrl: normalizeBaseUrl(o.baseUrl),
8943
+ ...o.username ? { username: o.username } : {},
8944
+ ...apiToken ? { apiToken } : {}
8945
+ };
8946
+ const next = idx >= 0 ? existing.map((i, n) => n === idx ? entry : i) : [...existing, entry];
8947
+ setConfigValue("jenkinsInstances", JSON.stringify(next), configPath);
8948
+ success({ name: entry.name, baseUrl: entry.baseUrl, overwritten: idx >= 0 }, "jenkins", "instance-add", start);
8949
+ } catch (err) {
8950
+ fail(err, "jenkins", "instance-add", start);
8951
+ }
8952
+ });
8953
+ instance.command("remove").description("Remove a named Jenkins instance from the global config").requiredOption("--name <name>", "Instance name to remove").action((o) => {
8954
+ const start = Date.now();
8955
+ try {
8956
+ const opts = jenkins.optsWithGlobals();
8957
+ const configPath = opts.config;
8958
+ const existing = loadConfig({ configPath }).jenkinsInstances;
8959
+ if (!existing.some((i) => i.name === o.name)) {
8960
+ throw new PncliError(`Jenkins instance "${o.name}" not found. Run: pncli jenkins instance list`, 1);
8961
+ }
8962
+ setConfigValue("jenkinsInstances", JSON.stringify(existing.filter((i) => i.name !== o.name)), configPath);
8963
+ success({ removed: o.name }, "jenkins", "instance-remove", start);
8964
+ } catch (err) {
8965
+ fail(err, "jenkins", "instance-remove", start);
8966
+ }
8967
+ });
8874
8968
  pipeline.command("list").description("List all jobs on the Jenkins instance (or within a folder)").option("--folder <name>", "Folder name to enumerate (supports nested folders: parentFolder/childFolder)").action(async (opts) => {
8875
8969
  const start = Date.now();
8876
8970
  try {
8877
- const client = getClient7(program2);
8971
+ const client = getClient7(jenkins);
8878
8972
  const data = await client.listJobs(opts.folder);
8879
8973
  success(data, "jenkins", "pipeline-list", start);
8880
8974
  } catch (err) {
@@ -8884,7 +8978,7 @@ function registerJenkinsCommands(program2) {
8884
8978
  pipeline.command("get").description("Get details for a specific job").requiredOption("--name <job>", "Job name (use folder/job for folder-scoped jobs)").action(async (opts) => {
8885
8979
  const start = Date.now();
8886
8980
  try {
8887
- const client = getClient7(program2);
8981
+ const client = getClient7(jenkins);
8888
8982
  const data = await client.getJob(opts.name);
8889
8983
  success(data, "jenkins", "pipeline-get", start);
8890
8984
  } catch (err) {
@@ -8897,7 +8991,7 @@ function registerJenkinsCommands(program2) {
8897
8991
  }, []).option("--wait", "Wait for the build to complete before returning").option("--timeout <s>", "Max wait time in seconds per phase (queue-wait and build-wait each get this budget; default 600)", "600").option("--poll <s>", "Poll interval in seconds (default 10)", "10").action(async (opts) => {
8898
8992
  const start = Date.now();
8899
8993
  try {
8900
- const client = getClient7(program2);
8994
+ const client = getClient7(jenkins);
8901
8995
  for (const p of opts.parameter) {
8902
8996
  if (!p.includes("=")) throw new PncliError(`Invalid --parameter "${p}". Expected format: key=value`, 1);
8903
8997
  }
@@ -8930,7 +9024,7 @@ function registerJenkinsCommands(program2) {
8930
9024
  pipeline.command("list-runs").description("List recent builds for a job").requiredOption("--name <job>", "Job name (use folder/job for folder-scoped jobs)").option("--top <n>", "Maximum number of builds to return (default 25)", "25").action(async (opts) => {
8931
9025
  const start = Date.now();
8932
9026
  try {
8933
- const client = getClient7(program2);
9027
+ const client = getClient7(jenkins);
8934
9028
  const top = parseInt(opts.top, 10);
8935
9029
  if (isNaN(top) || top <= 0) throw new PncliError(`Invalid --top "${opts.top}". Expected a positive integer.`, 1);
8936
9030
  const data = await client.listBuilds(opts.name, top);
@@ -8942,7 +9036,7 @@ function registerJenkinsCommands(program2) {
8942
9036
  pipeline.command("get-run").description("Get details for a specific build").requiredOption("--name <job>", "Job name (use folder/job for folder-scoped jobs)").requiredOption("--number <n>", "Build number").action(async (opts) => {
8943
9037
  const start = Date.now();
8944
9038
  try {
8945
- const client = getClient7(program2);
9039
+ const client = getClient7(jenkins);
8946
9040
  const buildNumber = parseInt(opts.number, 10);
8947
9041
  if (isNaN(buildNumber)) throw new PncliError(`Invalid --number "${opts.number}". Expected an integer.`, 1);
8948
9042
  const data = await client.getBuild(opts.name, buildNumber);
@@ -8954,7 +9048,7 @@ function registerJenkinsCommands(program2) {
8954
9048
  pipeline.command("logs").description("Fetch console log for a build (consider --output-file for large logs)").requiredOption("--name <job>", "Job name (use folder/job for folder-scoped jobs)").requiredOption("--number <n>", "Build number").action(async (opts) => {
8955
9049
  const start = Date.now();
8956
9050
  try {
8957
- const client = getClient7(program2);
9051
+ const client = getClient7(jenkins);
8958
9052
  const buildNumber = parseInt(opts.number, 10);
8959
9053
  if (isNaN(buildNumber)) throw new PncliError(`Invalid --number "${opts.number}". Expected an integer.`, 1);
8960
9054
  const text = await client.getConsoleLog(opts.name, buildNumber);
@@ -9031,7 +9125,7 @@ var ArtifactoryClient = class {
9031
9125
  }));
9032
9126
  const result = { builds, uri: raw.uri };
9033
9127
  if (builds.length === 0) {
9034
- result.hint = 'No builds are registered in Artifactory Build Integration. Build names may differ from ADO pipeline or uDeploy component names. Use "artifactory repos" to list repositories, then "artifactory search --repo <repo>" to locate artifacts directly.';
9128
+ result.hint = 'No builds are registered in Artifactory Build Integration. Build names may differ from ADO pipeline names. Use "artifactory repos" to list repositories, then "artifactory search --repo <repo>" to locate artifacts directly.';
9035
9129
  }
9036
9130
  return result;
9037
9131
  }
@@ -9134,7 +9228,7 @@ function registerArtifactoryCommands(program2) {
9134
9228
  fail(err, "artifactory", "search", start);
9135
9229
  }
9136
9230
  });
9137
- art.command("builds").description('List builds registered in Artifactory Build Integration (names may differ from ADO pipeline or uDeploy component names; use "search --repo" when empty)').option("--timeout <s>", "Request timeout in seconds (default 60; increase for large Artifactory instances)", "60").action(async (opts) => {
9231
+ art.command("builds").description('List builds registered in Artifactory Build Integration (names may differ from ADO pipeline names; use "search --repo" when empty)').option("--timeout <s>", "Request timeout in seconds (default 60; increase for large Artifactory instances)", "60").action(async (opts) => {
9138
9232
  const start = Date.now();
9139
9233
  try {
9140
9234
  const client = getClient8(program2);
@@ -9169,226 +9263,6 @@ function registerArtifactoryCommands(program2) {
9169
9263
  });
9170
9264
  }
9171
9265
 
9172
- // src/services/udeploy/client.ts
9173
- var UdeployClient = class {
9174
- constructor(http) {
9175
- this.http = http;
9176
- }
9177
- http;
9178
- listApplications() {
9179
- return this.http.udeploy("/cli/application");
9180
- }
9181
- listEnvironments(application) {
9182
- return this.http.udeploy("/cli/application/environmentsInApplication", {
9183
- params: { application }
9184
- });
9185
- }
9186
- listComponents() {
9187
- return this.http.udeploy("/cli/component");
9188
- }
9189
- listVersions(component) {
9190
- return this.http.udeploy("/cli/component/versions", {
9191
- params: { component }
9192
- });
9193
- }
9194
- createVersion(component, name) {
9195
- return this.http.udeploy("/cli/version/createVersion", {
9196
- method: "POST",
9197
- params: { component, name }
9198
- });
9199
- }
9200
- finishImporting(component, version) {
9201
- return this.http.udeploy("/cli/version/finishedImporting", {
9202
- method: "POST",
9203
- params: { component, version }
9204
- });
9205
- }
9206
- runProcess(opts) {
9207
- return this.http.udeploy("/cli/applicationProcessRequest/request", {
9208
- method: "PUT",
9209
- body: {
9210
- application: opts.application,
9211
- applicationProcess: opts.applicationProcess,
9212
- environment: opts.environment,
9213
- onlyChanged: opts.onlyChanged ?? false,
9214
- ...opts.versions ? { versions: opts.versions } : {},
9215
- ...opts.snapshot ? { snapshot: opts.snapshot } : {},
9216
- ...opts.properties ? { properties: opts.properties } : {}
9217
- }
9218
- });
9219
- }
9220
- getRequestStatus(requestId) {
9221
- return this.http.udeploy("/cli/applicationProcessRequest/requestStatus", {
9222
- params: { requestId }
9223
- });
9224
- }
9225
- getRequestInfo(requestId) {
9226
- return this.http.udeploy("/cli/applicationProcessRequest/requestInfo", {
9227
- params: { requestId }
9228
- });
9229
- }
9230
- };
9231
-
9232
- // src/services/udeploy/commands.ts
9233
- var POLL_INTERVAL_MS2 = 5e3;
9234
- var DEFAULT_TIMEOUT_MS = 10 * 60 * 1e3;
9235
- function getClient9(program2) {
9236
- const opts = program2.optsWithGlobals();
9237
- const config = loadConfig({ configPath: opts.config });
9238
- if (!config.udeploy.baseUrl) {
9239
- throw new PncliError("UDeploy baseUrl not configured. Run: pncli config init", 78);
9240
- }
9241
- const http = createHttpClient(config, opts.dryRun);
9242
- return { client: new UdeployClient(http), config };
9243
- }
9244
- function resolveApplication(config, cliValue) {
9245
- const app = cliValue ?? config.defaults.udeploy.application;
9246
- if (!app) throw new PncliError("--application is required (or set defaults.udeploy.application in config)", 2);
9247
- return app;
9248
- }
9249
- function resolveEnvironment(config, cliValue) {
9250
- const env = cliValue ?? config.defaults.udeploy.environment;
9251
- if (!env) throw new PncliError("--environment is required (or set defaults.udeploy.environment in config)", 2);
9252
- return env;
9253
- }
9254
- function registerUdeployCommands(program2) {
9255
- const udeploy = program2.command("udeploy").description("IBM UrbanCode Deploy \u2014 component versions and deployment processes").option("--application <name>", "Application name or ID").option("--environment <name>", "Environment name or ID");
9256
- udeploy.command("apps").description("List all applications").action(async () => {
9257
- const start = Date.now();
9258
- try {
9259
- const { client } = getClient9(program2);
9260
- const apps = await client.listApplications();
9261
- success(apps, "udeploy", "apps", start);
9262
- } catch (err) {
9263
- fail(err, "udeploy", "apps", start);
9264
- }
9265
- });
9266
- udeploy.command("environments").description("List environments for an application").action(async () => {
9267
- const start = Date.now();
9268
- try {
9269
- const { client, config } = getClient9(program2);
9270
- const parentOpts = udeploy.opts();
9271
- const application = resolveApplication(config, parentOpts.application);
9272
- const envs = await client.listEnvironments(application);
9273
- success(envs, "udeploy", "environments", start);
9274
- } catch (err) {
9275
- fail(err, "udeploy", "environments", start);
9276
- }
9277
- });
9278
- udeploy.command("components").description("List all components").action(async () => {
9279
- const start = Date.now();
9280
- try {
9281
- const { client } = getClient9(program2);
9282
- const components = await client.listComponents();
9283
- success(components, "udeploy", "components", start);
9284
- } catch (err) {
9285
- fail(err, "udeploy", "components", start);
9286
- }
9287
- });
9288
- udeploy.command("versions").description("List versions for a component").requiredOption("--component <name>", "Component name or ID").action(async (cmdOpts) => {
9289
- const start = Date.now();
9290
- try {
9291
- const { client } = getClient9(program2);
9292
- const versions = await client.listVersions(cmdOpts.component);
9293
- success(versions, "udeploy", "versions", start);
9294
- } catch (err) {
9295
- fail(err, "udeploy", "versions", start);
9296
- }
9297
- });
9298
- udeploy.command("import-version").description("Create a component version and mark it ready for deployment").requiredOption("--component <name>", "Component name or ID").requiredOption("--name <name>", "Version name").option("--no-finish", "Skip marking the version as finished importing").action(async (cmdOpts) => {
9299
- const start = Date.now();
9300
- try {
9301
- const { client } = getClient9(program2);
9302
- const created = await client.createVersion(cmdOpts.component, cmdOpts.name);
9303
- if (cmdOpts.finish) {
9304
- await client.finishImporting(cmdOpts.component, cmdOpts.name);
9305
- }
9306
- success({ ...created, finishedImporting: cmdOpts.finish }, "udeploy", "import-version", start);
9307
- } catch (err) {
9308
- fail(err, "udeploy", "import-version", start);
9309
- }
9310
- });
9311
- udeploy.command("run").description("Run an application deployment process").requiredOption("--process <name>", "Application process name or ID").option("--component <name>", "Component name (repeatable)", collect, []).option("--component-version <name>", "Component version (repeatable; positionally paired with --component)", collect, []).option("--snapshot <name>", "Snapshot name or ID (alternative to specifying versions)").option("--only-changed", "Deploy only changed components", false).option("--wait", "Poll until the process completes", false).option("--timeout <ms>", "Max wait time in milliseconds", String(DEFAULT_TIMEOUT_MS)).action(async (cmdOpts) => {
9312
- const start = Date.now();
9313
- try {
9314
- const { client, config } = getClient9(program2);
9315
- const parentOpts = udeploy.opts();
9316
- const application = resolveApplication(config, parentOpts.application);
9317
- const environment = resolveEnvironment(config, parentOpts.environment);
9318
- const components = cmdOpts.component;
9319
- const versions = cmdOpts.componentVersion;
9320
- const versionEntries = components.map((c, i) => ({
9321
- component: c,
9322
- version: versions[i] ?? "latest"
9323
- }));
9324
- const result = await client.runProcess({
9325
- application,
9326
- applicationProcess: cmdOpts.process,
9327
- environment,
9328
- onlyChanged: cmdOpts.onlyChanged,
9329
- ...versionEntries.length > 0 ? { versions: versionEntries } : {},
9330
- ...cmdOpts.snapshot ? { snapshot: cmdOpts.snapshot } : {}
9331
- });
9332
- if (!cmdOpts.wait) {
9333
- success(result, "udeploy", "run", start);
9334
- return;
9335
- }
9336
- const timeoutMs = parseInt(cmdOpts.timeout, 10);
9337
- const requestId = result.requestId;
9338
- log(`Process started. Request ID: ${requestId}. Polling for completion...`);
9339
- while (true) {
9340
- const elapsed = Date.now() - start;
9341
- if (elapsed >= timeoutMs) {
9342
- throw new PncliError(`Timed out after ${timeoutMs}ms waiting for request ${requestId}`, 1);
9343
- }
9344
- await sleep(POLL_INTERVAL_MS2);
9345
- const status = await client.getRequestStatus(requestId);
9346
- if (status.status === "CLOSED") {
9347
- const outcome = { requestId, status: status.status, result: status.result, elapsed_ms: Date.now() - start };
9348
- if (status.result === "SUCCEEDED") {
9349
- success(outcome, "udeploy", "run", start);
9350
- } else {
9351
- throw new PncliError(
9352
- `Deployment ${status.result ?? "failed"} (requestId: ${requestId})`,
9353
- 1
9354
- );
9355
- }
9356
- return;
9357
- }
9358
- log(`Status: ${status.status} (${Math.round((Date.now() - start) / 1e3)}s elapsed)`);
9359
- }
9360
- } catch (err) {
9361
- fail(err, "udeploy", "run", start);
9362
- }
9363
- });
9364
- udeploy.command("request-status").description("Get the current status of a deployment process request").requiredOption("--request-id <id>", "Request ID returned by udeploy run").action(async (cmdOpts) => {
9365
- const start = Date.now();
9366
- try {
9367
- const { client } = getClient9(program2);
9368
- const status = await client.getRequestStatus(cmdOpts.requestId);
9369
- success(status, "udeploy", "request-status", start);
9370
- } catch (err) {
9371
- fail(err, "udeploy", "request-status", start);
9372
- }
9373
- });
9374
- udeploy.command("request-info").description("Get full details of a deployment process request").requiredOption("--request-id <id>", "Request ID returned by udeploy run").action(async (cmdOpts) => {
9375
- const start = Date.now();
9376
- try {
9377
- const { client } = getClient9(program2);
9378
- const info = await client.getRequestInfo(cmdOpts.requestId);
9379
- success(info, "udeploy", "request-info", start);
9380
- } catch (err) {
9381
- fail(err, "udeploy", "request-info", start);
9382
- }
9383
- });
9384
- }
9385
- function collect(val, prev) {
9386
- return [...prev, val];
9387
- }
9388
- function sleep(ms) {
9389
- return new Promise((resolve) => setTimeout(resolve, ms));
9390
- }
9391
-
9392
9266
  // src/services/checkmarx/client.ts
9393
9267
  var CheckmarxClient = class {
9394
9268
  constructor(http) {
@@ -9429,7 +9303,7 @@ var CheckmarxClient = class {
9429
9303
  };
9430
9304
 
9431
9305
  // src/services/checkmarx/commands.ts
9432
- function getClient10(program2) {
9306
+ function getClient9(program2) {
9433
9307
  const opts = program2.optsWithGlobals();
9434
9308
  const config = loadConfig({ configPath: opts.config });
9435
9309
  if (!config.checkmarx.baseUrl) throw new PncliError("Checkmarx not configured. Run: pncli config init");
@@ -9443,7 +9317,7 @@ function registerCheckmarxCommands(program2) {
9443
9317
  project.command("list").description("List all projects").action(async () => {
9444
9318
  const start = Date.now();
9445
9319
  try {
9446
- const data = await getClient10(program2).listProjects();
9320
+ const data = await getClient9(program2).listProjects();
9447
9321
  success(data, "checkmarx", "project-list", start);
9448
9322
  } catch (err) {
9449
9323
  fail(err, "checkmarx", "project-list", start);
@@ -9452,7 +9326,7 @@ function registerCheckmarxCommands(program2) {
9452
9326
  project.command("get").description("Get a project by ID").requiredOption("--id <id>", "Project UUID").action(async (opts) => {
9453
9327
  const start = Date.now();
9454
9328
  try {
9455
- const data = await getClient10(program2).getProject(opts.id);
9329
+ const data = await getClient9(program2).getProject(opts.id);
9456
9330
  success(data, "checkmarx", "project-get", start);
9457
9331
  } catch (err) {
9458
9332
  fail(err, "checkmarx", "project-get", start);
@@ -9463,7 +9337,7 @@ function registerCheckmarxCommands(program2) {
9463
9337
  try {
9464
9338
  const last = opts.last ? parseInt(opts.last, 10) : void 0;
9465
9339
  if (opts.last && isNaN(last)) throw new PncliError(`Invalid --last "${opts.last}". Expected an integer.`, 1);
9466
- const data = await getClient10(program2).listScans({ projectId: opts.project, last });
9340
+ const data = await getClient9(program2).listScans({ projectId: opts.project, last });
9467
9341
  success(data, "checkmarx", "scan-list", start);
9468
9342
  } catch (err) {
9469
9343
  fail(err, "checkmarx", "scan-list", start);
@@ -9472,7 +9346,7 @@ function registerCheckmarxCommands(program2) {
9472
9346
  scan.command("get").description("Get a scan by ID").requiredOption("--id <id>", "Scan UUID").action(async (opts) => {
9473
9347
  const start = Date.now();
9474
9348
  try {
9475
- const data = await getClient10(program2).getScan(opts.id);
9349
+ const data = await getClient9(program2).getScan(opts.id);
9476
9350
  success(data, "checkmarx", "scan-get", start);
9477
9351
  } catch (err) {
9478
9352
  fail(err, "checkmarx", "scan-get", start);
@@ -9481,7 +9355,7 @@ function registerCheckmarxCommands(program2) {
9481
9355
  scan.command("stats").description("Get results statistics for a scan").requiredOption("--id <id>", "Scan UUID").action(async (opts) => {
9482
9356
  const start = Date.now();
9483
9357
  try {
9484
- const data = await getClient10(program2).getScanResultsStatistics(opts.id);
9358
+ const data = await getClient9(program2).getScanResultsStatistics(opts.id);
9485
9359
  success(data, "checkmarx", "scan-stats", start);
9486
9360
  } catch (err) {
9487
9361
  fail(err, "checkmarx", "scan-stats", start);
@@ -10934,6 +10808,29 @@ function podAge(creationTimestamp) {
10934
10808
  if (ageHours < 24) return `${ageHours}h`;
10935
10809
  return `${Math.floor(ageHours / 24)}d`;
10936
10810
  }
10811
+ function parseCpuMillicores(cpu) {
10812
+ if (!cpu) return null;
10813
+ if (cpu.endsWith("n")) return Math.round(Number(cpu.slice(0, -1)) / 1e6);
10814
+ if (cpu.endsWith("u")) return Math.round(Number(cpu.slice(0, -1)) / 1e3);
10815
+ if (cpu.endsWith("m")) return Number(cpu.slice(0, -1));
10816
+ const whole = Number(cpu);
10817
+ if (!Number.isNaN(whole)) return Math.round(whole * 1e3);
10818
+ return null;
10819
+ }
10820
+ function parseMemMiB(mem) {
10821
+ if (!mem) return null;
10822
+ const round2 = (n) => Math.round(n * 100) / 100;
10823
+ if (mem.endsWith("Ki")) return round2(Number(mem.slice(0, -2)) / 1024);
10824
+ if (mem.endsWith("Mi")) return Number(mem.slice(0, -2));
10825
+ if (mem.endsWith("Gi")) return Math.round(Number(mem.slice(0, -2)) * 1024);
10826
+ if (mem.endsWith("Ti")) return Math.round(Number(mem.slice(0, -2)) * 1024 * 1024);
10827
+ if (mem.endsWith("K")) return round2(Number(mem.slice(0, -1)) * 1e3 / (1024 * 1024));
10828
+ if (mem.endsWith("M")) return round2(Number(mem.slice(0, -1)) * 1e3 * 1e3 / (1024 * 1024));
10829
+ if (mem.endsWith("G")) return Math.round(Number(mem.slice(0, -1)) * 1e3 * 1e3 * 1e3 / (1024 * 1024));
10830
+ const bytes = Number(mem);
10831
+ if (!Number.isNaN(bytes)) return round2(bytes / (1024 * 1024));
10832
+ return null;
10833
+ }
10937
10834
  function registerOpenShiftCommands(program2) {
10938
10835
  const oc = program2.command("openshift").description("OpenShift / Kubernetes operations");
10939
10836
  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) => {
@@ -11131,6 +11028,71 @@ function registerOpenShiftCommands(program2) {
11131
11028
  fail(err, "openshift", "pod-metrics", start);
11132
11029
  }
11133
11030
  });
11031
+ 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) => {
11032
+ const start = Date.now();
11033
+ try {
11034
+ const http = getHttp2(program2);
11035
+ const nsEncoded = encodeURIComponent(opts.namespace);
11036
+ const podParams = {};
11037
+ if (opts.labelSelector) podParams["labelSelector"] = opts.labelSelector;
11038
+ const [podList, metricsList] = await Promise.all([
11039
+ http.openshift(`/api/v1/namespaces/${nsEncoded}/pods`, { params: podParams }),
11040
+ http.openshift(`/apis/metrics.k8s.io/v1beta1/namespaces/${nsEncoded}/pods`, { params: podParams })
11041
+ ]);
11042
+ const metricsIndex = /* @__PURE__ */ new Map();
11043
+ for (const pm of metricsList.items) {
11044
+ const containerMap = /* @__PURE__ */ new Map();
11045
+ for (const c of pm.containers) {
11046
+ containerMap.set(c.name, c.usage);
11047
+ }
11048
+ metricsIndex.set(pm.metadata.name, containerMap);
11049
+ }
11050
+ const rows = [];
11051
+ for (const pod of podList.items) {
11052
+ const podName = pod.metadata.name;
11053
+ const podMetrics = metricsIndex.get(podName);
11054
+ const containers = pod.spec.containers ?? [];
11055
+ for (const container of containers) {
11056
+ const usage = podMetrics?.get(container.name);
11057
+ rows.push({
11058
+ pod: podName,
11059
+ container: container.name,
11060
+ cpuUsageMillicores: parseCpuMillicores(usage?.cpu),
11061
+ memUsageMiB: parseMemMiB(usage?.memory),
11062
+ cpuLimitsMillicores: parseCpuMillicores(container.resources?.limits?.cpu),
11063
+ memLimitsMiB: parseMemMiB(container.resources?.limits?.memory),
11064
+ cpuRequestsMillicores: parseCpuMillicores(container.resources?.requests?.cpu),
11065
+ memRequestsMiB: parseMemMiB(container.resources?.requests?.memory)
11066
+ });
11067
+ }
11068
+ }
11069
+ if (opts.csv) {
11070
+ const header = "Pod,Container,CPU Usage (m),Memory Usage (Mi),CPU Limits (m),Memory Limits (Mi),CPU Requests (m),Memory Requests (Mi)";
11071
+ const lines = rows.map(
11072
+ (r) => [
11073
+ r.pod,
11074
+ r.container,
11075
+ r.cpuUsageMillicores ?? "",
11076
+ r.memUsageMiB ?? "",
11077
+ r.cpuLimitsMillicores ?? "",
11078
+ r.memLimitsMiB ?? "",
11079
+ r.cpuRequestsMillicores ?? "",
11080
+ r.memRequestsMiB ?? ""
11081
+ ].join(",")
11082
+ );
11083
+ writeRawOutput([header, ...lines].join("\n") + "\n");
11084
+ } else {
11085
+ success(
11086
+ { namespace: opts.namespace, count: rows.length, units: { cpu: "m", memory: "Mi" }, rows },
11087
+ "openshift",
11088
+ "resource-usage",
11089
+ start
11090
+ );
11091
+ }
11092
+ } catch (err) {
11093
+ fail(err, "openshift", "resource-usage", start);
11094
+ }
11095
+ });
11134
11096
  }
11135
11097
 
11136
11098
  // src/services/dynatrace/commands.ts
@@ -11356,6 +11318,500 @@ function registerLogscaleCommands(program2) {
11356
11318
  });
11357
11319
  }
11358
11320
 
11321
+ // src/services/splitio/commands.ts
11322
+ import fs5 from "fs";
11323
+ import confirm2 from "@inquirer/confirm";
11324
+ function getHttp5(program2) {
11325
+ const opts = program2.optsWithGlobals();
11326
+ return createHttpClient(
11327
+ loadConfig({ configPath: opts.config }),
11328
+ Boolean(opts.dryRun)
11329
+ );
11330
+ }
11331
+ function loadInputFile(filePath) {
11332
+ let raw;
11333
+ if (filePath === "-") {
11334
+ raw = fs5.readFileSync(process.stdin.fd, "utf8");
11335
+ } else {
11336
+ raw = fs5.readFileSync(filePath, "utf8");
11337
+ }
11338
+ try {
11339
+ return JSON.parse(raw);
11340
+ } catch {
11341
+ throw new PncliError(`Could not parse JSON from ${filePath === "-" ? "stdin" : filePath}`);
11342
+ }
11343
+ }
11344
+ async function confirmWrite(opts, opLabel) {
11345
+ if (opts.yes) return;
11346
+ const proceed = await confirm2({
11347
+ message: `Submit Change Request "${opts.mnemonic}" (${opLabel})? This creates an approval request in Split.`,
11348
+ default: false
11349
+ });
11350
+ if (!proceed) throw new PncliError("Aborted by user.", 0);
11351
+ }
11352
+ function buildCrBase(opts) {
11353
+ const base = {
11354
+ title: opts.mnemonic,
11355
+ comment: opts.description,
11356
+ crNumber: opts.changeNumber
11357
+ };
11358
+ if (opts.approvers) {
11359
+ base["approvers"] = opts.approvers.split(",").map((s) => s.trim()).filter(Boolean).map((id) => ({ id }));
11360
+ }
11361
+ return base;
11362
+ }
11363
+ function registerSplitioCommands(program2) {
11364
+ const splitio = program2.command("splitio").description("Split.IO feature flag administration");
11365
+ const workspaces = splitio.command("workspaces").description("Workspace operations");
11366
+ workspaces.command("list").description("List all Split.IO workspaces").action(async () => {
11367
+ const start = Date.now();
11368
+ try {
11369
+ const http = getHttp5(program2);
11370
+ const data = await http.splitio("/internal/api/v2/workspaces");
11371
+ success(
11372
+ { count: data.objects.length, workspaces: data.objects },
11373
+ "splitio",
11374
+ "workspaces list",
11375
+ start
11376
+ );
11377
+ } catch (err) {
11378
+ fail(err, "splitio", "workspaces list", start);
11379
+ }
11380
+ });
11381
+ const environments = splitio.command("environments").description("Environment operations");
11382
+ environments.command("list").description("List environments in a workspace").requiredOption("--workspace <id>", "Workspace ID").action(async (opts) => {
11383
+ const start = Date.now();
11384
+ try {
11385
+ const http = getHttp5(program2);
11386
+ const data = await http.splitio(
11387
+ `/internal/api/v2/workspaces/${encodeURIComponent(opts.workspace)}/environments`
11388
+ );
11389
+ success(
11390
+ { count: data.objects.length, environments: data.objects },
11391
+ "splitio",
11392
+ "environments list",
11393
+ start
11394
+ );
11395
+ } catch (err) {
11396
+ fail(err, "splitio", "environments list", start);
11397
+ }
11398
+ });
11399
+ const flags = splitio.command("flags").description("Feature flag operations");
11400
+ 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) => {
11401
+ const start = Date.now();
11402
+ try {
11403
+ const http = getHttp5(program2);
11404
+ const params = {
11405
+ wsId: opts.workspace,
11406
+ limit: parseInt(opts.limit, 10),
11407
+ offset: parseInt(opts.offset, 10)
11408
+ };
11409
+ if (opts.flagSet) params["flagSetName"] = opts.flagSet;
11410
+ const data = await http.splitio("/internal/api/v2/splits", { params });
11411
+ success(
11412
+ { count: data.objects.length, totalCount: data.totalCount, flags: data.objects },
11413
+ "splitio",
11414
+ "flags list",
11415
+ start
11416
+ );
11417
+ } catch (err) {
11418
+ fail(err, "splitio", "flags list", start);
11419
+ }
11420
+ });
11421
+ 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) => {
11422
+ const start = Date.now();
11423
+ try {
11424
+ const http = getHttp5(program2);
11425
+ if (opts.environment) {
11426
+ const data = await http.splitio(
11427
+ `/internal/api/v2/splits/${encodeURIComponent(opts.flag)}/environments/${encodeURIComponent(opts.environment)}`,
11428
+ { params: { wsId: opts.workspace } }
11429
+ );
11430
+ success(data, "splitio", "flags get", start);
11431
+ } else {
11432
+ const data = await http.splitio(
11433
+ `/internal/api/v2/splits/${encodeURIComponent(opts.flag)}`,
11434
+ { params: { wsId: opts.workspace } }
11435
+ );
11436
+ success(data, "splitio", "flags get", start);
11437
+ }
11438
+ } catch (err) {
11439
+ fail(err, "splitio", "flags get", start);
11440
+ }
11441
+ });
11442
+ flags.command("update").description("Submit a Change Request to replace a flag's full definition in an environment").requiredOption("--workspace <id>", "Workspace ID").requiredOption("--flag <name>", "Feature flag name").requiredOption("--environment <id>", "Environment ID").requiredOption("--mnemonic <text>", "Short Change Request title (mnemonic)").requiredOption("--description <text>", "Change Request description").requiredOption("--change-number <CHG>", "ServiceNow change request number (e.g. CHG0001234)").option("--approvers <ids>", "Comma-separated Split user IDs required to approve this CR").option("--input-file <path>", "JSON file with the complete flag definition body (use - for stdin)").option("--yes", "Skip interactive confirmation prompt").action(async (opts) => {
11443
+ const start = Date.now();
11444
+ try {
11445
+ if (!opts.inputFile) throw new PncliError("--input-file is required for flags update");
11446
+ const definition = loadInputFile(opts.inputFile);
11447
+ await confirmWrite(opts, `UPDATE ${opts.flag} in ${opts.environment}`);
11448
+ const http = getHttp5(program2);
11449
+ const body = {
11450
+ ...buildCrBase(opts),
11451
+ operationType: "UPDATE_SPLIT_DEFINITION",
11452
+ split: {
11453
+ name: opts.flag,
11454
+ environment: { id: opts.environment },
11455
+ definition
11456
+ },
11457
+ workspace: { id: opts.workspace }
11458
+ };
11459
+ const data = await http.splitio("/internal/api/v2/changeRequests", {
11460
+ method: "POST",
11461
+ body
11462
+ });
11463
+ success(data, "splitio", "flags update", start);
11464
+ } catch (err) {
11465
+ fail(err, "splitio", "flags update", start);
11466
+ }
11467
+ });
11468
+ flags.command("set-rule").description("Change one targeting rule without altering the rest of the flag definition").requiredOption("--workspace <id>", "Workspace ID").requiredOption("--flag <name>", "Feature flag name").requiredOption("--environment <id>", "Environment ID").requiredOption("--rule-index <n>", "Zero-based index of the targeting rule to update").option("--treatment <name>", "Treatment to assign to the entire rule bucket").option("--weights <json>", "JSON array of {treatment, size} objects for weighted distribution").requiredOption("--mnemonic <text>", "Short Change Request title (mnemonic)").requiredOption("--description <text>", "Change Request description").requiredOption("--change-number <CHG>", "ServiceNow 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) => {
11469
+ const start = Date.now();
11470
+ try {
11471
+ if (!opts.treatment && !opts.weights) {
11472
+ throw new PncliError("Either --treatment or --weights must be specified");
11473
+ }
11474
+ const http = getHttp5(program2);
11475
+ const current = await http.splitio(
11476
+ `/internal/api/v2/splits/${encodeURIComponent(opts.flag)}/environments/${encodeURIComponent(opts.environment)}`,
11477
+ { params: { wsId: opts.workspace } }
11478
+ );
11479
+ const rules = current.rules ?? [];
11480
+ const idx = parseInt(opts.ruleIndex, 10);
11481
+ if (Number.isNaN(idx) || idx < 0 || idx >= rules.length) {
11482
+ throw new PncliError(`Rule index ${Number.isNaN(idx) ? JSON.stringify(opts.ruleIndex) : idx} is out of range (flag has ${rules.length} rules)`);
11483
+ }
11484
+ if (opts.treatment) {
11485
+ rules[idx] = { ...rules[idx], buckets: [{ treatment: opts.treatment, size: 1e5 }] };
11486
+ } else {
11487
+ const parsed = JSON.parse(opts.weights);
11488
+ rules[idx] = { ...rules[idx], buckets: parsed };
11489
+ }
11490
+ await confirmWrite(opts, `SET_RULE ${idx} on ${opts.flag} in ${opts.environment}`);
11491
+ const body = {
11492
+ ...buildCrBase(opts),
11493
+ operationType: "UPDATE_SPLIT_DEFINITION",
11494
+ split: {
11495
+ name: opts.flag,
11496
+ environment: { id: opts.environment },
11497
+ definition: { ...current, rules }
11498
+ },
11499
+ workspace: { id: opts.workspace }
11500
+ };
11501
+ const data = await http.splitio("/internal/api/v2/changeRequests", {
11502
+ method: "POST",
11503
+ body
11504
+ });
11505
+ success(data, "splitio", "flags set-rule", start);
11506
+ } catch (err) {
11507
+ fail(err, "splitio", "flags set-rule", start);
11508
+ }
11509
+ });
11510
+ 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>", "ServiceNow 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) => {
11511
+ const start = Date.now();
11512
+ try {
11513
+ const http = getHttp5(program2);
11514
+ const current = await http.splitio(
11515
+ `/internal/api/v2/splits/${encodeURIComponent(opts.flag)}/environments/${encodeURIComponent(opts.environment)}`,
11516
+ { params: { wsId: opts.workspace } }
11517
+ );
11518
+ await confirmWrite(opts, `SET_DEFAULT ${opts.treatment} on ${opts.flag} in ${opts.environment}`);
11519
+ const body = {
11520
+ ...buildCrBase(opts),
11521
+ operationType: "UPDATE_SPLIT_DEFINITION",
11522
+ split: {
11523
+ name: opts.flag,
11524
+ environment: { id: opts.environment },
11525
+ definition: {
11526
+ ...current,
11527
+ defaultRule: [{ treatment: opts.treatment, size: 1e5 }]
11528
+ }
11529
+ },
11530
+ workspace: { id: opts.workspace }
11531
+ };
11532
+ const data = await http.splitio("/internal/api/v2/changeRequests", {
11533
+ method: "POST",
11534
+ body
11535
+ });
11536
+ success(data, "splitio", "flags set-default", start);
11537
+ } catch (err) {
11538
+ fail(err, "splitio", "flags set-default", start);
11539
+ }
11540
+ });
11541
+ flags.command("toggle").description("Enable or disable a single flag via a Change Request").requiredOption("--workspace <id>", "Workspace ID").requiredOption("--flag <name>", "Feature flag name").requiredOption("--environment <id>", "Environment ID").requiredOption("--enabled <bool>", "true to restore / false to kill the flag").requiredOption("--mnemonic <text>", "Short Change Request title (mnemonic)").requiredOption("--description <text>", "Change Request description").requiredOption("--change-number <CHG>", "ServiceNow 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) => {
11542
+ const start = Date.now();
11543
+ try {
11544
+ const enable = opts.enabled.toLowerCase() !== "false" && opts.enabled !== "0";
11545
+ const opType = enable ? "RESTORE_SPLIT" : "KILL_SPLIT";
11546
+ await confirmWrite(opts, `${opType} ${opts.flag} in ${opts.environment}`);
11547
+ const http = getHttp5(program2);
11548
+ const body = {
11549
+ ...buildCrBase(opts),
11550
+ operationType: opType,
11551
+ split: { name: opts.flag, environment: { id: opts.environment } },
11552
+ workspace: { id: opts.workspace }
11553
+ };
11554
+ const data = await http.splitio("/internal/api/v2/changeRequests", {
11555
+ method: "POST",
11556
+ body
11557
+ });
11558
+ success(data, "splitio", "flags toggle", start);
11559
+ } catch (err) {
11560
+ fail(err, "splitio", "flags toggle", start);
11561
+ }
11562
+ });
11563
+ flags.command("batch-toggle").description("Enable or disable multiple flags from a JSON file (each creates its own Change Request)").requiredOption("--workspace <id>", "Workspace ID").requiredOption("--environment <id>", "Environment ID").requiredOption("--mnemonic <text>", "Short Change Request title prefix (mnemonic)").requiredOption("--description <text>", "Change Request description").requiredOption("--change-number <CHG>", "ServiceNow change request number (e.g. CHG0001234)").option("--approvers <ids>", "Comma-separated Split user IDs required to approve each CR").option("--input-file <path>", "JSON file: array of {flag, enabled} objects (use - for stdin)").option("--yes", "Skip interactive confirmation prompt").action(async (opts) => {
11564
+ const start = Date.now();
11565
+ try {
11566
+ if (!opts.inputFile) throw new PncliError("--input-file is required for batch-toggle");
11567
+ const entries = loadInputFile(opts.inputFile);
11568
+ if (!Array.isArray(entries)) throw new PncliError("Input file must be a JSON array of {flag, enabled} objects");
11569
+ await confirmWrite(opts, `BATCH_TOGGLE ${entries.length} flags in ${opts.environment}`);
11570
+ const http = getHttp5(program2);
11571
+ const results = [];
11572
+ for (const entry of entries) {
11573
+ try {
11574
+ const opType = entry.enabled ? "RESTORE_SPLIT" : "KILL_SPLIT";
11575
+ const body = {
11576
+ ...buildCrBase(opts),
11577
+ title: `${opts.mnemonic} \u2014 ${entry.flag}`,
11578
+ operationType: opType,
11579
+ split: { name: entry.flag, environment: { id: opts.environment } },
11580
+ workspace: { id: opts.workspace }
11581
+ };
11582
+ const cr = await http.splitio("/internal/api/v2/changeRequests", {
11583
+ method: "POST",
11584
+ body
11585
+ });
11586
+ results.push({ flag: entry.flag, ok: true, id: cr.id });
11587
+ } catch (err) {
11588
+ results.push({
11589
+ flag: entry.flag,
11590
+ ok: false,
11591
+ error: err instanceof Error ? err.message : String(err)
11592
+ });
11593
+ }
11594
+ }
11595
+ success({ submitted: results.length, results }, "splitio", "flags batch-toggle", start);
11596
+ } catch (err) {
11597
+ fail(err, "splitio", "flags batch-toggle", start);
11598
+ }
11599
+ });
11600
+ flags.command("kill").description("Submit a Change Request to kill (disable) a flag").requiredOption("--workspace <id>", "Workspace ID").requiredOption("--flag <name>", "Feature flag name").requiredOption("--environment <id>", "Environment ID").requiredOption("--mnemonic <text>", "Short Change Request title (mnemonic)").requiredOption("--description <text>", "Change Request description").requiredOption("--change-number <CHG>", "ServiceNow 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) => {
11601
+ const start = Date.now();
11602
+ try {
11603
+ await confirmWrite(opts, `KILL ${opts.flag} in ${opts.environment}`);
11604
+ const http = getHttp5(program2);
11605
+ const body = {
11606
+ ...buildCrBase(opts),
11607
+ operationType: "KILL_SPLIT",
11608
+ split: { name: opts.flag, environment: { id: opts.environment } },
11609
+ workspace: { id: opts.workspace }
11610
+ };
11611
+ const data = await http.splitio("/internal/api/v2/changeRequests", {
11612
+ method: "POST",
11613
+ body
11614
+ });
11615
+ success(data, "splitio", "flags kill", start);
11616
+ } catch (err) {
11617
+ fail(err, "splitio", "flags kill", start);
11618
+ }
11619
+ });
11620
+ flags.command("restore").description("Submit a Change Request to restore a killed flag").requiredOption("--workspace <id>", "Workspace ID").requiredOption("--flag <name>", "Feature flag name").requiredOption("--environment <id>", "Environment ID").requiredOption("--mnemonic <text>", "Short Change Request title (mnemonic)").requiredOption("--description <text>", "Change Request description").requiredOption("--change-number <CHG>", "ServiceNow 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) => {
11621
+ const start = Date.now();
11622
+ try {
11623
+ await confirmWrite(opts, `RESTORE ${opts.flag} in ${opts.environment}`);
11624
+ const http = getHttp5(program2);
11625
+ const body = {
11626
+ ...buildCrBase(opts),
11627
+ operationType: "RESTORE_SPLIT",
11628
+ split: { name: opts.flag, environment: { id: opts.environment } },
11629
+ workspace: { id: opts.workspace }
11630
+ };
11631
+ const data = await http.splitio("/internal/api/v2/changeRequests", {
11632
+ method: "POST",
11633
+ body
11634
+ });
11635
+ success(data, "splitio", "flags restore", start);
11636
+ } catch (err) {
11637
+ fail(err, "splitio", "flags restore", start);
11638
+ }
11639
+ });
11640
+ flags.command("archive").description("Submit a Change Request to archive (delete definition of) a flag").requiredOption("--workspace <id>", "Workspace ID").requiredOption("--flag <name>", "Feature flag name").requiredOption("--mnemonic <text>", "Short Change Request title (mnemonic)").requiredOption("--description <text>", "Change Request description").requiredOption("--change-number <CHG>", "ServiceNow change request number (e.g. CHG0001234)").option("--yes", "Skip interactive confirmation prompt").action(async (opts) => {
11641
+ const start = Date.now();
11642
+ try {
11643
+ await confirmWrite(opts, `ARCHIVE ${opts.flag}`);
11644
+ const http = getHttp5(program2);
11645
+ const body = {
11646
+ ...buildCrBase(opts),
11647
+ operationType: "ARCHIVE_SPLIT",
11648
+ split: { name: opts.flag },
11649
+ workspace: { id: opts.workspace }
11650
+ };
11651
+ const data = await http.splitio("/internal/api/v2/changeRequests", {
11652
+ method: "POST",
11653
+ body
11654
+ });
11655
+ success(data, "splitio", "flags archive", start);
11656
+ } catch (err) {
11657
+ fail(err, "splitio", "flags archive", start);
11658
+ }
11659
+ });
11660
+ const changeRequests = splitio.command("change-requests").description("Change Request tracking");
11661
+ 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) => {
11662
+ const start = Date.now();
11663
+ try {
11664
+ const http = getHttp5(program2);
11665
+ const params = {
11666
+ wsId: opts.workspace,
11667
+ limit: parseInt(opts.limit, 10),
11668
+ offset: parseInt(opts.offset, 10)
11669
+ };
11670
+ if (opts.environment) params["environmentId"] = opts.environment;
11671
+ if (opts.status) params["status"] = opts.status;
11672
+ const data = await http.splitio(
11673
+ "/internal/api/v2/changeRequests",
11674
+ { params }
11675
+ );
11676
+ success(
11677
+ { count: data.objects.length, totalCount: data.totalCount, changeRequests: data.objects },
11678
+ "splitio",
11679
+ "change-requests list",
11680
+ start
11681
+ );
11682
+ } catch (err) {
11683
+ fail(err, "splitio", "change-requests list", start);
11684
+ }
11685
+ });
11686
+ changeRequests.command("get").description("Get a Change Request by ID").requiredOption("--id <id>", "Change Request ID").action(async (opts) => {
11687
+ const start = Date.now();
11688
+ try {
11689
+ const http = getHttp5(program2);
11690
+ const data = await http.splitio(
11691
+ `/internal/api/v2/changeRequests/${encodeURIComponent(opts.id)}`
11692
+ );
11693
+ success(data, "splitio", "change-requests get", start);
11694
+ } catch (err) {
11695
+ fail(err, "splitio", "change-requests get", start);
11696
+ }
11697
+ });
11698
+ }
11699
+
11700
+ // src/services/figma/commands.ts
11701
+ function parseFileKey(input3) {
11702
+ if (input3.startsWith("http://") || input3.startsWith("https://")) {
11703
+ const match = /figma\.com\/(?:design|file)\/([A-Za-z0-9]+)/i.exec(input3);
11704
+ if (!match?.[1]) {
11705
+ throw new Error(`Could not extract Figma file key from URL: ${input3}
11706
+ Expected format: https://www.figma.com/design/<file-key>/...`);
11707
+ }
11708
+ return match[1];
11709
+ }
11710
+ return input3;
11711
+ }
11712
+ function getHttp6(program2) {
11713
+ const opts = program2.optsWithGlobals();
11714
+ return createHttpClient(
11715
+ loadConfig({ configPath: opts.config }),
11716
+ Boolean(opts.dryRun)
11717
+ );
11718
+ }
11719
+ function registerFigmaCommands(program2) {
11720
+ const figma = program2.command("figma").description("Figma design operations (files, comments, versions)");
11721
+ figma.command("file").description("Get a Figma file \u2014 metadata, structure, and component/style inventory").argument("<file-key-or-url>", "Figma file key or full Figma URL (https://www.figma.com/design/<key>/...)").option("--document", "Include the full document node tree (can be large; omitted by default)").action(async (fileKeyOrUrl, opts) => {
11722
+ const start = Date.now();
11723
+ const { success: success2, fail: fail2 } = await import("./output-XT3WT5L5.js");
11724
+ try {
11725
+ const fileKey = parseFileKey(fileKeyOrUrl);
11726
+ const params = {};
11727
+ if (!opts.document) {
11728
+ params["geometry"] = "paths";
11729
+ params["depth"] = 0;
11730
+ }
11731
+ const data = await getHttp6(program2).figma(`/v1/files/${encodeURIComponent(fileKey)}`, { params });
11732
+ success2(
11733
+ {
11734
+ fileKey,
11735
+ name: data.name,
11736
+ lastModified: data.lastModified,
11737
+ version: data.version,
11738
+ thumbnailUrl: data.thumbnailUrl,
11739
+ role: data.role,
11740
+ editorType: data.editorType,
11741
+ schemaVersion: data.schemaVersion,
11742
+ ...opts.document ? { document: data.document } : {},
11743
+ componentCount: data.components ? Object.keys(data.components).length : 0,
11744
+ styleCount: data.styles ? Object.keys(data.styles).length : 0
11745
+ },
11746
+ "figma",
11747
+ "file",
11748
+ start
11749
+ );
11750
+ } catch (err) {
11751
+ fail2(err, "figma", "file", start);
11752
+ }
11753
+ });
11754
+ figma.command("comments").description("Get comments on a Figma file").argument("<file-key-or-url>", "Figma file key or full Figma URL").option("--as-of <date>", "ISO 8601 timestamp \u2014 return only comments created before this time").action(async (fileKeyOrUrl, opts) => {
11755
+ const start = Date.now();
11756
+ const { success: success2, fail: fail2 } = await import("./output-XT3WT5L5.js");
11757
+ try {
11758
+ const fileKey = parseFileKey(fileKeyOrUrl);
11759
+ const params = {};
11760
+ if (opts.asOf) params["as_of"] = opts.asOf;
11761
+ const data = await getHttp6(program2).figma(`/v1/files/${encodeURIComponent(fileKey)}/comments`, { params });
11762
+ success2(
11763
+ { fileKey, count: data.comments.length, comments: data.comments },
11764
+ "figma",
11765
+ "comments",
11766
+ start
11767
+ );
11768
+ } catch (err) {
11769
+ fail2(err, "figma", "comments", start);
11770
+ }
11771
+ });
11772
+ figma.command("versions").description("Get the version history of a Figma file").argument("<file-key-or-url>", "Figma file key or full Figma URL").action(async (fileKeyOrUrl) => {
11773
+ const start = Date.now();
11774
+ const { success: success2, fail: fail2 } = await import("./output-XT3WT5L5.js");
11775
+ try {
11776
+ const fileKey = parseFileKey(fileKeyOrUrl);
11777
+ const data = await getHttp6(program2).figma(`/v1/files/${encodeURIComponent(fileKey)}/versions`);
11778
+ success2(
11779
+ { fileKey, count: data.versions.length, versions: data.versions },
11780
+ "figma",
11781
+ "versions",
11782
+ start
11783
+ );
11784
+ } catch (err) {
11785
+ fail2(err, "figma", "versions", start);
11786
+ }
11787
+ });
11788
+ figma.command("project-files").description("List files in a Figma project").argument("<project-id>", "Figma project ID (visible in the project URL)").action(async (projectId) => {
11789
+ const start = Date.now();
11790
+ const { success: success2, fail: fail2 } = await import("./output-XT3WT5L5.js");
11791
+ try {
11792
+ const data = await getHttp6(program2).figma(`/v1/projects/${encodeURIComponent(projectId)}/files`);
11793
+ success2(
11794
+ { projectId, count: data.files.length, files: data.files },
11795
+ "figma",
11796
+ "project-files",
11797
+ start
11798
+ );
11799
+ } catch (err) {
11800
+ fail2(err, "figma", "project-files", start);
11801
+ }
11802
+ });
11803
+ figma.command("me").description("Get the current Figma user (useful for verifying credentials)").action(async () => {
11804
+ const start = Date.now();
11805
+ const { success: success2, fail: fail2 } = await import("./output-XT3WT5L5.js");
11806
+ try {
11807
+ const data = await getHttp6(program2).figma("/v1/me");
11808
+ success2(data, "figma", "me", start);
11809
+ } catch (err) {
11810
+ fail2(err, "figma", "me", start);
11811
+ }
11812
+ });
11813
+ }
11814
+
11359
11815
  // src/cli.ts
11360
11816
  if (!process.env.PNCLI_VERIFY_TLS) {
11361
11817
  process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
@@ -11392,7 +11848,6 @@ registerConfigCommands(program);
11392
11848
  registerAdoCommands(program);
11393
11849
  registerJenkinsCommands(program);
11394
11850
  registerArtifactoryCommands(program);
11395
- registerUdeployCommands(program);
11396
11851
  registerCheckmarxCommands(program);
11397
11852
  registerServiceNowCommands(program);
11398
11853
  registerContrastCommands(program);
@@ -11402,6 +11857,8 @@ registerJwtCommands(program);
11402
11857
  registerOpenShiftCommands(program);
11403
11858
  registerDynatraceCommands(program);
11404
11859
  registerLogscaleCommands(program);
11860
+ registerSplitioCommands(program);
11861
+ registerFigmaCommands(program);
11405
11862
  program.addHelpText("after", `
11406
11863
  Services:
11407
11864
  git Local git operations (status, diff, log, branch)
@@ -11413,9 +11870,8 @@ Services:
11413
11870
  sonar SonarQube Server (quality gates, issues, metrics, hotspots)
11414
11871
  sde SDElements (threat modeling, countermeasures, compliance)
11415
11872
  ado Azure DevOps Server (work items, repos, PRs, pipelines)
11416
- jenkins Jenkins Data Center (jobs, builds, logs)
11873
+ jenkins Jenkins (jobs, builds, logs, named instances)
11417
11874
  artifactory Artifactory (repos, artifact search, build info, properties)
11418
- udeploy IBM UrbanCode Deploy (component versions, deployment processes)
11419
11875
  checkmarx Checkmarx One (projects, scans, scan statistics)
11420
11876
  servicenow ServiceNow (change requests)
11421
11877
  contrast Contrast IAST (applications, vulnerability findings)
@@ -11423,6 +11879,8 @@ Services:
11423
11879
  openshift OpenShift / Kubernetes (pods, events, logs, metrics)
11424
11880
  dynatrace Dynatrace (services, entities, problems, traces, Kubernetes workloads)
11425
11881
  logscale LogScale (log query, repositories)
11882
+ splitio Split.IO (feature flags, Change Requests)
11883
+ figma Figma (files, comments, version history)
11426
11884
  config Manage pncli configuration
11427
11885
  skills Download and manage Claude Code skills
11428
11886
  jwt JWT token utilities (decode header and payload)