@kolatts/pncli 1.9.0 → 1.10.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
@@ -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) {
@@ -4471,6 +4734,47 @@ async function initGlobalConfig(start) {
4471
4734
  }
4472
4735
  }
4473
4736
  }
4737
+ 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");
4738
+ const useSonatypeIq = await confirm({
4739
+ message: "Configure Sonatype IQ Server for dependency vulnerability scanning?",
4740
+ default: false
4741
+ });
4742
+ let sonatypeIqBaseUrl = "";
4743
+ let sonatypeIqUserCode = "";
4744
+ let sonatypeIqPasscode = "";
4745
+ if (useSonatypeIq) {
4746
+ process.stderr.write(" User Token credentials are found in your IQ Server profile under User Token.\n");
4747
+ sonatypeIqBaseUrl = await input({
4748
+ message: "Sonatype IQ Server base URL (e.g. https://iq.your-company.com):",
4749
+ default: ""
4750
+ });
4751
+ sonatypeIqUserCode = await input({
4752
+ message: "User Code (from User Token):",
4753
+ default: ""
4754
+ });
4755
+ sonatypeIqPasscode = await password({
4756
+ message: "Passcode (from User Token):"
4757
+ });
4758
+ if (sonatypeIqBaseUrl && sonatypeIqUserCode && sonatypeIqPasscode) {
4759
+ process.stderr.write("\n Verifying connection...\n");
4760
+ try {
4761
+ const tempConfig = {
4762
+ ...loadConfig(),
4763
+ sonatypeiq: {
4764
+ baseUrl: sonatypeIqBaseUrl,
4765
+ userCode: sonatypeIqUserCode,
4766
+ passcode: sonatypeIqPasscode
4767
+ }
4768
+ };
4769
+ const tempHttp = createHttpClient(tempConfig);
4770
+ await tempHttp.sonatypeiq("/api/v2/applications", { params: { limit: 1 } });
4771
+ process.stderr.write(" Connected.\n");
4772
+ } catch (err) {
4773
+ warn(`Could not connect to Sonatype IQ Server: ${err instanceof Error ? err.message : String(err)}`);
4774
+ warn("Config will be saved anyway. Check your URL and credentials and re-run pncli config init or pncli config test.");
4775
+ }
4776
+ }
4777
+ }
4474
4778
  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
