@kolatts/pncli 1.9.0 → 1.10.1

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
@@ -7,7 +7,7 @@ import {
7
7
  setRepoConfigValue,
8
8
  writeGlobalConfig,
9
9
  writeRepoConfig
10
- } from "./chunk-YR6GFJMV.js";
10
+ } from "./chunk-O3LWAWSN.js";
11
11
  import {
12
12
  ExitCode,
13
13
  PncliError,
@@ -18,7 +18,7 @@ import {
18
18
  setGlobalUser,
19
19
  success,
20
20
  warn
21
- } from "./chunk-QQZJQ5DP.js";
21
+ } from "./chunk-3XHTRFRX.js";
22
22
  import {
23
23
  AdoCoreClient
24
24
  } from "./chunk-VDL32PSG.js";
@@ -3021,11 +3021,224 @@ async function checkPackagesForVulnsViaSonatype(packages) {
3021
3021
  return allVulnerable;
3022
3022
  }
3023
3023
 
3024
- // src/services/deps/clients/artifactory.ts
3025
- var TIMEOUT_MS3 = 15e3;
3026
- async function fetchWithTimeout3(url, token) {
3024
+ // src/services/deps/clients/sonatypeiq.ts
3025
+ var TIMEOUT_MS3 = 6e4;
3026
+ var MAX_POLL_ATTEMPTS = 10;
3027
+ var POLL_INTERVAL_MS = 2e3;
3028
+ var BATCH_SIZE3 = 128;
3029
+ async function fetchWithTimeout3(url, init) {
3027
3030
  const controller = new AbortController();
3028
3031
  const timer = setTimeout(() => controller.abort(), TIMEOUT_MS3);
3032
+ try {
3033
+ return await fetch(url, { ...init, signal: controller.signal });
3034
+ } finally {
3035
+ clearTimeout(timer);
3036
+ }
3037
+ }
3038
+ function buildBasicAuth(userCode, passcode) {
3039
+ return `Basic ${Buffer.from(`${userCode}:${passcode}`).toString("base64")}`;
3040
+ }
3041
+ function threatLevelToSeverity(level) {
3042
+ if (level >= 9) return "CRITICAL";
3043
+ if (level >= 7) return "HIGH";
3044
+ if (level >= 4) return "MEDIUM";
3045
+ if (level > 0) return "LOW";
3046
+ return "UNKNOWN";
3047
+ }
3048
+ function toPurl2(pkg2) {
3049
+ switch (pkg2.ecosystem) {
3050
+ case "npm": {
3051
+ const encodedName = pkg2.name.startsWith("@") ? pkg2.name.replace("@", "%40") : pkg2.name;
3052
+ return `pkg:npm/${encodedName}@${pkg2.version}`;
3053
+ }
3054
+ case "nuget":
3055
+ return `pkg:nuget/${pkg2.name}@${pkg2.version}`;
3056
+ case "maven": {
3057
+ const colonIdx = pkg2.name.indexOf(":");
3058
+ if (colonIdx !== -1) {
3059
+ const groupId = pkg2.name.slice(0, colonIdx);
3060
+ const artifactId = pkg2.name.slice(colonIdx + 1);
3061
+ return `pkg:maven/${encodeURIComponent(groupId)}/${encodeURIComponent(artifactId)}@${pkg2.version}`;
3062
+ }
3063
+ return `pkg:maven/${encodeURIComponent(pkg2.name)}@${pkg2.version}`;
3064
+ }
3065
+ default:
3066
+ return `pkg:generic/${encodeURIComponent(pkg2.name)}@${pkg2.version}`;
3067
+ }
3068
+ }
3069
+ function mapViolationToVuln(violation) {
3070
+ const cveRefs = violation.constraintViolations.flatMap(
3071
+ (cv) => cv.reasons.filter((r) => r.reference?.type === "VULNERABILITY").map((r) => r.reference.value)
3072
+ );
3073
+ const id = cveRefs[0] ?? violation.policyId;
3074
+ const severity = threatLevelToSeverity(violation.policyThreatLevel);
3075
+ return {
3076
+ id,
3077
+ summary: violation.policyName,
3078
+ severity,
3079
+ cvss: null,
3080
+ aliases: [...new Set(cveRefs)],
3081
+ remediation: {
3082
+ fixAvailable: false,
3083
+ fixedVersions: [],
3084
+ advice: `Policy violation: ${violation.policyName} (threat level ${violation.policyThreatLevel}). Check Sonatype IQ Server for details.`
3085
+ },
3086
+ references: []
3087
+ };
3088
+ }
3089
+ function syntheticVulnsFromCounts(criticalCount, severeCount, moderateCount, pkgName) {
3090
+ const vulns = [];
3091
+ if (criticalCount > 0) {
3092
+ vulns.push({
3093
+ id: `iq-critical-${pkgName}`,
3094
+ summary: `${criticalCount} critical vulnerability(ies) detected by Sonatype IQ`,
3095
+ severity: "CRITICAL",
3096
+ cvss: null,
3097
+ aliases: [],
3098
+ remediation: { fixAvailable: false, fixedVersions: [], advice: "Check Sonatype IQ Server for details." },
3099
+ references: []
3100
+ });
3101
+ }
3102
+ if (severeCount > 0) {
3103
+ vulns.push({
3104
+ id: `iq-high-${pkgName}`,
3105
+ summary: `${severeCount} severe vulnerability(ies) detected by Sonatype IQ`,
3106
+ severity: "HIGH",
3107
+ cvss: null,
3108
+ aliases: [],
3109
+ remediation: { fixAvailable: false, fixedVersions: [], advice: "Check Sonatype IQ Server for details." },
3110
+ references: []
3111
+ });
3112
+ }
3113
+ if (moderateCount > 0) {
3114
+ vulns.push({
3115
+ id: `iq-medium-${pkgName}`,
3116
+ summary: `${moderateCount} moderate vulnerability(ies) detected by Sonatype IQ`,
3117
+ severity: "MEDIUM",
3118
+ cvss: null,
3119
+ aliases: [],
3120
+ remediation: { fixAvailable: false, fixedVersions: [], advice: "Check Sonatype IQ Server for details." },
3121
+ references: []
3122
+ });
3123
+ }
3124
+ return vulns;
3125
+ }
3126
+ async function resolveApplicationInternalId(baseUrl, applicationId, auth) {
3127
+ const url = `${baseUrl.replace(/\/$/, "")}/api/v2/applications?publicId=${encodeURIComponent(applicationId)}`;
3128
+ const res = await fetchWithTimeout3(url, {
3129
+ method: "GET",
3130
+ headers: { "Authorization": auth, "Accept": "application/json" }
3131
+ });
3132
+ if (!res.ok) return applicationId;
3133
+ const data = await res.json();
3134
+ if (data.applications && data.applications.length > 0) {
3135
+ return data.applications[0].id;
3136
+ }
3137
+ return applicationId;
3138
+ }
3139
+ async function pollResults(baseUrl, resultsUrl, auth) {
3140
+ for (let attempt = 0; attempt < MAX_POLL_ATTEMPTS; attempt++) {
3141
+ if (attempt > 0) {
3142
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS * attempt));
3143
+ }
3144
+ const url = resultsUrl.startsWith("http") ? resultsUrl : `${baseUrl.replace(/\/$/, "")}${resultsUrl}`;
3145
+ const res = await fetchWithTimeout3(url, {
3146
+ method: "GET",
3147
+ headers: { "Authorization": auth, "Accept": "application/json" }
3148
+ });
3149
+ if (!res.ok) {
3150
+ throw new Error(`Sonatype IQ Server returned HTTP ${res.status} polling results`);
3151
+ }
3152
+ const data = await res.json();
3153
+ if (!data.isQueued) return data;
3154
+ }
3155
+ throw new Error("Sonatype IQ Server evaluation timed out waiting for results.");
3156
+ }
3157
+ async function evaluateBatch(purls, packages, baseUrl, applicationId, auth) {
3158
+ const url = `${baseUrl.replace(/\/$/, "")}/api/v2/evaluation/applications/${encodeURIComponent(applicationId)}`;
3159
+ const res = await fetchWithTimeout3(url, {
3160
+ method: "POST",
3161
+ headers: { "Authorization": auth, "Content-Type": "application/json", "Accept": "application/json" },
3162
+ body: JSON.stringify({ components: purls.map((p) => ({ packageUrl: p })) })
3163
+ });
3164
+ if (!res.ok) {
3165
+ throw new Error(`Sonatype IQ Server returned HTTP ${res.status}`);
3166
+ }
3167
+ let data = await res.json();
3168
+ if (data.isQueued && data.resultsUrl) {
3169
+ data = await pollResults(baseUrl, data.resultsUrl, auth);
3170
+ }
3171
+ const components = data.components ?? [];
3172
+ const vulnerable = [];
3173
+ const purlToPackage = /* @__PURE__ */ new Map();
3174
+ for (let i = 0; i < purls.length; i++) {
3175
+ purlToPackage.set(purls[i], packages[i]);
3176
+ }
3177
+ for (const result of components) {
3178
+ if (!result?.component?.packageUrl) continue;
3179
+ const pkg2 = purlToPackage.get(result.component.packageUrl);
3180
+ if (!pkg2) continue;
3181
+ const criticalCount = result.criticalVulnerabilityCount ?? 0;
3182
+ const severeCount = result.severeVulnerabilityCount ?? 0;
3183
+ const moderateCount = result.moderateVulnerabilityCount ?? 0;
3184
+ const totalVulns = criticalCount + severeCount + moderateCount;
3185
+ const violations = result.policyData?.policyViolations ?? [];
3186
+ const securityViolations = violations.filter((v) => v.policyThreatCategory === "SECURITY");
3187
+ if (securityViolations.length === 0 && totalVulns === 0) continue;
3188
+ const vulnerabilities = securityViolations.length > 0 ? securityViolations.map((v) => mapViolationToVuln(v)) : syntheticVulnsFromCounts(criticalCount, severeCount, moderateCount, pkg2.name);
3189
+ vulnerable.push({ ...pkg2, vulnerabilities });
3190
+ }
3191
+ return vulnerable;
3192
+ }
3193
+ async function checkPackagesViaIqServer(packages, applicationId, config) {
3194
+ const { baseUrl, userCode, passcode } = config.sonatypeiq;
3195
+ if (!baseUrl) throw new Error("Sonatype IQ Server baseUrl not configured.");
3196
+ if (!userCode || !passcode) throw new Error("Sonatype IQ Server credentials not configured.");
3197
+ const auth = buildBasicAuth(userCode, passcode);
3198
+ const internalId = await resolveApplicationInternalId(baseUrl, applicationId, auth);
3199
+ const allVulnerable = [];
3200
+ for (let i = 0; i < packages.length; i += BATCH_SIZE3) {
3201
+ const chunk = packages.slice(i, i + BATCH_SIZE3);
3202
+ const purls = chunk.map(toPurl2);
3203
+ const results = await evaluateBatch(purls, chunk, baseUrl, internalId, auth);
3204
+ allVulnerable.push(...results);
3205
+ }
3206
+ return allVulnerable;
3207
+ }
3208
+ async function checkSonatypeIqConnectivity(baseUrl, userCode, passcode) {
3209
+ try {
3210
+ const auth = buildBasicAuth(userCode, passcode);
3211
+ const url = `${baseUrl.replace(/\/$/, "")}/api/v2/applications?limit=1`;
3212
+ const controller = new AbortController();
3213
+ const timer = setTimeout(() => controller.abort(), 1e4);
3214
+ let res;
3215
+ try {
3216
+ res = await fetch(url, {
3217
+ method: "GET",
3218
+ headers: { "Authorization": auth, "Accept": "application/json" },
3219
+ signal: controller.signal
3220
+ });
3221
+ } finally {
3222
+ clearTimeout(timer);
3223
+ }
3224
+ if (res.status === 401 || res.status === 403) {
3225
+ return { reachable: true, authenticated: false, error: `Authentication failed (HTTP ${res.status})` };
3226
+ }
3227
+ return { reachable: true, authenticated: res.ok };
3228
+ } catch (err) {
3229
+ return {
3230
+ reachable: false,
3231
+ authenticated: false,
3232
+ error: err instanceof Error ? err.message : String(err)
3233
+ };
3234
+ }
3235
+ }
3236
+
3237
+ // src/services/deps/clients/artifactory.ts
3238
+ var TIMEOUT_MS4 = 15e3;
3239
+ async function fetchWithTimeout4(url, token) {
3240
+ const controller = new AbortController();
3241
+ const timer = setTimeout(() => controller.abort(), TIMEOUT_MS4);
3029
3242
  try {
3030
3243
  return await fetch(url, {
3031
3244
  headers: { Authorization: `Bearer ${token}` },
@@ -3061,7 +3274,7 @@ async function checkArtifactoryConnectivity(config) {
3061
3274
  };
3062
3275
  }
3063
3276
  try {
3064
- const res = await fetchWithTimeout3(`${config.baseUrl}/api/system/ping`, config.token);
3277
+ const res = await fetchWithTimeout4(`${config.baseUrl}/api/system/ping`, config.token);
3065
3278
  if (res.status === 401 || res.status === 403) {
3066
3279
  return {
3067
3280
  reachable: true,
@@ -3111,7 +3324,7 @@ function repoForEcosystem(config, eco) {
3111
3324
  async function getLatestNpm(baseUrl, token, repoName, packageName) {
3112
3325
  try {
3113
3326
  const encoded = packageName.startsWith("@") ? packageName.replace("/", "%2F") : packageName;
3114
- const res = await fetchWithTimeout3(`${baseUrl}/api/npm/${repoName}/${encoded}`, token);
3327
+ const res = await fetchWithTimeout4(`${baseUrl}/api/npm/${repoName}/${encoded}`, token);
3115
3328
  if (!res.ok) return null;
3116
3329
  const data = await res.json();
3117
3330
  return data["dist-tags"]?.latest ?? null;
@@ -3122,7 +3335,7 @@ async function getLatestNpm(baseUrl, token, repoName, packageName) {
3122
3335
  async function getLatestNuget(baseUrl, token, repoName, packageName) {
3123
3336
  try {
3124
3337
  const lower = packageName.toLowerCase();
3125
- const res = await fetchWithTimeout3(
3338
+ const res = await fetchWithTimeout4(
3126
3339
  `${baseUrl}/api/nuget/v3/${repoName}/flatcontainer/${lower}/index.json`,
3127
3340
  token
3128
3341
  );
@@ -3138,7 +3351,7 @@ async function getLatestMaven(baseUrl, token, repoName, packageName) {
3138
3351
  const [groupId, artifactId] = packageName.split(":");
3139
3352
  if (!groupId || !artifactId) return null;
3140
3353
  try {
3141
- const res = await fetchWithTimeout3(
3354
+ const res = await fetchWithTimeout4(
3142
3355
  `${baseUrl}/api/search/latestVersion?g=${encodeURIComponent(groupId)}&a=${encodeURIComponent(artifactId)}&repos=${repoName}`,
3143
3356
  token
3144
3357
  );
@@ -3188,7 +3401,7 @@ async function getOutdatedPackages(packages, config, filterType) {
3188
3401
  async function getLicenseNpm(baseUrl, token, repoName, packageName) {
3189
3402
  try {
3190
3403
  const encoded = packageName.startsWith("@") ? packageName.replace("/", "%2F") : packageName;
3191
- const res = await fetchWithTimeout3(`${baseUrl}/api/npm/${repoName}/${encoded}`, token);
3404
+ const res = await fetchWithTimeout4(`${baseUrl}/api/npm/${repoName}/${encoded}`, token);
3192
3405
  if (!res.ok) return null;
3193
3406
  const data = await res.json();
3194
3407
  if (typeof data.license === "string") return data.license;
@@ -3201,7 +3414,7 @@ async function getLicenseNpm(baseUrl, token, repoName, packageName) {
3201
3414
  async function getLicenseNuget(baseUrl, token, repoName, packageName, version) {
3202
3415
  try {
3203
3416
  const lower = packageName.toLowerCase();
3204
- const res = await fetchWithTimeout3(
3417
+ const res = await fetchWithTimeout4(
3205
3418
  `${baseUrl}/api/nuget/v3/${repoName}/registration/${lower}/${version}.json`,
3206
3419
  token
3207
3420
  );
@@ -3258,11 +3471,15 @@ async function checkSonatypeReachable() {
3258
3471
  return result.reachable;
3259
3472
  }
3260
3473
  async function buildConnectivityData(config) {
3261
- const [osvResult, artResult, sonatypeResult] = await Promise.all([
3474
+ const iqCfg = config.sonatypeiq;
3475
+ const iqConfigured = !!(iqCfg.baseUrl && iqCfg.userCode && iqCfg.passcode);
3476
+ const checks = await Promise.all([
3262
3477
  checkOsvConnectivity(),
3263
3478
  checkArtifactoryConnectivity(config.artifactory),
3264
- checkSonatypeConnectivity()
3479
+ checkSonatypeConnectivity(),
3480
+ iqConfigured ? checkSonatypeIqConnectivity(iqCfg.baseUrl, iqCfg.userCode, iqCfg.passcode) : Promise.resolve(null)
3265
3481
  ]);
3482
+ const [osvResult, artResult, sonatypeResult, iqResult] = checks;
3266
3483
  const artCfg = config.artifactory;
3267
3484
  let tier = "local";
3268
3485
  if (artResult.reachable && artResult.authenticated) tier = "artifactory";
@@ -3294,13 +3511,21 @@ async function buildConnectivityData(config) {
3294
3511
  url: "https://ossindex.sonatype.org",
3295
3512
  ...sonatypeResult.error ? { error: sonatypeResult.error } : {}
3296
3513
  },
3514
+ ...iqConfigured && iqResult ? {
3515
+ sonatypeiq: {
3516
+ reachable: iqResult.reachable,
3517
+ authenticated: iqResult.authenticated,
3518
+ url: iqCfg.baseUrl,
3519
+ ...iqResult.error ? { error: iqResult.error } : {}
3520
+ }
3521
+ } : {},
3297
3522
  tier,
3298
3523
  capabilities: {
3299
3524
  scan: true,
3300
3525
  diff: true,
3301
3526
  outdated: artResult.reachable && artResult.authenticated,
3302
3527
  licenseCheck: artResult.reachable && artResult.authenticated,
3303
- cveCheck: osvResult.reachable || sonatypeResult.reachable
3528
+ cveCheck: osvResult.reachable || sonatypeResult.reachable || (iqResult?.reachable && iqResult?.authenticated) === true
3304
3529
  }
3305
3530
  };
3306
3531
  }
@@ -3309,13 +3534,27 @@ async function buildConnectivityData(config) {
3309
3534
  function toErrorMessage(err) {
3310
3535
  return err instanceof Error ? err.message : String(err);
3311
3536
  }
3312
- async function runFrisk(config, opts, source = "osv") {
3537
+ async function runFrisk(config, opts, source = "osv", applicationId) {
3313
3538
  const repoRoot = getRepoRoot();
3314
3539
  if (!repoRoot) {
3315
3540
  throw new PncliError("Not inside a git repository.", 1);
3316
3541
  }
3542
+ if (source === "sonatypeiq") {
3543
+ if (!config.sonatypeiq.baseUrl || !config.sonatypeiq.userCode || !config.sonatypeiq.passcode) {
3544
+ throw new PncliError(
3545
+ "deps frisk --source sonatypeiq requires Sonatype IQ Server to be configured. Run: pncli config init",
3546
+ 1
3547
+ );
3548
+ }
3549
+ if (!applicationId) {
3550
+ throw new PncliError(
3551
+ "deps frisk --source sonatypeiq requires --application-id <id>. Use 'pncli sonatypeiq applications list' to find application IDs.",
3552
+ 1
3553
+ );
3554
+ }
3555
+ }
3317
3556
  const { tier: baseTier, osvReachable } = await detectTier(config);
3318
- const sonatypeReachable = source !== "osv" ? await checkSonatypeReachable() : false;
3557
+ const sonatypeReachable = source === "sonatype" || source === "all" ? await checkSonatypeReachable() : false;
3319
3558
  const tier = sonatypeReachable ? "full" : baseTier;
3320
3559
  if (source === "osv" && !osvReachable) {
3321
3560
  throw new PncliError(
@@ -3356,7 +3595,9 @@ async function runFrisk(config, opts, source = "osv") {
3356
3595
  return { tier, source, sourcesQueried, sourceErrors, scanned: 0, vulnerable: 0, packages: [] };
3357
3596
  }
3358
3597
  let vulnerable;
3359
- if (source === "sonatype") {
3598
+ if (source === "sonatypeiq") {
3599
+ vulnerable = await checkPackagesViaIqServer(scan.packages, applicationId, config);
3600
+ } else if (source === "sonatype") {
3360
3601
  vulnerable = await checkPackagesForVulnsViaSonatype(scan.packages);
3361
3602
  } else if (source === "all") {
3362
3603
  const [osvSettled, sonatypeSettled] = await Promise.allSettled([
@@ -3491,7 +3732,7 @@ async function runLicenseCheck(config, opts) {
3491
3732
  // src/services/deps/commands.ts
3492
3733
  function registerDepsCommands(program2) {
3493
3734
  const deps = program2.command("deps").description("Dependency scanning, CVE detection, and license auditing");
3494
- deps.command("frisk").description("Scan all dependencies for CVEs and output remediation paths (requires OSV.dev or Sonatype OSS Index)").option("--ecosystem <ecosystem>", "Filter to one ecosystem: npm, nuget, maven, all", "all").option("--direct-only", "Only scan direct dependencies (default: include transitive)", false).option("--include-dev", "Include dev/test dependencies", false).addOption(new Option("--source <source>", "Vulnerability source").choices(["osv", "sonatype", "all"]).default("osv")).action(async (opts, cmd) => {
3735
+ deps.command("frisk").description("Scan all dependencies for CVEs and output remediation paths (requires OSV.dev, Sonatype OSS Index, or Sonatype IQ Server)").option("--ecosystem <ecosystem>", "Filter to one ecosystem: npm, nuget, maven, all", "all").option("--direct-only", "Only scan direct dependencies (default: include transitive)", false).option("--include-dev", "Include dev/test dependencies", false).addOption(new Option("--source <source>", "Vulnerability source").choices(["osv", "sonatype", "sonatypeiq", "all"]).default("osv")).option("--application-id <id>", "Sonatype IQ Server application ID (required when --source sonatypeiq)").action(async (opts, cmd) => {
3495
3736
  const startTime = Date.now();
3496
3737
  try {
3497
3738
  const globalOpts = cmd.optsWithGlobals();
@@ -3501,7 +3742,7 @@ function registerDepsCommands(program2) {
3501
3742
  includeTransitive: !opts.directOnly,
3502
3743
  includeDev: opts.includeDev
3503
3744
  };
3504
- const data = await runFrisk(config, scanOpts, opts.source);
3745
+ const data = await runFrisk(config, scanOpts, opts.source, opts.applicationId);
3505
3746
  success(data, "deps", "frisk", startTime);
3506
3747
  } catch (err) {
3507
3748
  fail(err, "deps", "frisk", startTime);
@@ -3894,6 +4135,16 @@ function registerConfigCommands(program2) {
3894
4135
  } else {
3895
4136
  results.contrast = { ok: null, message: "not configured" };
3896
4137
  }
4138
+ if (cfg.sonatypeiq.baseUrl && cfg.sonatypeiq.userCode && cfg.sonatypeiq.passcode) {
4139
+ try {
4140
+ await http.sonatypeiq("/api/v2/applications", { params: { limit: 1 } });
4141
+ results.sonatypeiq = { ok: true, message: "connected" };
4142
+ } catch (err) {
4143
+ results.sonatypeiq = { ok: false, message: err instanceof Error ? err.message : String(err) };
4144
+ }
4145
+ } else {
4146
+ results.sonatypeiq = { ok: null, message: "not configured" };
4147
+ }
3897
4148
  success(results, "config", "test", start);
3898
4149
  } catch (err) {
3899
4150
  fail(err, "config", "test", start);
@@ -4065,11 +4316,23 @@ function registerConfigCommands(program2) {
4065
4316
  results.contrast = categorize2(err);
4066
4317
  }
4067
4318
  }
4319
+ if (!cfg.sonatypeiq.userCode || !cfg.sonatypeiq.passcode) {
4320
+ results.sonatypeiq = { status: "blank", message: "not configured" };
4321
+ } else if (!cfg.sonatypeiq.baseUrl) {
4322
+ results.sonatypeiq = { status: "error", message: "baseUrl not configured" };
4323
+ } else {
4324
+ try {
4325
+ await http.sonatypeiq("/api/v2/applications", { params: { limit: 1 }, timeoutMs: 1e4 });
4326
+ results.sonatypeiq = { status: "valid", message: "ok" };
4327
+ } catch (err) {
4328
+ results.sonatypeiq = categorize2(err);
4329
+ }
4330
+ }
4068
4331
  const statuses = Object.values(results).map((r) => r.status);
4069
4332
  if (statuses.includes("invalid")) process.exitCode = ExitCode.AUTH_ERROR;
4070
4333
  else if (statuses.includes("error")) process.exitCode = ExitCode.NETWORK_ERROR;
4071
4334
  if (cmdOpts.output === "table") {
4072
- const services = ["jira", "bitbucket", "confluence", "sonar", "sde", "ado", "jenkins", "udeploy", "artifactory", "checkmarx", "servicenow", "contrast"];
4335
+ const services = ["jira", "bitbucket", "confluence", "sonar", "sde", "ado", "jenkins", "udeploy", "artifactory", "checkmarx", "servicenow", "contrast", "sonatypeiq"];
4073
4336
  const labelWidth = 14;
4074
4337
  const statusWidth = 9;
4075
4338
  for (const svc of services) {
@@ -4083,7 +4346,7 @@ function registerConfigCommands(program2) {
4083
4346
  process.stdout.write("\n");
4084
4347
  } else {
4085
4348
  if (opts.pretty) {
4086
- const services = ["jira", "bitbucket", "confluence", "sonar", "sde", "ado", "jenkins", "udeploy", "artifactory", "checkmarx", "servicenow", "contrast"];
4349
+ const services = ["jira", "bitbucket", "confluence", "sonar", "sde", "ado", "jenkins", "udeploy", "artifactory", "checkmarx", "servicenow", "contrast", "sonatypeiq"];
4087
4350
  const labelWidth = 14;
4088
4351
  const statusWidth = 9;
4089
4352
  for (const svc of services) {
@@ -4103,6 +4366,11 @@ function registerConfigCommands(program2) {
4103
4366
  }
4104
4367
  });
4105
4368
  }
4369
+ function normalizeBaseUrl(raw) {
4370
+ const trimmed = raw.trim();
4371
+ if (!trimmed) return trimmed;
4372
+ return /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
4373
+ }
4106
4374
  async function initGlobalConfig(start) {
4107
4375
  process.stderr.write("pncli config init \u2014 Global configuration\n\n");
4108
4376
  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");
@@ -4116,7 +4384,7 @@ async function initGlobalConfig(start) {
4116
4384
  });
4117
4385
  process.stderr.write("\n\u2500\u2500 Jira \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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");
4118
4386
  const jiraBaseUrl = await input({
4119
- message: "Jira base URL (e.g. https://jira.your-company.com):",
4387
+ message: "Jira base URL (e.g. jira.your-company.com):",
4120
4388
  default: ""
4121
4389
  });
4122
4390
  const jiraApiToken = await password({
@@ -4124,7 +4392,7 @@ async function initGlobalConfig(start) {
4124
4392
  });
4125
4393
  process.stderr.write("\n\u2500\u2500 Bitbucket \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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");
4126
4394
  const bitbucketBaseUrl = await input({
4127
- message: "Bitbucket Server base URL (e.g. https://bitbucket.your-company.com):",
4395
+ message: "Bitbucket Server base URL (e.g. bitbucket.your-company.com):",
4128
4396
  default: ""
4129
4397
  });
4130
4398
  const bitbucketPat = await password({
@@ -4132,7 +4400,7 @@ async function initGlobalConfig(start) {
4132
4400
  });
4133
4401
  process.stderr.write("\n\u2500\u2500 Confluence \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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");
4134
4402
  const confluenceBaseUrl = await input({
4135
- message: "Confluence base URL (e.g. https://confluence.your-company.com):",
4403
+ message: "Confluence base URL (e.g. confluence.your-company.com):",
4136
4404
  default: ""
4137
4405
  });
4138
4406
  const confluenceApiToken = await password({
@@ -4150,7 +4418,7 @@ async function initGlobalConfig(start) {
4150
4418
  let mavenRepo = "";
4151
4419
  if (useArtifactory) {
4152
4420
  artifactoryBaseUrl = await input({
4153
- message: "Artifactory base URL (e.g. https://artifactory.company.com):",
4421
+ message: "Artifactory base URL (e.g. artifactory.company.com):",
4154
4422
  default: ""
4155
4423
  });
4156
4424
  artifactoryToken = await password({
@@ -4179,7 +4447,7 @@ async function initGlobalConfig(start) {
4179
4447
  let sonarToken = "";
4180
4448
  if (useSonar) {
4181
4449
  sonarBaseUrl = await input({
4182
- message: "SonarQube Server base URL (e.g. https://sonar.your-company.com):",
4450
+ message: "SonarQube Server base URL (e.g. sonar.your-company.com):",
4183
4451
  default: ""
4184
4452
  });
4185
4453
  sonarToken = await password({
@@ -4232,7 +4500,7 @@ async function initGlobalConfig(start) {
4232
4500
  let adoDiscoveredTypes = [];
4233
4501
  if (useAdo) {
4234
4502
  adoBaseUrl = await input({
4235
- message: "Azure DevOps Server base URL (e.g. https://tfs.company.com or https://devops.company.com/tfs):",
4503
+ message: "Azure DevOps Server base URL (e.g. tfs.company.com or devops.company.com/tfs):",
4236
4504
  default: ""
4237
4505
  });
4238
4506
  adoPat = await password({
@@ -4252,7 +4520,7 @@ async function initGlobalConfig(start) {
4252
4520
  const tempConfig = {
4253
4521
  ...loadConfig(),
4254
4522
  ado: {
4255
- baseUrl: adoBaseUrl,
4523
+ baseUrl: normalizeBaseUrl(adoBaseUrl),
4256
4524
  pat: adoPat || void 0,
4257
4525
  fieldAliases: {},
4258
4526
  discoveredFields: [],
@@ -4304,7 +4572,7 @@ async function initGlobalConfig(start) {
4304
4572
  let jenkinsApiToken = "";
4305
4573
  if (useJenkins) {
4306
4574
  jenkinsBaseUrl = await input({
4307
- message: "Jenkins base URL (e.g. https://jenkins.your-company.com):",
4575
+ message: "Jenkins base URL (e.g. jenkins.your-company.com):",
4308
4576
  default: ""
4309
4577
  });
4310
4578
  jenkinsUsername = await input({
@@ -4328,7 +4596,7 @@ async function initGlobalConfig(start) {
4328
4596
  let udeployDefaultEnv = "";
4329
4597
  if (useUdeploy) {
4330
4598
  udeployBaseUrl = await input({
4331
- message: "UDeploy base URL (e.g. https://ucd.company.com:8443):",
4599
+ message: "UDeploy base URL (e.g. ucd.company.com:8443):",
4332
4600
  default: ""
4333
4601
  });
4334
4602
  udeployUsername = await input({
@@ -4360,7 +4628,7 @@ async function initGlobalConfig(start) {
4360
4628
  const tempConfig = {
4361
4629
  ...loadConfig(),
4362
4630
  udeploy: {
4363
- baseUrl: udeployBaseUrl,
4631
+ baseUrl: normalizeBaseUrl(udeployBaseUrl),
4364
4632
  pat: udeployPat || void 0,
4365
4633
  username: udeployUsername || void 0,
4366
4634
  password: udeployPassword || void 0
@@ -4385,7 +4653,7 @@ async function initGlobalConfig(start) {
4385
4653
  let checkmarxPassword = "";
4386
4654
  if (useCheckmarx) {
4387
4655
  checkmarxBaseUrl = await input({
4388
- message: "Checkmarx base URL (e.g. https://cx.company.com):",
4656
+ message: "Checkmarx base URL (e.g. cx.company.com):",
4389
4657
  default: ""
4390
4658
  });
4391
4659
  checkmarxUsername = await input({
@@ -4402,7 +4670,7 @@ async function initGlobalConfig(start) {
4402
4670
  const tempConfig = {
4403
4671
  ...loadConfig(),
4404
4672
  checkmarx: {
4405
- baseUrl: checkmarxBaseUrl,
4673
+ baseUrl: normalizeBaseUrl(checkmarxBaseUrl),
4406
4674
  username: checkmarxUsername,
4407
4675
  password: checkmarxPassword
4408
4676
  }
@@ -4427,7 +4695,7 @@ async function initGlobalConfig(start) {
4427
4695
  let servicenowApiToken = "";
4428
4696
  if (useServiceNow) {
4429
4697
  servicenowBaseUrl = await input({
4430
- message: "ServiceNow instance URL (e.g. https://mycompany.service-now.com):",
4698
+ message: "ServiceNow instance URL (e.g. mycompany.service-now.com):",
4431
4699
  default: ""
4432
4700
  });
4433
4701
  servicenowUsername = await input({
@@ -4456,7 +4724,7 @@ async function initGlobalConfig(start) {
4456
4724
  const tempConfig = {
4457
4725
  ...loadConfig(),
4458
4726
  servicenow: {
4459
- baseUrl: servicenowBaseUrl,
4727
+ baseUrl: normalizeBaseUrl(servicenowBaseUrl),
4460
4728
  username: servicenowUsername,
4461
4729
  password: servicenowPassword || void 0,
4462
4730
  apiToken: servicenowApiToken || void 0
@@ -4471,6 +4739,47 @@ async function initGlobalConfig(start) {
4471
4739
  }
4472
4740
  }
4473
4741
  }
4742
+ process.stderr.write("\n\u2500\u2500 Sonatype IQ Server \u2500\u2500\u2500\u2500\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");
4743
+ const useSonatypeIq = await confirm({
4744
+ message: "Configure Sonatype IQ Server for dependency vulnerability scanning?",
4745
+ default: false
4746
+ });
4747
+ let sonatypeIqBaseUrl = "";
4748
+ let sonatypeIqUserCode = "";
4749
+ let sonatypeIqPasscode = "";
4750
+ if (useSonatypeIq) {
4751
+ process.stderr.write(" User Token credentials are found in your IQ Server profile under User Token.\n");
4752
+ sonatypeIqBaseUrl = await input({
4753
+ message: "Sonatype IQ Server base URL (e.g. iq.your-company.com):",
4754
+ default: ""
4755
+ });
4756
+ sonatypeIqUserCode = await input({
4757
+ message: "User Code (from User Token):",
4758
+ default: ""
4759
+ });
4760
+ sonatypeIqPasscode = await password({
4761
+ message: "Passcode (from User Token):"
4762
+ });
4763
+ if (sonatypeIqBaseUrl && sonatypeIqUserCode && sonatypeIqPasscode) {
4764
+ process.stderr.write("\n Verifying connection...\n");
4765
+ try {
4766
+ const tempConfig = {
4767
+ ...loadConfig(),
4768
+ sonatypeiq: {
4769
+ baseUrl: normalizeBaseUrl(sonatypeIqBaseUrl),
4770
+ userCode: sonatypeIqUserCode,
4771
+ passcode: sonatypeIqPasscode
4772
+ }
4773
+ };
4774
+ const tempHttp = createHttpClient(tempConfig);
4775
+ await tempHttp.sonatypeiq("/api/v2/applications", { params: { limit: 1 } });
4776
+ process.stderr.write(" Connected.\n");
4777
+ } catch (err) {
4778
+ warn(`Could not connect to Sonatype IQ Server: ${err instanceof Error ? err.message : String(err)}`);
4779
+ warn("Config will be saved anyway. Check your URL and credentials and re-run pncli config init or pncli config test.");
4780
+ }
4781
+ }
4782
+ }
4474
4783
  process.stderr.write("\n\u2500\u2500 Contrast IAST \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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");
4475
4784
  const useContrast = await confirm({
4476
4785
  message: "Configure Contrast Security IAST for vulnerability findings?",
@@ -4508,7 +4817,7 @@ async function initGlobalConfig(start) {
4508
4817
  const tempConfig = {
4509
4818
  ...loadConfig(),
4510
4819
  contrast: {
4511
- baseUrl: contrastBaseUrl || void 0,
4820
+ baseUrl: normalizeBaseUrl(contrastBaseUrl) || void 0,
4512
4821
  orgUuid: contrastOrgUuid,
4513
4822
  username: contrastUsername,
4514
4823
  apiKey: contrastApiKey,
@@ -4553,22 +4862,22 @@ async function initGlobalConfig(start) {
4553
4862
  userId: userId || void 0
4554
4863
  },
4555
4864
  jira: {
4556
- baseUrl: jiraBaseUrl || void 0,
4865
+ baseUrl: normalizeBaseUrl(jiraBaseUrl) || void 0,
4557
4866
  apiToken: jiraApiToken || void 0
4558
4867
  },
4559
4868
  bitbucket: {
4560
- baseUrl: bitbucketBaseUrl || void 0,
4869
+ baseUrl: normalizeBaseUrl(bitbucketBaseUrl) || void 0,
4561
4870
  pat: bitbucketPat || void 0
4562
4871
  },
4563
4872
  ...confluenceBaseUrl || confluenceApiToken ? {
4564
4873
  confluence: {
4565
- baseUrl: confluenceBaseUrl || void 0,
4874
+ baseUrl: normalizeBaseUrl(confluenceBaseUrl) || void 0,
4566
4875
  apiToken: confluenceApiToken || void 0
4567
4876
  }
4568
4877
  } : {},
4569
4878
  ...useArtifactory ? {
4570
4879
  artifactory: {
4571
- baseUrl: artifactoryBaseUrl || void 0,
4880
+ baseUrl: normalizeBaseUrl(artifactoryBaseUrl) || void 0,
4572
4881
  token: artifactoryToken || void 0,
4573
4882
  npmRepo: npmRepo || void 0,
4574
4883
  nugetRepo: nugetRepo || void 0,
@@ -4577,7 +4886,7 @@ async function initGlobalConfig(start) {
4577
4886
  } : {},
4578
4887
  ...useSonar ? {
4579
4888
  sonar: {
4580
- baseUrl: sonarBaseUrl || void 0,
4889
+ baseUrl: normalizeBaseUrl(sonarBaseUrl) || void 0,
4581
4890
  token: sonarToken || void 0
4582
4891
  }
4583
4892
  } : {},
@@ -4588,7 +4897,7 @@ async function initGlobalConfig(start) {
4588
4897
  } : {},
4589
4898
  ...useAdo && adoBaseUrl ? {
4590
4899
  ado: {
4591
- baseUrl: adoBaseUrl,
4900
+ baseUrl: normalizeBaseUrl(adoBaseUrl),
4592
4901
  ...adoPat ? { pat: adoPat } : {},
4593
4902
  ...Object.keys(adoFieldAliases).length ? { fieldAliases: adoFieldAliases } : {},
4594
4903
  ...adoDiscoveredFields.length ? { discoveredFields: adoDiscoveredFields } : {},
@@ -4597,14 +4906,14 @@ async function initGlobalConfig(start) {
4597
4906
  } : {},
4598
4907
  ...useJenkins && jenkinsBaseUrl ? {
4599
4908
  jenkins: {
4600
- baseUrl: jenkinsBaseUrl,
4909
+ baseUrl: normalizeBaseUrl(jenkinsBaseUrl),
4601
4910
  ...jenkinsUsername ? { username: jenkinsUsername } : {},
4602
4911
  ...jenkinsApiToken ? { apiToken: jenkinsApiToken } : {}
4603
4912
  }
4604
4913
  } : {},
4605
4914
  ...useUdeploy && udeployBaseUrl ? {
4606
4915
  udeploy: {
4607
- baseUrl: udeployBaseUrl,
4916
+ baseUrl: normalizeBaseUrl(udeployBaseUrl),
4608
4917
  ...udeployPat ? { pat: udeployPat } : {},
4609
4918
  ...udeployUsername ? { username: udeployUsername } : {},
4610
4919
  ...udeployPassword ? { password: udeployPassword } : {}
@@ -4612,14 +4921,14 @@ async function initGlobalConfig(start) {
4612
4921
  } : {},
4613
4922
  ...useCheckmarx && checkmarxBaseUrl ? {
4614
4923
  checkmarx: {
4615
- baseUrl: checkmarxBaseUrl,
4924
+ baseUrl: normalizeBaseUrl(checkmarxBaseUrl),
4616
4925
  username: checkmarxUsername || void 0,
4617
4926
  password: checkmarxPassword || void 0
4618
4927
  }
4619
4928
  } : {},
4620
4929
  ...useServiceNow && servicenowBaseUrl ? {
4621
4930
  servicenow: {
4622
- baseUrl: servicenowBaseUrl,
4931
+ baseUrl: normalizeBaseUrl(servicenowBaseUrl),
4623
4932
  username: servicenowUsername || void 0,
4624
4933
  ...servicenowApiToken ? { apiToken: servicenowApiToken } : {},
4625
4934
  ...servicenowPassword ? { password: servicenowPassword } : {}
@@ -4627,13 +4936,20 @@ async function initGlobalConfig(start) {
4627
4936
  } : {},
4628
4937
  ...useContrast && contrastOrgUuid ? {
4629
4938
  contrast: {
4630
- ...contrastBaseUrl ? { baseUrl: contrastBaseUrl } : {},
4939
+ ...contrastBaseUrl ? { baseUrl: normalizeBaseUrl(contrastBaseUrl) } : {},
4631
4940
  orgUuid: contrastOrgUuid,
4632
4941
  username: contrastUsername || void 0,
4633
4942
  apiKey: contrastApiKey || void 0,
4634
4943
  serviceKey: contrastServiceKey || void 0
4635
4944
  }
4636
4945
  } : {},
4946
+ ...useSonatypeIq && sonatypeIqBaseUrl ? {
4947
+ sonatypeiq: {
4948
+ baseUrl: normalizeBaseUrl(sonatypeIqBaseUrl),
4949
+ userCode: sonatypeIqUserCode || void 0,
4950
+ passcode: sonatypeIqPasscode || void 0
4951
+ }
4952
+ } : {},
4637
4953
  defaults: {
4638
4954
  jira: {
4639
4955
  project: jiraProject || void 0
@@ -5538,11 +5854,11 @@ function registerAdoRepoCommands(ado) {
5538
5854
  const start = Date.now();
5539
5855
  try {
5540
5856
  const globalOpts = ado.optsWithGlobals();
5541
- const { loadConfig: lc } = await import("./config-3VKBM32K.js");
5857
+ const { loadConfig: lc } = await import("./config-NCI2BTQG.js");
5542
5858
  const config = lc({ configPath: globalOpts.config });
5543
5859
  const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
5544
5860
  const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
5545
- const { createHttpClient: createHttpClient2 } = await import("./http-GVHZQCEY.js");
5861
+ const { createHttpClient: createHttpClient2 } = await import("./http-L2P23H2D.js");
5546
5862
  const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
5547
5863
  const coreClient = new AdoCoreClient2(http);
5548
5864
  const me = await coreClient.getConnectionData(collection);
@@ -6154,7 +6470,7 @@ var UdeployClient = class {
6154
6470
  };
6155
6471
 
6156
6472
  // src/services/udeploy/commands.ts
6157
- var POLL_INTERVAL_MS = 5e3;
6473
+ var POLL_INTERVAL_MS2 = 5e3;
6158
6474
  var DEFAULT_TIMEOUT_MS = 10 * 60 * 1e3;
6159
6475
  function getClient8(program2) {
6160
6476
  const opts = program2.optsWithGlobals();
@@ -6265,7 +6581,7 @@ function registerUdeployCommands(program2) {
6265
6581
  if (elapsed >= timeoutMs) {
6266
6582
  throw new PncliError(`Timed out after ${timeoutMs}ms waiting for request ${requestId}`, 1);
6267
6583
  }
6268
- await sleep(POLL_INTERVAL_MS);
6584
+ await sleep(POLL_INTERVAL_MS2);
6269
6585
  const status = await client.getRequestStatus(requestId);
6270
6586
  if (status.status === "CLOSED") {
6271
6587
  const outcome = { requestId, status: status.status, result: status.result, elapsed_ms: Date.now() - start };
@@ -6584,19 +6900,87 @@ function registerContrastCommands(program2) {
6584
6900
  });
6585
6901
  }
6586
6902
 
6903
+ // src/services/sonatypeiq/commands.ts
6904
+ function getHttpClient(program2) {
6905
+ const opts = program2.optsWithGlobals();
6906
+ const config = loadConfig({ configPath: opts.config });
6907
+ if (!config.sonatypeiq.baseUrl) throw new PncliError("Sonatype IQ Server baseUrl not configured. Run: pncli config init");
6908
+ if (!config.sonatypeiq.userCode || !config.sonatypeiq.passcode) throw new PncliError("Sonatype IQ Server credentials not configured. Run: pncli config init");
6909
+ const http = createHttpClient(config, Boolean(opts.dryRun));
6910
+ return http;
6911
+ }
6912
+ function registerSonatypeIqCommands(program2) {
6913
+ const sonatypeiq = program2.command("sonatypeiq").description("Sonatype IQ Server operations");
6914
+ const applications = sonatypeiq.command("applications").description("Application operations");
6915
+ applications.command("list").description("List all applications in Sonatype IQ Server").option("--organization-id <id>", "Filter by organization ID").action(async (opts) => {
6916
+ const startTime = Date.now();
6917
+ try {
6918
+ const http = getHttpClient(program2);
6919
+ const params = {};
6920
+ if (opts.organizationId) params["organizationId"] = opts.organizationId;
6921
+ const data = await http.sonatypeiq("/api/v2/applications", { params });
6922
+ success(data, "sonatypeiq", "applications list", startTime);
6923
+ } catch (err) {
6924
+ fail(err, "sonatypeiq", "applications list", startTime);
6925
+ }
6926
+ });
6927
+ applications.command("get").description("Get an application by its public ID").requiredOption("--public-id <id>", "Application public ID").action(async (opts) => {
6928
+ const startTime = Date.now();
6929
+ try {
6930
+ const http = getHttpClient(program2);
6931
+ const data = await http.sonatypeiq("/api/v2/applications", {
6932
+ params: { publicId: opts.publicId }
6933
+ });
6934
+ success(data, "sonatypeiq", "applications get", startTime);
6935
+ } catch (err) {
6936
+ fail(err, "sonatypeiq", "applications get", startTime);
6937
+ }
6938
+ });
6939
+ const organizations = sonatypeiq.command("organizations").description("Organization operations");
6940
+ organizations.command("list").description("List all organizations in Sonatype IQ Server").action(async () => {
6941
+ const startTime = Date.now();
6942
+ try {
6943
+ const http = getHttpClient(program2);
6944
+ const data = await http.sonatypeiq("/api/v2/organizations");
6945
+ success(data, "sonatypeiq", "organizations list", startTime);
6946
+ } catch (err) {
6947
+ fail(err, "sonatypeiq", "organizations list", startTime);
6948
+ }
6949
+ });
6950
+ const policies = sonatypeiq.command("policies").description("Policy operations");
6951
+ policies.command("list").description("List all policies").option("--organization-id <id>", "Filter by organization ID").action(async (opts) => {
6952
+ const startTime = Date.now();
6953
+ try {
6954
+ const http = getHttpClient(program2);
6955
+ const path6 = opts.organizationId ? `/api/v2/policies/organization/${encodeURIComponent(opts.organizationId)}` : "/api/v2/policies";
6956
+ const data = await http.sonatypeiq(path6);
6957
+ success(data, "sonatypeiq", "policies list", startTime);
6958
+ } catch (err) {
6959
+ fail(err, "sonatypeiq", "policies list", startTime);
6960
+ }
6961
+ });
6962
+ }
6963
+
6587
6964
  // src/services/skills/commands.ts
6588
6965
  import fs3 from "fs";
6589
6966
  import path5 from "path";
6590
6967
  import os from "os";
6591
- var GITHUB_API = "https://api.github.com/repos/kolatts/pncli/contents/skills";
6592
- var RAW_BASE = "https://raw.githubusercontent.com/kolatts/pncli/main/skills";
6968
+ import { fileURLToPath } from "url";
6593
6969
  var AGENT_PATHS = {
6594
6970
  "github-copilot": { project: ".agents/skills", user: path5.join(os.homedir(), ".copilot/skills") },
6595
6971
  "claude-code": { project: ".claude/skills", user: path5.join(os.homedir(), ".claude/skills") }
6596
6972
  };
6973
+ function getBundledSkillsDir() {
6974
+ try {
6975
+ const thisFile = fileURLToPath(import.meta.url);
6976
+ return path5.resolve(path5.dirname(thisFile), "..", "skills");
6977
+ } catch {
6978
+ return "";
6979
+ }
6980
+ }
6597
6981
  function registerSkillsCommands(program2) {
6598
6982
  const skills = program2.command("skills").description("Manage pncli Claude Code skills");
6599
- skills.command("install").description("Download latest pncli skills into the current repo").option("--agent <agent>", "Target agent host: github-copilot | claude-code", "github-copilot").option("--scope <scope>", "Installation scope: project | user", "project").option("--target <dir>", "Override install directory (ignores --agent and --scope)").action(async (opts) => {
6983
+ skills.command("install").description("Install pncli skills into the current repo").option("--agent <agent>", "Target agent host: github-copilot | claude-code", "github-copilot").option("--scope <scope>", "Installation scope: project | user", "project").option("--target <dir>", "Override install directory (ignores --agent and --scope)").action((opts) => {
6600
6984
  const start = Date.now();
6601
6985
  try {
6602
6986
  let targetDir;
@@ -6610,19 +6994,22 @@ function registerSkillsCommands(program2) {
6610
6994
  const scopePath = opts.scope === "user" ? agentConfig.user : agentConfig.project;
6611
6995
  targetDir = path5.resolve(scopePath);
6612
6996
  }
6613
- warn("Fetching skill list from GitHub...");
6614
- const listRes = await fetch(GITHUB_API, {
6615
- headers: { "Accept": "application/vnd.github.v3+json", "User-Agent": "pncli" }
6616
- });
6617
- if (!listRes.ok) {
6618
- throw new Error(`GitHub API returned ${listRes.status}: ${await listRes.text()}`);
6997
+ const resolvedTarget = path5.resolve(targetDir);
6998
+ const bundledDir = getBundledSkillsDir();
6999
+ let skillDirs = [];
7000
+ if (bundledDir !== "") {
7001
+ try {
7002
+ skillDirs = fs3.readdirSync(bundledDir).filter(
7003
+ (name) => fs3.existsSync(path5.join(bundledDir, name, "SKILL.md"))
7004
+ );
7005
+ } catch {
7006
+ }
6619
7007
  }
6620
- const entries = await listRes.json();
6621
- const skillDirs = entries.filter((e) => e.type === "dir").map((e) => e.name);
6622
7008
  if (skillDirs.length === 0) {
6623
- throw new Error("No skills found in the pncli repository");
7009
+ throw new Error("No bundled skills found. Reinstall pncli to get the latest version: npm install -g @kolatts/pncli");
6624
7010
  }
6625
- const resolvedTarget = path5.resolve(targetDir);
7011
+ const installed = [];
7012
+ const failed = [];
6626
7013
  for (const skillName of skillDirs) {
6627
7014
  const existingDir = path5.resolve(targetDir, skillName);
6628
7015
  if (!existingDir.startsWith(resolvedTarget + path5.sep)) continue;
@@ -6630,25 +7017,15 @@ function registerSkillsCommands(program2) {
6630
7017
  fs3.rmSync(existingDir, { recursive: true, force: true });
6631
7018
  }
6632
7019
  }
6633
- warn(`Found ${skillDirs.length} skills. Downloading to ${targetDir}...`);
6634
- const installed = [];
6635
- const failed = [];
7020
+ warn(`Installing ${skillDirs.length} bundled skill(s) to ${targetDir}...`);
6636
7021
  for (const skillName of skillDirs) {
6637
7022
  const skillDir = path5.resolve(targetDir, skillName);
6638
7023
  if (!skillDir.startsWith(resolvedTarget + path5.sep)) {
6639
7024
  failed.push(skillName);
6640
7025
  continue;
6641
7026
  }
6642
- const skillUrl = `${RAW_BASE}/${skillName}/SKILL.md`;
6643
7027
  try {
6644
- const res = await fetch(skillUrl, {
6645
- headers: { "User-Agent": "pncli" }
6646
- });
6647
- if (!res.ok) {
6648
- failed.push(skillName);
6649
- continue;
6650
- }
6651
- const content = await res.text();
7028
+ const content = fs3.readFileSync(path5.join(bundledDir, skillName, "SKILL.md"), "utf8");
6652
7029
  fs3.mkdirSync(skillDir, { recursive: true });
6653
7030
  fs3.writeFileSync(path5.join(skillDir, "SKILL.md"), content, "utf8");
6654
7031
  installed.push(skillName);
@@ -6658,7 +7035,7 @@ function registerSkillsCommands(program2) {
6658
7035
  }
6659
7036
  warn(`Installed ${installed.length} skill(s) to ${targetDir}`);
6660
7037
  if (failed.length > 0) {
6661
- warn(`Failed to download: ${failed.join(", ")}`);
7038
+ warn(`Failed to install: ${failed.join(", ")}`);
6662
7039
  }
6663
7040
  success({
6664
7041
  installed,
@@ -6666,7 +7043,8 @@ function registerSkillsCommands(program2) {
6666
7043
  target: targetDir,
6667
7044
  total: installed.length,
6668
7045
  agent: opts.target ? "custom" : opts.agent,
6669
- scope: opts.target ? "custom" : opts.scope
7046
+ scope: opts.target ? "custom" : opts.scope,
7047
+ source: "bundled"
6670
7048
  }, "skills", "install", start);
6671
7049
  } catch (err) {
6672
7050
  fail(err, "skills", "install", start);
@@ -6772,6 +7150,7 @@ registerUdeployCommands(program);
6772
7150
  registerCheckmarxCommands(program);
6773
7151
  registerServiceNowCommands(program);
6774
7152
  registerContrastCommands(program);
7153
+ registerSonatypeIqCommands(program);
6775
7154
  registerSkillsCommands(program);
6776
7155
  program.addHelpText("after", `
6777
7156
  Services:
@@ -6789,6 +7168,7 @@ Services:
6789
7168
  checkmarx Checkmarx CxSAST (projects, scans, scan statistics)
6790
7169
  servicenow ServiceNow (change requests)
6791
7170
  contrast Contrast IAST (applications, vulnerability findings)
7171
+ sonatypeiq Sonatype IQ Server (applications, organizations, policies)
6792
7172
  config Manage pncli configuration
6793
7173
  skills Download and manage Claude Code skills
6794
7174
  `);