4779
  const useContrast = await confirm({
4476
4780
  message: "Configure Contrast Security IAST for vulnerability findings?",
@@ -4634,6 +4938,13 @@ async function initGlobalConfig(start) {
4634
4938
  serviceKey: contrastServiceKey || void 0
4635
4939
  }
4636
4940
  } : {},
4941
+ ...useSonatypeIq && sonatypeIqBaseUrl ? {
4942
+ sonatypeiq: {
4943
+ baseUrl: sonatypeIqBaseUrl,
4944
+ userCode: sonatypeIqUserCode || void 0,
4945
+ passcode: sonatypeIqPasscode || void 0
4946
+ }
4947
+ } : {},
4637
4948
  defaults: {
4638
4949
  jira: {
4639
4950
  project: jiraProject || void 0
@@ -5538,11 +5849,11 @@ function registerAdoRepoCommands(ado) {
5538
5849
  const start = Date.now();
5539
5850
  try {
5540
5851
  const globalOpts = ado.optsWithGlobals();
5541
- const { loadConfig: lc } = await import("./config-3VKBM32K.js");
5852
+ const { loadConfig: lc } = await import("./config-NCI2BTQG.js");
5542
5853
  const config = lc({ configPath: globalOpts.config });
5543
5854
  const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
5544
5855
  const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
5545
- const { createHttpClient: createHttpClient2 } = await import("./http-GVHZQCEY.js");
5856
+ const { createHttpClient: createHttpClient2 } = await import("./http-L2P23H2D.js");
5546
5857
  const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
5547
5858
  const coreClient = new AdoCoreClient2(http);
5548
5859
  const me = await coreClient.getConnectionData(collection);
@@ -6154,7 +6465,7 @@ var UdeployClient = class {
6154
6465
  };
6155
6466
 
6156
6467
  // src/services/udeploy/commands.ts
6157
- var POLL_INTERVAL_MS = 5e3;
6468
+ var POLL_INTERVAL_MS2 = 5e3;
6158
6469
  var DEFAULT_TIMEOUT_MS = 10 * 60 * 1e3;
6159
6470
  function getClient8(program2) {
6160
6471
  const opts = program2.optsWithGlobals();
@@ -6265,7 +6576,7 @@ function registerUdeployCommands(program2) {
6265
6576
  if (elapsed >= timeoutMs) {
6266
6577
  throw new PncliError(`Timed out after ${timeoutMs}ms waiting for request ${requestId}`, 1);
6267
6578
  }
6268
- await sleep(POLL_INTERVAL_MS);
6579
+ await sleep(POLL_INTERVAL_MS2);
6269
6580
  const status = await client.getRequestStatus(requestId);
6270
6581
  if (status.status === "CLOSED") {
6271
6582
  const outcome = { requestId, status: status.status, result: status.result, elapsed_ms: Date.now() - start };
@@ -6584,19 +6895,87 @@ function registerContrastCommands(program2) {
6584
6895
  });
6585
6896
  }
6586
6897
 
6898
+ // src/services/sonatypeiq/commands.ts
6899
+ function getHttpClient(program2) {
6900
+ const opts = program2.optsWithGlobals();
6901
+ const config = loadConfig({ configPath: opts.config });
6902
+ if (!config.sonatypeiq.baseUrl) throw new PncliError("Sonatype IQ Server baseUrl not configured. Run: pncli config init");
6903
+ if (!config.sonatypeiq.userCode || !config.sonatypeiq.passcode) throw new PncliError("Sonatype IQ Server credentials not configured. Run: pncli config init");
6904
+ const http = createHttpClient(config, Boolean(opts.dryRun));
6905
+ return http;
6906
+ }
6907
+ function registerSonatypeIqCommands(program2) {
6908
+ const sonatypeiq = program2.command("sonatypeiq").description("Sonatype IQ Server operations");
6909
+ const applications = sonatypeiq.command("applications").description("Application operations");
6910
+ applications.command("list").description("List all applications in Sonatype IQ Server").option("--organization-id <id>", "Filter by organization ID").action(async (opts) => {
6911
+ const startTime = Date.now();
6912
+ try {
6913
+ const http = getHttpClient(program2);
6914
+ const params = {};
6915
+ if (opts.organizationId) params["organizationId"] = opts.organizationId;
6916
+ const data = await http.sonatypeiq("/api/v2/applications", { params });
6917
+ success(data, "sonatypeiq", "applications list", startTime);
6918
+ } catch (err) {
6919
+ fail(err, "sonatypeiq", "applications list", startTime);
6920
+ }
6921
+ });
6922
+ applications.command("get").description("Get an application by its public ID").requiredOption("--public-id <id>", "Application public ID").action(async (opts) => {
6923
+ const startTime = Date.now();
6924
+ try {
6925
+ const http = getHttpClient(program2);
6926
+ const data = await http.sonatypeiq("/api/v2/applications", {
6927
+ params: { publicId: opts.publicId }
6928
+ });
6929
+ success(data, "sonatypeiq", "applications get", startTime);
6930
+ } catch (err) {
6931
+ fail(err, "sonatypeiq", "applications get", startTime);
6932
+ }
6933
+ });
6934
+ const organizations = sonatypeiq.command("organizations").description("Organization operations");
6935
+ organizations.command("list").description("List all organizations in Sonatype IQ Server").action(async () => {
6936
+ const startTime = Date.now();
6937
+ try {
6938
+ const http = getHttpClient(program2);
6939
+ const data = await http.sonatypeiq("/api/v2/organizations");
6940
+ success(data, "sonatypeiq", "organizations list", startTime);
6941
+ } catch (err) {
6942
+ fail(err, "sonatypeiq", "organizations list", startTime);
6943
+ }
6944
+ });
6945
+ const policies = sonatypeiq.command("policies").description("Policy operations");
6946
+ policies.command("list").description("List all policies").option("--organization-id <id>", "Filter by organization ID").action(async (opts) => {
6947
+ const startTime = Date.now();
6948
+ try {
6949
+ const http = getHttpClient(program2);
6950
+ const path6 = opts.organizationId ? `/api/v2/policies/organization/${encodeURIComponent(opts.organizationId)}` : "/api/v2/policies";
6951
+ const data = await http.sonatypeiq(path6);
6952
+ success(data, "sonatypeiq", "policies list", startTime);
6953
+ } catch (err) {
6954
+ fail(err, "sonatypeiq", "policies list", startTime);
6955
+ }
6956
+ });
6957
+ }
6958
+
6587
6959
  // src/services/skills/commands.ts
6588
6960
  import fs3 from "fs";
6589
6961
  import path5 from "path";
6590
6962
  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";
6963
+ import { fileURLToPath } from "url";
6593
6964
  var AGENT_PATHS = {
6594
6965
  "github-copilot": { project: ".agents/skills", user: path5.join(os.homedir(), ".copilot/skills") },
6595
6966
  "claude-code": { project: ".claude/skills", user: path5.join(os.homedir(), ".claude/skills") }
6596
6967
  };
6968
+ function getBundledSkillsDir() {
6969
+ try {
6970
+ const thisFile = fileURLToPath(import.meta.url);
6971
+ return path5.resolve(path5.dirname(thisFile), "..", "skills");
6972
+ } catch {
6973
+ return "";
6974
+ }
6975
+ }
6597
6976
  function registerSkillsCommands(program2) {
6598
6977
  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) => {
6978
+ 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
6979
  const start = Date.now();
6601
6980
  try {
6602
6981
  let targetDir;
@@ -6610,19 +6989,22 @@ function registerSkillsCommands(program2) {
6610
6989
  const scopePath = opts.scope === "user" ? agentConfig.user : agentConfig.project;
6611
6990
  targetDir = path5.resolve(scopePath);
6612
6991
  }
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()}`);
6992
+ const resolvedTarget = path5.resolve(targetDir);
6993
+ const bundledDir = getBundledSkillsDir();
6994
+ let skillDirs = [];
6995
+ if (bundledDir !== "") {
6996
+ try {
6997
+ skillDirs = fs3.readdirSync(bundledDir).filter(
6998
+ (name) => fs3.existsSync(path5.join(bundledDir, name, "SKILL.md"))
6999
+ );
7000
+ } catch {
7001
+ }
6619
7002
  }
6620
- const entries = await listRes.json();
6621
- const skillDirs = entries.filter((e) => e.type === "dir").map((e) => e.name);
6622
7003
  if (skillDirs.length === 0) {
6623
- throw new Error("No skills found in the pncli repository");
7004
+ throw new Error("No bundled skills found. Reinstall pncli to get the latest version: npm install -g @kolatts/pncli");
6624
7005
  }
6625
- const resolvedTarget = path5.resolve(targetDir);
7006
+ const installed = [];
7007
+ const failed = [];
6626
7008
  for (const skillName of skillDirs) {
6627
7009
  const existingDir = path5.resolve(targetDir, skillName);
6628
7010
  if (!existingDir.startsWith(resolvedTarget + path5.sep)) continue;
@@ -6630,25 +7012,15 @@ function registerSkillsCommands(program2) {
6630
7012
  fs3.rmSync(existingDir, { recursive: true, force: true });
6631
7013
  }
6632
7014
  }
6633
- warn(`Found ${skillDirs.length} skills. Downloading to ${targetDir}...`);
6634
- const installed = [];
6635
- const failed = [];
7015
+ warn(`Installing ${skillDirs.length} bundled skill(s) to ${targetDir}...`);
6636
7016
  for (const skillName of skillDirs) {
6637
7017
  const skillDir = path5.resolve(targetDir, skillName);
6638
7018
  if (!skillDir.startsWith(resolvedTarget + path5.sep)) {
6639
7019
  failed.push(skillName);
6640
7020
  continue;
6641
7021
  }
6642
- const skillUrl = `${RAW_BASE}/${skillName}/SKILL.md`;
6643
7022
  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();
7023
+ const content = fs3.readFileSync(path5.join(bundledDir, skillName, "SKILL.md"), "utf8");
6652
7024
  fs3.mkdirSync(skillDir, { recursive: true });
6653
7025
  fs3.writeFileSync(path5.join(skillDir, "SKILL.md"), content, "utf8");
6654
7026
  installed.push(skillName);
@@ -6658,7 +7030,7 @@ function registerSkillsCommands(program2) {
6658
7030
  }
6659
7031
  warn(`Installed ${installed.length} skill(s) to ${targetDir}`);
6660
7032
  if (failed.length > 0) {
6661
- warn(`Failed to download: ${failed.join(", ")}`);
7033
+ warn(`Failed to install: ${failed.join(", ")}`);
6662
7034
  }
6663
7035
  success({
6664
7036
  installed,
@@ -6666,7 +7038,8 @@ function registerSkillsCommands(program2) {
6666
7038
  target: targetDir,
6667
7039
  total: installed.length,
6668
7040
  agent: opts.target ? "custom" : opts.agent,
6669
- scope: opts.target ? "custom" : opts.scope
7041
+ scope: opts.target ? "custom" : opts.scope,
7042
+ source: "bundled"
6670
7043
  }, "skills", "install", start);
6671
7044
  } catch (err) {
6672
7045
  fail(err, "skills", "install", start);
@@ -6772,6 +7145,7 @@ registerUdeployCommands(program);
6772
7145
  registerCheckmarxCommands(program);
6773
7146
  registerServiceNowCommands(program);
6774
7147
  registerContrastCommands(program);
7148
+ registerSonatypeIqCommands(program);
6775
7149
  registerSkillsCommands(program);
6776
7150
  program.addHelpText("after", `
6777
7151
  Services:
@@ -6789,6 +7163,7 @@ Services:
6789
7163
  checkmarx Checkmarx CxSAST (projects, scans, scan statistics)
6790
7164
  servicenow ServiceNow (change requests)
6791
7165
  contrast Contrast IAST (applications, vulnerability findings)
7166
+ sonatypeiq Sonatype IQ Server (applications, organizations, policies)
6792
7167
  config Manage pncli configuration
6793
7168
  skills Download and manage Claude Code skills
6794
7169
  `);