@kolatts/pncli 1.7.0 → 1.9.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-VEHCNGUP.js";
10
+ } from "./chunk-YR6GFJMV.js";
11
11
  import {
12
12
  ExitCode,
13
13
  PncliError,
@@ -18,13 +18,13 @@ import {
18
18
  setGlobalUser,
19
19
  success,
20
20
  warn
21
- } from "./chunk-HWXXNMGE.js";
21
+ } from "./chunk-QQZJQ5DP.js";
22
22
  import {
23
23
  AdoCoreClient
24
- } from "./chunk-KT3XINLJ.js";
24
+ } from "./chunk-VDL32PSG.js";
25
25
 
26
26
  // src/cli.ts
27
- import { Command } from "commander";
27
+ import { Command as Command2 } from "commander";
28
28
  import { createRequire } from "module";
29
29
 
30
30
  // src/services/git/client.ts
@@ -771,7 +771,7 @@ function registerJiraCommands(program2) {
771
771
  fail(err, "jira", "get-issue", start);
772
772
  }
773
773
  });
774
- jira.command("create-issue").description("Create a Jira issue").option("--project <key>", "Project key").option("--type <type>", "Issue type (Bug, Story, Task, ...)").requiredOption("--summary <text>", "Issue summary").option("--description <text>", "Issue description").option("--priority <name>", "Priority name").option("--assignee <accountId>", "Assignee account ID").option("--labels <labels>", "Comma-separated labels").option("--field <Name=value>", "Custom field value (repeatable)", (val, acc) => [...acc, val], []).action(async (opts) => {
774
+ jira.command("create-issue").description("Create a Jira issue").option("--project <key>", "Project key").option("--type <type>", "Issue type (Bug, Story, Task, ...)").requiredOption("--summary <text>", "Issue summary").option("--description <text>", "Issue description").option("--priority <name>", "Priority name").option("--assignee <accountId>", "Assignee account ID").option("--labels <labels>", "Comma-separated labels").option("--parent <key>", 'Parent issue key \u2014 creates an "is child of" link after creation').option("--field <Name=value>", "Custom field value (repeatable)", (val, acc) => [...acc, val], []).action(async (opts) => {
775
775
  const start = Date.now();
776
776
  try {
777
777
  const { client, fieldMap } = getClientAndFields(program2);
@@ -784,6 +784,14 @@ function registerJiraCommands(program2) {
784
784
  const customFieldValues = parseFieldArgs(opts.field, fieldMap);
785
785
  const data = await client.createIssue({ project, issueType, summary: opts.summary, description: opts.description, priority, assignee: opts.assignee, labels, customFieldValues });
786
786
  success(data, "jira", "create-issue", start);
787
+ if (opts.parent) {
788
+ try {
789
+ await client.linkIssue({ key: data.key, linkType: "is child of", target: opts.parent });
790
+ } catch (linkErr) {
791
+ warn(`Created ${data.key} but failed to link to parent ${opts.parent}: ${linkErr instanceof Error ? linkErr.message : linkErr}`);
792
+ }
793
+ }
794
+ return;
787
795
  } catch (err) {
788
796
  fail(err, "jira", "create-issue", start);
789
797
  }
@@ -847,7 +855,7 @@ function registerJiraCommands(program2) {
847
855
  fail(err, "jira", "list-comments", start);
848
856
  }
849
857
  });
850
- jira.command("search").description("Search Jira issues with JQL").requiredOption("--jql <query>", "JQL query string").option("--max-results <n>", "Maximum number of results").action(async (opts) => {
858
+ jira.command("search").description("Search Jira issues with JQL (consider --output-file for large results)").requiredOption("--jql <query>", "JQL query string").option("--max-results <n>", "Maximum number of results").action(async (opts) => {
851
859
  const start = Date.now();
852
860
  try {
853
861
  const { client, fieldMap, customFields } = getClientAndFields(program2);
@@ -883,7 +891,7 @@ function registerJiraCommands(program2) {
883
891
  fail(err, "jira", "link-issue", start);
884
892
  }
885
893
  });
886
- jira.command("fields").description("List custom fields (configured or discovered from Jira API)").option("--discover", "Fetch field metadata from Jira API").option("--custom-only", "Show only custom fields (requires --discover)").action(async (opts) => {
894
+ jira.command("fields").description("List custom fields (configured or discovered from Jira API) (consider --output-file for large results)").option("--discover", "Fetch field metadata from Jira API").option("--custom-only", "Show only custom fields (requires --discover)").action(async (opts) => {
887
895
  const start = Date.now();
888
896
  try {
889
897
  if (opts.discover) {
@@ -936,7 +944,7 @@ function registerBitbucketCommands(program2) {
936
944
  bb.command("list-prs").description("List pull requests").option("--state <state>", "PR state: OPEN|MERGED|DECLINED|ALL", "OPEN").option("--author <username>", "Filter by author username").option("--reviewer <username>", "Filter by reviewer username").action(async (opts) => {
937
945
  const start = Date.now();
938
946
  try {
939
- const { client, project, repo } = getClient2(program2);
947
+ const { client, project, repo } = getClient2(bb);
940
948
  const data = await client.listPRs({ project, repo, state: opts.state, author: opts.author, reviewer: opts.reviewer });
941
949
  success(data, "bitbucket", "list-prs", start);
942
950
  } catch (err) {
@@ -946,7 +954,7 @@ function registerBitbucketCommands(program2) {
946
954
  bb.command("get-pr").description("Get a pull request by ID").requiredOption("--id <pr-id>", "Pull request ID").action(async (opts) => {
947
955
  const start = Date.now();
948
956
  try {
949
- const { client, project, repo } = getClient2(program2);
957
+ const { client, project, repo } = getClient2(bb);
950
958
  const data = await client.getPR(project, repo, parseInt(opts.id, 10));
951
959
  success(data, "bitbucket", "get-pr", start);
952
960
  } catch (err) {
@@ -956,8 +964,8 @@ function registerBitbucketCommands(program2) {
956
964
  bb.command("create-pr").description("Create a pull request").requiredOption("--title <title>", "PR title").requiredOption("--source <branch>", "Source branch").option("--target <branch>", "Target branch (defaults to config)").option("--description <desc>", "PR description").option("--reviewers <users>", "Comma-separated reviewer usernames").action(async (opts) => {
957
965
  const start = Date.now();
958
966
  try {
959
- const { client, project, repo } = getClient2(program2);
960
- const config = loadConfig({ configPath: program2.optsWithGlobals().config });
967
+ const { client, project, repo } = getClient2(bb);
968
+ const config = loadConfig({ configPath: bb.optsWithGlobals().config });
961
969
  const target = opts.target ?? config.defaults.bitbucket?.targetBranch ?? "main";
962
970
  const reviewers = opts.reviewers ? opts.reviewers.split(",").map((s) => s.trim()) : [];
963
971
  const data = await client.createPR({ project, repo, title: opts.title, source: opts.source, target, description: opts.description, reviewers });
@@ -969,7 +977,7 @@ function registerBitbucketCommands(program2) {
969
977
  bb.command("update-pr").description("Update a pull request").requiredOption("--id <pr-id>", "Pull request ID").option("--title <title>", "New title").option("--description <desc>", "New description").option("--reviewers <users>", "Comma-separated reviewer usernames").action(async (opts) => {
970
978
  const start = Date.now();
971
979
  try {
972
- const { client, project, repo } = getClient2(program2);
980
+ const { client, project, repo } = getClient2(bb);
973
981
  const prId = parseInt(opts.id, 10);
974
982
  const pr = await client.getPR(project, repo, prId);
975
983
  const reviewers = opts.reviewers ? opts.reviewers.split(",").map((s) => s.trim()) : void 0;
@@ -982,7 +990,7 @@ function registerBitbucketCommands(program2) {
982
990
  bb.command("merge-pr").description("Merge a pull request").requiredOption("--id <pr-id>", "Pull request ID").option("--strategy <strategy>", "Merge strategy: merge|squash|ff").option("--delete-branch", "Delete source branch after merge").action(async (opts) => {
983
991
  const start = Date.now();
984
992
  try {
985
- const { client, project, repo } = getClient2(program2);
993
+ const { client, project, repo } = getClient2(bb);
986
994
  const prId = parseInt(opts.id, 10);
987
995
  const pr = await client.getPR(project, repo, prId);
988
996
  const data = await client.mergePR({ project, repo, id: prId, version: pr.version, strategy: opts.strategy, deleteBranch: opts.deleteBranch });
@@ -994,7 +1002,7 @@ function registerBitbucketCommands(program2) {
994
1002
  bb.command("decline-pr").description("Decline a pull request").requiredOption("--id <pr-id>", "Pull request ID").action(async (opts) => {
995
1003
  const start = Date.now();
996
1004
  try {
997
- const { client, project, repo } = getClient2(program2);
1005
+ const { client, project, repo } = getClient2(bb);
998
1006
  const prId = parseInt(opts.id, 10);
999
1007
  const pr = await client.getPR(project, repo, prId);
1000
1008
  const data = await client.declinePR(project, repo, prId, pr.version);
@@ -1006,7 +1014,7 @@ function registerBitbucketCommands(program2) {
1006
1014
  bb.command("list-comments").description("List comments on a pull request (includes threaded replies by default)").requiredOption("--pr <pr-id>", "Pull request ID").option("--no-with-replies", "Exclude replies; return top-level comments only").option("--inline-only", "Return only inline file comments").option("--general-only", "Return only general (non-inline) PR comments").action(async (opts) => {
1007
1015
  const start = Date.now();
1008
1016
  try {
1009
- const { client, project, repo } = getClient2(program2);
1017
+ const { client, project, repo } = getClient2(bb);
1010
1018
  const data = await client.listComments(project, repo, parseInt(opts.pr, 10), {
1011
1019
  withReplies: opts.withReplies,
1012
1020
  inlineOnly: opts.inlineOnly,
@@ -1020,7 +1028,7 @@ function registerBitbucketCommands(program2) {
1020
1028
  bb.command("add-comment").description("Add a comment to a pull request").requiredOption("--pr <pr-id>", "Pull request ID").requiredOption("--body <text>", "Comment text").action(async (opts) => {
1021
1029
  const start = Date.now();
1022
1030
  try {
1023
- const { client, project, repo } = getClient2(program2);
1031
+ const { client, project, repo } = getClient2(bb);
1024
1032
  const data = await client.addComment(project, repo, parseInt(opts.pr, 10), opts.body);
1025
1033
  success(data, "bitbucket", "add-comment", start);
1026
1034
  } catch (err) {
@@ -1030,7 +1038,7 @@ function registerBitbucketCommands(program2) {
1030
1038
  bb.command("add-inline-comment").description("Add an inline comment to a file in a pull request").requiredOption("--pr <pr-id>", "Pull request ID").requiredOption("--file <path>", "File path").requiredOption("--line <n>", "Line number").requiredOption("--body <text>", "Comment text").option("--line-type <type>", "Line type: ADDED|REMOVED|CONTEXT", "ADDED").action(async (opts) => {
1031
1039
  const start = Date.now();
1032
1040
  try {
1033
- const { client, project, repo } = getClient2(program2);
1041
+ const { client, project, repo } = getClient2(bb);
1034
1042
  const data = await client.addInlineComment({
1035
1043
  project,
1036
1044
  repo,
@@ -1048,7 +1056,7 @@ function registerBitbucketCommands(program2) {
1048
1056
  bb.command("reply-comment").description("Reply to a comment on a pull request").requiredOption("--pr <pr-id>", "Pull request ID").requiredOption("--comment-id <id>", "Comment ID to reply to").requiredOption("--body <text>", "Reply text").action(async (opts) => {
1049
1057
  const start = Date.now();
1050
1058
  try {
1051
- const { client, project, repo } = getClient2(program2);
1059
+ const { client, project, repo } = getClient2(bb);
1052
1060
  const data = await client.replyComment(project, repo, parseInt(opts.pr, 10), parseInt(opts.commentId, 10), opts.body);
1053
1061
  success(data, "bitbucket", "reply-comment", start);
1054
1062
  } catch (err) {
@@ -1058,7 +1066,7 @@ function registerBitbucketCommands(program2) {
1058
1066
  bb.command("resolve-comment").description("Resolve a comment on a pull request").requiredOption("--pr <pr-id>", "Pull request ID").requiredOption("--comment-id <id>", "Comment ID").option("--version <n>", "Comment version", "0").action(async (opts) => {
1059
1067
  const start = Date.now();
1060
1068
  try {
1061
- const { client, project, repo } = getClient2(program2);
1069
+ const { client, project, repo } = getClient2(bb);
1062
1070
  await client.resolveComment(project, repo, parseInt(opts.pr, 10), parseInt(opts.commentId, 10), parseInt(opts.version ?? "0", 10));
1063
1071
  success({ resolved: true }, "bitbucket", "resolve-comment", start);
1064
1072
  } catch (err) {
@@ -1068,7 +1076,7 @@ function registerBitbucketCommands(program2) {
1068
1076
  bb.command("delete-comment").description("Delete a comment on a pull request").requiredOption("--pr <pr-id>", "Pull request ID").requiredOption("--comment-id <id>", "Comment ID").option("--version <n>", "Comment version", "0").action(async (opts) => {
1069
1077
  const start = Date.now();
1070
1078
  try {
1071
- const { client, project, repo } = getClient2(program2);
1079
+ const { client, project, repo } = getClient2(bb);
1072
1080
  await client.deleteComment(project, repo, parseInt(opts.pr, 10), parseInt(opts.commentId, 10), parseInt(opts.version ?? "0", 10));
1073
1081
  success({ deleted: true }, "bitbucket", "delete-comment", start);
1074
1082
  } catch (err) {
@@ -1078,7 +1086,7 @@ function registerBitbucketCommands(program2) {
1078
1086
  bb.command("diff").description("Get unified diff for a pull request").requiredOption("--pr <pr-id>", "Pull request ID").option("--file <path>", "Limit diff to a specific file").option("--context-lines <n>", "Lines of context around changes").action(async (opts) => {
1079
1087
  const start = Date.now();
1080
1088
  try {
1081
- const { client, project, repo } = getClient2(program2);
1089
+ const { client, project, repo } = getClient2(bb);
1082
1090
  const contextLines = opts.contextLines ? parseInt(opts.contextLines, 10) : void 0;
1083
1091
  const diff = await client.getDiff(project, repo, parseInt(opts.pr, 10), opts.file, contextLines);
1084
1092
  success({ diff }, "bitbucket", "diff", start);
@@ -1089,7 +1097,7 @@ function registerBitbucketCommands(program2) {
1089
1097
  bb.command("list-files").description("List files changed in a pull request").requiredOption("--pr <pr-id>", "Pull request ID").action(async (opts) => {
1090
1098
  const start = Date.now();
1091
1099
  try {
1092
- const { client, project, repo } = getClient2(program2);
1100
+ const { client, project, repo } = getClient2(bb);
1093
1101
  const data = await client.listFiles(project, repo, parseInt(opts.pr, 10));
1094
1102
  success(data, "bitbucket", "list-files", start);
1095
1103
  } catch (err) {
@@ -1099,7 +1107,7 @@ function registerBitbucketCommands(program2) {
1099
1107
  bb.command("approve").description("Approve a pull request").requiredOption("--pr <pr-id>", "Pull request ID").action(async (opts) => {
1100
1108
  const start = Date.now();
1101
1109
  try {
1102
- const { client, project, repo } = getClient2(program2);
1110
+ const { client, project, repo } = getClient2(bb);
1103
1111
  const data = await client.approvePR(project, repo, parseInt(opts.pr, 10));
1104
1112
  success(data, "bitbucket", "approve", start);
1105
1113
  } catch (err) {
@@ -1109,7 +1117,7 @@ function registerBitbucketCommands(program2) {
1109
1117
  bb.command("unapprove").description("Remove approval from a pull request").requiredOption("--pr <pr-id>", "Pull request ID").action(async (opts) => {
1110
1118
  const start = Date.now();
1111
1119
  try {
1112
- const { client, project, repo } = getClient2(program2);
1120
+ const { client, project, repo } = getClient2(bb);
1113
1121
  const data = await client.unapprovePR(project, repo, parseInt(opts.pr, 10));
1114
1122
  success(data, "bitbucket", "unapprove", start);
1115
1123
  } catch (err) {
@@ -1119,7 +1127,7 @@ function registerBitbucketCommands(program2) {
1119
1127
  bb.command("needs-work").description("Mark a pull request as needs-work").requiredOption("--pr <pr-id>", "Pull request ID").action(async (opts) => {
1120
1128
  const start = Date.now();
1121
1129
  try {
1122
- const { client, project, repo } = getClient2(program2);
1130
+ const { client, project, repo } = getClient2(bb);
1123
1131
  const data = await client.needsWorkPR(project, repo, parseInt(opts.pr, 10));
1124
1132
  success(data, "bitbucket", "needs-work", start);
1125
1133
  } catch (err) {
@@ -1129,7 +1137,7 @@ function registerBitbucketCommands(program2) {
1129
1137
  bb.command("list-reviewers").description("List reviewers of a pull request").requiredOption("--pr <pr-id>", "Pull request ID").action(async (opts) => {
1130
1138
  const start = Date.now();
1131
1139
  try {
1132
- const { client, project, repo } = getClient2(program2);
1140
+ const { client, project, repo } = getClient2(bb);
1133
1141
  const data = await client.listReviewers(project, repo, parseInt(opts.pr, 10));
1134
1142
  success(data, "bitbucket", "list-reviewers", start);
1135
1143
  } catch (err) {
@@ -1139,7 +1147,7 @@ function registerBitbucketCommands(program2) {
1139
1147
  bb.command("list-builds").description("List build statuses for a pull request").requiredOption("--pr <pr-id>", "Pull request ID").action(async (opts) => {
1140
1148
  const start = Date.now();
1141
1149
  try {
1142
- const { client, project, repo } = getClient2(program2);
1150
+ const { client, project, repo } = getClient2(bb);
1143
1151
  const data = await client.listBuilds(project, repo, parseInt(opts.pr, 10));
1144
1152
  success(data, "bitbucket", "list-builds", start);
1145
1153
  } catch (err) {
@@ -1149,7 +1157,7 @@ function registerBitbucketCommands(program2) {
1149
1157
  bb.command("get-build-status").description("Get build status for a commit SHA").requiredOption("--commit <sha>", "Commit SHA").action(async (opts) => {
1150
1158
  const start = Date.now();
1151
1159
  try {
1152
- const { client } = getClient2(program2);
1160
+ const { client } = getClient2(bb);
1153
1161
  const data = await client.getBuildStatus(opts.commit);
1154
1162
  success(data, "bitbucket", "get-build-status", start);
1155
1163
  } catch (err) {
@@ -1336,7 +1344,7 @@ function registerConfluenceCommands(program2) {
1336
1344
  fail(err, "confluence", "get-page-by-title", start);
1337
1345
  }
1338
1346
  });
1339
- confluence.command("list-pages").description("List pages in a Confluence space").requiredOption("--space <key>", "Space key").option("--limit <n>", "Max results per page (default: all)").option("--start <n>", "Offset for first result").action(async (opts) => {
1347
+ confluence.command("list-pages").description("List pages in a Confluence space (consider --output-file for large results)").requiredOption("--space <key>", "Space key").option("--limit <n>", "Max results per page (default: all)").option("--start <n>", "Offset for first result").action(async (opts) => {
1340
1348
  const start = Date.now();
1341
1349
  try {
1342
1350
  const client = getClient3(program2);
@@ -1631,7 +1639,7 @@ function registerSonarCommands(program2) {
1631
1639
  fail(err, "sonar", "quality-gate", start);
1632
1640
  }
1633
1641
  });
1634
- sonar.command("issues").description("Search/list issues for a project").option("--project <key>", "SonarQube project key (or set defaults.sonar.project in config)").option("--severities <list>", "Filter by severity: BLOCKER,CRITICAL,MAJOR,MINOR,INFO (comma-separated)").option("--types <list>", "Filter by type: BUG,VULNERABILITY,CODE_SMELL (comma-separated)").option("--statuses <list>", "Filter by status: OPEN,CONFIRMED,REOPENED,RESOLVED,CLOSED (comma-separated)").option("--branch <name>", "Branch name").option("--resolved <bool>", "Filter resolved issues: true or false").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page (max 500)", "100").option("--all", "Fetch all pages (ignores --page/--page-size)").action(async (opts) => {
1642
+ sonar.command("issues").description("Search/list issues for a project").option("--project <key>", "SonarQube project key (or set defaults.sonar.project in config)").option("--severities <list>", "Filter by severity: BLOCKER,CRITICAL,MAJOR,MINOR,INFO (comma-separated)").option("--types <list>", "Filter by type: BUG,VULNERABILITY,CODE_SMELL (comma-separated)").option("--statuses <list>", "Filter by status: OPEN,CONFIRMED,REOPENED,RESOLVED,CLOSED (comma-separated)").option("--branch <name>", "Branch name").option("--resolved <bool>", "Filter resolved issues: true or false").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page (max 500)", "100").option("--all", "Fetch all pages (consider --output-file for large results; ignores --page/--page-size)").action(async (opts) => {
1635
1643
  const start = Date.now();
1636
1644
  try {
1637
1645
  const { client, config } = getClient4(program2);
@@ -1674,7 +1682,7 @@ function registerSonarCommands(program2) {
1674
1682
  fail(err, "sonar", "measures", start);
1675
1683
  }
1676
1684
  });
1677
- sonar.command("projects").description("Search/list projects").option("--query <text>", "Search query").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page", "100").option("--all", "Fetch all pages").action(async (opts) => {
1685
+ sonar.command("projects").description("Search/list projects").option("--query <text>", "Search query").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page", "100").option("--all", "Fetch all pages (consider --output-file for large results)").action(async (opts) => {
1678
1686
  const start = Date.now();
1679
1687
  try {
1680
1688
  const { client } = getClient4(program2);
@@ -1693,7 +1701,7 @@ function registerSonarCommands(program2) {
1693
1701
  fail(err, "sonar", "projects", start);
1694
1702
  }
1695
1703
  });
1696
- sonar.command("hotspots").description("List security hotspots for a project").option("--project <key>", "SonarQube project key (or set defaults.sonar.project in config)").option("--status <status>", "Filter: TO_REVIEW or REVIEWED").option("--resolution <list>", "Filter: FIXED,SAFE,ACKNOWLEDGED (comma-separated)").option("--branch <name>", "Branch name").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page", "100").option("--all", "Fetch all pages").action(async (opts) => {
1704
+ sonar.command("hotspots").description("List security hotspots for a project").option("--project <key>", "SonarQube project key (or set defaults.sonar.project in config)").option("--status <status>", "Filter: TO_REVIEW or REVIEWED").option("--resolution <list>", "Filter: FIXED,SAFE,ACKNOWLEDGED (comma-separated)").option("--branch <name>", "Branch name").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page", "100").option("--all", "Fetch all pages (consider --output-file for large results)").action(async (opts) => {
1697
1705
  const start = Date.now();
1698
1706
  try {
1699
1707
  const { client, config } = getClient4(program2);
@@ -1901,7 +1909,7 @@ function registerSdeCommands(program2) {
1901
1909
  fail(err, "sde", "whoami", start);
1902
1910
  }
1903
1911
  });
1904
- sde.command("users").description("List users").option("--email <email>", "Filter by email address").option("--first-name <name>", "Filter by first name").option("--last-name <name>", "Filter by last name").option("--active <bool>", "Filter by active status: true or false").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page", "100").option("--all", "Fetch all pages").action(async (opts) => {
1912
+ sde.command("users").description("List users").option("--email <email>", "Filter by email address").option("--first-name <name>", "Filter by first name").option("--last-name <name>", "Filter by last name").option("--active <bool>", "Filter by active status: true or false").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page", "100").option("--all", "Fetch all pages (consider --output-file for large results)").action(async (opts) => {
1905
1913
  const start = Date.now();
1906
1914
  try {
1907
1915
  const { client } = getClient5(program2);
@@ -1926,7 +1934,7 @@ function registerSdeCommands(program2) {
1926
1934
  fail(err, "sde", "users", start);
1927
1935
  }
1928
1936
  });
1929
- sde.command("projects").description("List projects").option("--name <name>", "Filter by project name").option("--search <text>", "Text search on name and profile").option("--active <val>", "Filter by active status: true, false, or all").option("--ordering <field>", "Sort by: name, created, updated (prefix with - for descending)").option("--expand <fields>", "Expand nested fields (comma-separated): application,business_unit,creator").option("--include <fields>", "Include extra fields (comma-separated): task_counts,permissions").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page", "100").option("--all", "Fetch all pages").action(async (opts) => {
1937
+ sde.command("projects").description("List projects").option("--name <name>", "Filter by project name").option("--search <text>", "Text search on name and profile").option("--active <val>", "Filter by active status: true, false, or all").option("--ordering <field>", "Sort by: name, created, updated (prefix with - for descending)").option("--expand <fields>", "Expand nested fields (comma-separated): application,business_unit,creator").option("--include <fields>", "Include extra fields (comma-separated): task_counts,permissions").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page", "100").option("--all", "Fetch all pages (consider --output-file for large results)").action(async (opts) => {
1930
1938
  const start = Date.now();
1931
1939
  try {
1932
1940
  const { client } = getClient5(program2);
@@ -1964,7 +1972,7 @@ function registerSdeCommands(program2) {
1964
1972
  fail(err, "sde", "project", start);
1965
1973
  }
1966
1974
  });
1967
- sde.command("tasks").description("List countermeasures for a project").option("--project <id>", "Project ID (or set defaults.sde.project in config)").option("--phase <slug>", "Filter by phase slug (e.g. development, architecture-design)").option("--priority <n>", "Filter by priority (1-10)").option("--status <id>", "Filter by status ID (e.g. TS1, TS2)").option("--assigned-to <email>", "Filter by assignee email").option("--source <val>", "Filter by source: default, custom, manual, project").option("--verification <val>", "Filter by verification: pass, fail, partial, none").option("--tag <name>", "Filter by tag name").option("--accepted <bool>", "Filter by accepted status: true or false").option("--relevant <bool>", "Filter by relevant status: true or false").option("--expand <fields>", "Expand nested fields (comma-separated): status,phase,problem,text").option("--include <fields>", "Include extra fields (comma-separated): how_tos,last_note,references,regulation_sections").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page", "100").option("--all", "Fetch all pages").action(async (opts) => {
1975
+ sde.command("tasks").description("List countermeasures for a project").option("--project <id>", "Project ID (or set defaults.sde.project in config)").option("--phase <slug>", "Filter by phase slug (e.g. development, architecture-design)").option("--priority <n>", "Filter by priority (1-10)").option("--status <id>", "Filter by status ID (e.g. TS1, TS2)").option("--assigned-to <email>", "Filter by assignee email").option("--source <val>", "Filter by source: default, custom, manual, project").option("--verification <val>", "Filter by verification: pass, fail, partial, none").option("--tag <name>", "Filter by tag name").option("--accepted <bool>", "Filter by accepted status: true or false").option("--relevant <bool>", "Filter by relevant status: true or false").option("--expand <fields>", "Expand nested fields (comma-separated): status,phase,problem,text").option("--include <fields>", "Include extra fields (comma-separated): how_tos,last_note,references,regulation_sections").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page", "100").option("--all", "Fetch all pages (consider --output-file for large results)").action(async (opts) => {
1968
1976
  const start = Date.now();
1969
1977
  try {
1970
1978
  const { client, config } = getClient5(program2);
@@ -2009,7 +2017,7 @@ function registerSdeCommands(program2) {
2009
2017
  fail(err, "sde", "task", start);
2010
2018
  }
2011
2019
  });
2012
- sde.command("threats").description("List threats for a project").option("--project <id>", "Project ID (or set defaults.sde.project in config)").option("--severity <n>", "Filter by severity (1-10)").option("--search <text>", "Full-text search on title and threat ID").option("--ordering <field>", "Sort by: threat__severity, threat_id, status (prefix - for descending)").option("--capec-id <id>", "Filter by CAPEC attack pattern ID").option("--component-id <id>", "Filter by component ID").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page", "100").option("--all", "Fetch all pages").action(async (opts) => {
2020
+ sde.command("threats").description("List threats for a project").option("--project <id>", "Project ID (or set defaults.sde.project in config)").option("--severity <n>", "Filter by severity (1-10)").option("--search <text>", "Full-text search on title and threat ID").option("--ordering <field>", "Sort by: threat__severity, threat_id, status (prefix - for descending)").option("--capec-id <id>", "Filter by CAPEC attack pattern ID").option("--component-id <id>", "Filter by component ID").option("--page <n>", "Page number (1-based)", "1").option("--page-size <n>", "Results per page", "100").option("--all", "Fetch all pages (consider --output-file for large results)").action(async (opts) => {
2013
2021
  const start = Date.now();
2014
2022
  try {
2015
2023
  const { client, config } = getClient5(program2);
@@ -2039,6 +2047,9 @@ function registerSdeCommands(program2) {
2039
2047
  });
2040
2048
  }
2041
2049
 
2050
+ // src/services/deps/commands.ts
2051
+ import { Option } from "commander";
2052
+
2042
2053
  // src/services/deps/parsers/index.ts
2043
2054
  import fs from "fs";
2044
2055
  import path4 from "path";
@@ -2893,11 +2904,128 @@ async function checkPackagesForVulns(packages) {
2893
2904
  return allVulnerable;
2894
2905
  }
2895
2906
 
2896
- // src/services/deps/clients/artifactory.ts
2897
- var TIMEOUT_MS2 = 15e3;
2898
- async function fetchWithTimeout2(url, token) {
2907
+ // src/services/deps/clients/sonatype.ts
2908
+ var SONATYPE_URL = "https://ossindex.sonatype.org";
2909
+ var BATCH_SIZE2 = 128;
2910
+ var TIMEOUT_MS2 = 3e4;
2911
+ async function fetchWithTimeout2(url, init) {
2899
2912
  const controller = new AbortController();
2900
2913
  const timer = setTimeout(() => controller.abort(), TIMEOUT_MS2);
2914
+ try {
2915
+ return await fetch(url, { ...init, signal: controller.signal });
2916
+ } finally {
2917
+ clearTimeout(timer);
2918
+ }
2919
+ }
2920
+ function toPurl(pkg2) {
2921
+ switch (pkg2.ecosystem) {
2922
+ case "npm": {
2923
+ const encodedName = pkg2.name.startsWith("@") ? pkg2.name.replace("@", "%40") : pkg2.name;
2924
+ return `pkg:npm/${encodedName}@${pkg2.version}`;
2925
+ }
2926
+ case "nuget":
2927
+ return `pkg:nuget/${pkg2.name}@${pkg2.version}`;
2928
+ case "maven": {
2929
+ const colonIdx = pkg2.name.indexOf(":");
2930
+ if (colonIdx !== -1) {
2931
+ const groupId = pkg2.name.slice(0, colonIdx);
2932
+ const artifactId = pkg2.name.slice(colonIdx + 1);
2933
+ return `pkg:maven/${encodeURIComponent(groupId)}/${encodeURIComponent(artifactId)}@${pkg2.version}`;
2934
+ }
2935
+ return `pkg:maven/${encodeURIComponent(pkg2.name)}@${pkg2.version}`;
2936
+ }
2937
+ default:
2938
+ return `pkg:generic/${encodeURIComponent(pkg2.name)}@${pkg2.version}`;
2939
+ }
2940
+ }
2941
+ function inferSeverityFromCvss2(score) {
2942
+ if (score >= 9) return "CRITICAL";
2943
+ if (score >= 7) return "HIGH";
2944
+ if (score >= 4) return "MEDIUM";
2945
+ if (score > 0) return "LOW";
2946
+ return "UNKNOWN";
2947
+ }
2948
+ function mapSonatypeVuln(v, pkg2) {
2949
+ const refs = [v.reference, ...v.externalReferences ?? []].filter(Boolean);
2950
+ const advice = `See ${v.reference} for remediation details for ${pkg2.name}@${pkg2.version}.`;
2951
+ const cvePattern = /CVE-\d{4}-\d+/gi;
2952
+ const searchText = [v.displayName, v.title, v.description].filter(Boolean).join(" ");
2953
+ const aliases = [...new Set([...searchText.matchAll(cvePattern)].map((m) => m[0].toUpperCase()))];
2954
+ return {
2955
+ id: v.displayName || v.id,
2956
+ summary: v.title || v.description,
2957
+ severity: inferSeverityFromCvss2(v.cvssScore),
2958
+ cvss: v.cvssScore > 0 ? v.cvssScore : null,
2959
+ aliases,
2960
+ remediation: {
2961
+ fixAvailable: false,
2962
+ fixedVersions: [],
2963
+ advice
2964
+ },
2965
+ references: refs
2966
+ };
2967
+ }
2968
+ async function queryBatch2(purls, packages) {
2969
+ const res = await fetchWithTimeout2(`${SONATYPE_URL}/api/v3/component-report`, {
2970
+ method: "POST",
2971
+ headers: { "Content-Type": "application/json", Accept: "application/json" },
2972
+ body: JSON.stringify({ coordinates: purls })
2973
+ });
2974
+ if (res.status === 429) {
2975
+ throw new Error(
2976
+ "Sonatype OSS Index is rate-limiting requests from this IP. Create a free account at ossindex.sonatype.org and add your credentials to your pncli config to raise the limit."
2977
+ );
2978
+ }
2979
+ if (!res.ok) {
2980
+ throw new Error(`Sonatype OSS Index returned HTTP ${res.status}`);
2981
+ }
2982
+ const reports = await res.json();
2983
+ const vulnerable = [];
2984
+ const purlToPackage = /* @__PURE__ */ new Map();
2985
+ for (let i = 0; i < purls.length; i++) {
2986
+ purlToPackage.set(purls[i], packages[i]);
2987
+ }
2988
+ for (const report of reports) {
2989
+ if (!report?.coordinates) continue;
2990
+ const pkg2 = purlToPackage.get(report.coordinates);
2991
+ if (!pkg2) continue;
2992
+ const vulns = report.vulnerabilities ?? [];
2993
+ if (vulns.length === 0) continue;
2994
+ vulnerable.push({ ...pkg2, vulnerabilities: vulns.map((v) => mapSonatypeVuln(v, pkg2)) });
2995
+ }
2996
+ return vulnerable;
2997
+ }
2998
+ async function checkSonatypeConnectivity() {
2999
+ try {
3000
+ const res = await fetchWithTimeout2(`${SONATYPE_URL}/api/v3/component-report`, {
3001
+ method: "POST",
3002
+ headers: { "Content-Type": "application/json", Accept: "application/json" },
3003
+ body: JSON.stringify({ coordinates: [] })
3004
+ });
3005
+ return { reachable: res.ok || res.status === 400 };
3006
+ } catch (err) {
3007
+ return {
3008
+ reachable: false,
3009
+ error: err instanceof Error ? err.message : String(err)
3010
+ };
3011
+ }
3012
+ }
3013
+ async function checkPackagesForVulnsViaSonatype(packages) {
3014
+ const allVulnerable = [];
3015
+ for (let i = 0; i < packages.length; i += BATCH_SIZE2) {
3016
+ const chunk = packages.slice(i, i + BATCH_SIZE2);
3017
+ const purls = chunk.map(toPurl);
3018
+ const results = await queryBatch2(purls, chunk);
3019
+ allVulnerable.push(...results);
3020
+ }
3021
+ return allVulnerable;
3022
+ }
3023
+
3024
+ // src/services/deps/clients/artifactory.ts
3025
+ var TIMEOUT_MS3 = 15e3;
3026
+ async function fetchWithTimeout3(url, token) {
3027
+ const controller = new AbortController();
3028
+ const timer = setTimeout(() => controller.abort(), TIMEOUT_MS3);
2901
3029
  try {
2902
3030
  return await fetch(url, {
2903
3031
  headers: { Authorization: `Bearer ${token}` },
@@ -2933,7 +3061,7 @@ async function checkArtifactoryConnectivity(config) {
2933
3061
  };
2934
3062
  }
2935
3063
  try {
2936
- const res = await fetchWithTimeout2(`${config.baseUrl}/api/system/ping`, config.token);
3064
+ const res = await fetchWithTimeout3(`${config.baseUrl}/api/system/ping`, config.token);
2937
3065
  if (res.status === 401 || res.status === 403) {
2938
3066
  return {
2939
3067
  reachable: true,
@@ -2983,7 +3111,7 @@ function repoForEcosystem(config, eco) {
2983
3111
  async function getLatestNpm(baseUrl, token, repoName, packageName) {
2984
3112
  try {
2985
3113
  const encoded = packageName.startsWith("@") ? packageName.replace("/", "%2F") : packageName;
2986
- const res = await fetchWithTimeout2(`${baseUrl}/api/npm/${repoName}/${encoded}`, token);
3114
+ const res = await fetchWithTimeout3(`${baseUrl}/api/npm/${repoName}/${encoded}`, token);
2987
3115
  if (!res.ok) return null;
2988
3116
  const data = await res.json();
2989
3117
  return data["dist-tags"]?.latest ?? null;
@@ -2994,7 +3122,7 @@ async function getLatestNpm(baseUrl, token, repoName, packageName) {
2994
3122
  async function getLatestNuget(baseUrl, token, repoName, packageName) {
2995
3123
  try {
2996
3124
  const lower = packageName.toLowerCase();
2997
- const res = await fetchWithTimeout2(
3125
+ const res = await fetchWithTimeout3(
2998
3126
  `${baseUrl}/api/nuget/v3/${repoName}/flatcontainer/${lower}/index.json`,
2999
3127
  token
3000
3128
  );
@@ -3010,7 +3138,7 @@ async function getLatestMaven(baseUrl, token, repoName, packageName) {
3010
3138
  const [groupId, artifactId] = packageName.split(":");
3011
3139
  if (!groupId || !artifactId) return null;
3012
3140
  try {
3013
- const res = await fetchWithTimeout2(
3141
+ const res = await fetchWithTimeout3(
3014
3142
  `${baseUrl}/api/search/latestVersion?g=${encodeURIComponent(groupId)}&a=${encodeURIComponent(artifactId)}&repos=${repoName}`,
3015
3143
  token
3016
3144
  );
@@ -3060,7 +3188,7 @@ async function getOutdatedPackages(packages, config, filterType) {
3060
3188
  async function getLicenseNpm(baseUrl, token, repoName, packageName) {
3061
3189
  try {
3062
3190
  const encoded = packageName.startsWith("@") ? packageName.replace("/", "%2F") : packageName;
3063
- const res = await fetchWithTimeout2(`${baseUrl}/api/npm/${repoName}/${encoded}`, token);
3191
+ const res = await fetchWithTimeout3(`${baseUrl}/api/npm/${repoName}/${encoded}`, token);
3064
3192
  if (!res.ok) return null;
3065
3193
  const data = await res.json();
3066
3194
  if (typeof data.license === "string") return data.license;
@@ -3073,7 +3201,7 @@ async function getLicenseNpm(baseUrl, token, repoName, packageName) {
3073
3201
  async function getLicenseNuget(baseUrl, token, repoName, packageName, version) {
3074
3202
  try {
3075
3203
  const lower = packageName.toLowerCase();
3076
- const res = await fetchWithTimeout2(
3204
+ const res = await fetchWithTimeout3(
3077
3205
  `${baseUrl}/api/nuget/v3/${repoName}/registration/${lower}/${version}.json`,
3078
3206
  token
3079
3207
  );
@@ -3125,15 +3253,20 @@ async function detectTier(config) {
3125
3253
  cachedTier = { tier, osvReachable, artifactoryReachable };
3126
3254
  return cachedTier;
3127
3255
  }
3256
+ async function checkSonatypeReachable() {
3257
+ const result = await checkSonatypeConnectivity();
3258
+ return result.reachable;
3259
+ }
3128
3260
  async function buildConnectivityData(config) {
3129
- const [osvResult, artResult] = await Promise.all([
3261
+ const [osvResult, artResult, sonatypeResult] = await Promise.all([
3130
3262
  checkOsvConnectivity(),
3131
- checkArtifactoryConnectivity(config.artifactory)
3263
+ checkArtifactoryConnectivity(config.artifactory),
3264
+ checkSonatypeConnectivity()
3132
3265
  ]);
3133
3266
  const artCfg = config.artifactory;
3134
3267
  let tier = "local";
3135
3268
  if (artResult.reachable && artResult.authenticated) tier = "artifactory";
3136
- if (osvResult.reachable) tier = "full";
3269
+ if (osvResult.reachable || sonatypeResult.reachable) tier = "full";
3137
3270
  cachedTier = {
3138
3271
  tier,
3139
3272
  osvReachable: osvResult.reachable,
@@ -3156,46 +3289,134 @@ async function buildConnectivityData(config) {
3156
3289
  url: "https://api.osv.dev",
3157
3290
  ...osvResult.error ? { error: osvResult.error } : {}
3158
3291
  },
3292
+ sonatype: {
3293
+ reachable: sonatypeResult.reachable,
3294
+ url: "https://ossindex.sonatype.org",
3295
+ ...sonatypeResult.error ? { error: sonatypeResult.error } : {}
3296
+ },
3159
3297
  tier,
3160
3298
  capabilities: {
3161
3299
  scan: true,
3162
3300
  diff: true,
3163
3301
  outdated: artResult.reachable && artResult.authenticated,
3164
3302
  licenseCheck: artResult.reachable && artResult.authenticated,
3165
- cveCheck: osvResult.reachable
3303
+ cveCheck: osvResult.reachable || sonatypeResult.reachable
3166
3304
  }
3167
3305
  };
3168
3306
  }
3169
3307
 
3170
3308
  // src/services/deps/frisk.ts
3171
- async function runFrisk(config, opts) {
3309
+ function toErrorMessage(err) {
3310
+ return err instanceof Error ? err.message : String(err);
3311
+ }
3312
+ async function runFrisk(config, opts, source = "osv") {
3172
3313
  const repoRoot = getRepoRoot();
3173
3314
  if (!repoRoot) {
3174
3315
  throw new PncliError("Not inside a git repository.", 1);
3175
3316
  }
3176
- const { tier, osvReachable } = await detectTier(config);
3177
- if (!osvReachable) {
3317
+ const { tier: baseTier, osvReachable } = await detectTier(config);
3318
+ const sonatypeReachable = source !== "osv" ? await checkSonatypeReachable() : false;
3319
+ const tier = sonatypeReachable ? "full" : baseTier;
3320
+ if (source === "osv" && !osvReachable) {
3178
3321
  throw new PncliError(
3179
3322
  "deps frisk requires OSV.dev access but api.osv.dev is not reachable from this machine. Run 'pncli deps connectivity' to diagnose.",
3180
3323
  503
3181
3324
  );
3182
3325
  }
3326
+ if (source === "sonatype" && !sonatypeReachable) {
3327
+ throw new PncliError(
3328
+ "deps frisk --source sonatype requires Sonatype OSS Index access but ossindex.sonatype.org is not reachable from this machine. Run 'pncli deps connectivity' to diagnose.",
3329
+ 503
3330
+ );
3331
+ }
3332
+ if (source === "all" && !osvReachable && !sonatypeReachable) {
3333
+ throw new PncliError(
3334
+ "deps frisk requires either OSV.dev or Sonatype OSS Index access, but neither is reachable from this machine. Run 'pncli deps connectivity' to diagnose.",
3335
+ 503
3336
+ );
3337
+ }
3338
+ const sourceErrors = [];
3339
+ let sourcesQueried;
3340
+ if (source === "all") {
3341
+ if (!osvReachable) sourceErrors.push({ source: "osv", error: "api.osv.dev is not reachable \u2014 run pncli deps connectivity to diagnose." });
3342
+ if (!sonatypeReachable) sourceErrors.push({ source: "sonatype", error: "ossindex.sonatype.org is not reachable \u2014 run pncli deps connectivity to diagnose." });
3343
+ sourcesQueried = [
3344
+ ...osvReachable ? ["osv"] : [],
3345
+ ...sonatypeReachable ? ["sonatype"] : []
3346
+ ];
3347
+ } else {
3348
+ sourcesQueried = [source];
3349
+ }
3183
3350
  const scanOpts = {
3184
3351
  ...opts,
3185
3352
  includeTransitive: opts.includeTransitive ?? true
3186
3353
  };
3187
3354
  const scan = scanRepo(repoRoot, scanOpts);
3188
3355
  if (scan.packages.length === 0) {
3189
- return { tier, scanned: 0, vulnerable: 0, packages: [] };
3356
+ return { tier, source, sourcesQueried, sourceErrors, scanned: 0, vulnerable: 0, packages: [] };
3357
+ }
3358
+ let vulnerable;
3359
+ if (source === "sonatype") {
3360
+ vulnerable = await checkPackagesForVulnsViaSonatype(scan.packages);
3361
+ } else if (source === "all") {
3362
+ const [osvSettled, sonatypeSettled] = await Promise.allSettled([
3363
+ osvReachable ? checkPackagesForVulns(scan.packages) : Promise.resolve([]),
3364
+ sonatypeReachable ? checkPackagesForVulnsViaSonatype(scan.packages) : Promise.resolve([])
3365
+ ]);
3366
+ if (osvReachable) {
3367
+ if (osvSettled.status === "rejected") {
3368
+ sourceErrors.push({ source: "osv", error: toErrorMessage(osvSettled.reason) });
3369
+ sourcesQueried = sourcesQueried.filter((s) => s !== "osv");
3370
+ }
3371
+ }
3372
+ if (sonatypeReachable) {
3373
+ if (sonatypeSettled.status === "rejected") {
3374
+ sourceErrors.push({ source: "sonatype", error: toErrorMessage(sonatypeSettled.reason) });
3375
+ sourcesQueried = sourcesQueried.filter((s) => s !== "sonatype");
3376
+ }
3377
+ }
3378
+ if (sourcesQueried.length === 0) {
3379
+ throw new PncliError(
3380
+ "All vulnerability sources failed during scanning. Run 'pncli deps connectivity' to diagnose.",
3381
+ 503
3382
+ );
3383
+ }
3384
+ vulnerable = mergeVulnerablePackages(
3385
+ osvSettled.status === "fulfilled" ? osvSettled.value : [],
3386
+ sonatypeSettled.status === "fulfilled" ? sonatypeSettled.value : []
3387
+ );
3388
+ } else {
3389
+ vulnerable = await checkPackagesForVulns(scan.packages);
3190
3390
  }
3191
- const vulnerable = await checkPackagesForVulns(scan.packages);
3192
3391
  return {
3193
3392
  tier,
3393
+ source,
3394
+ sourcesQueried,
3395
+ sourceErrors,
3194
3396
  scanned: scan.packages.length,
3195
3397
  vulnerable: vulnerable.length,
3196
3398
  packages: vulnerable
3197
3399
  };
3198
3400
  }
3401
+ function mergeVulnerablePackages(osvResults, sonatypeResults) {
3402
+ const byKey = /* @__PURE__ */ new Map();
3403
+ for (const pkg2 of osvResults) {
3404
+ const key = `${pkg2.ecosystem}:${pkg2.name}@${pkg2.version}`;
3405
+ byKey.set(key, { ...pkg2 });
3406
+ }
3407
+ for (const pkg2 of sonatypeResults) {
3408
+ const key = `${pkg2.ecosystem}:${pkg2.name}@${pkg2.version}`;
3409
+ const existing = byKey.get(key);
3410
+ if (existing) {
3411
+ const existingIds = new Set(existing.vulnerabilities.map((v) => v.id));
3412
+ const newVulns = pkg2.vulnerabilities.filter((v) => !existingIds.has(v.id));
3413
+ existing.vulnerabilities = [...existing.vulnerabilities, ...newVulns];
3414
+ } else {
3415
+ byKey.set(key, { ...pkg2 });
3416
+ }
3417
+ }
3418
+ return [...byKey.values()];
3419
+ }
3199
3420
 
3200
3421
  // src/services/deps/outdated.ts
3201
3422
  async function runOutdated(config, opts, filterType) {
@@ -3270,7 +3491,7 @@ async function runLicenseCheck(config, opts) {
3270
3491
  // src/services/deps/commands.ts
3271
3492
  function registerDepsCommands(program2) {
3272
3493
  const deps = program2.command("deps").description("Dependency scanning, CVE detection, and license auditing");
3273
- deps.command("frisk").description("Scan all dependencies for CVEs and output remediation paths (requires OSV.dev)").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).action(async (opts, cmd) => {
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) => {
3274
3495
  const startTime = Date.now();
3275
3496
  try {
3276
3497
  const globalOpts = cmd.optsWithGlobals();
@@ -3280,7 +3501,7 @@ function registerDepsCommands(program2) {
3280
3501
  includeTransitive: !opts.directOnly,
3281
3502
  includeDev: opts.includeDev
3282
3503
  };
3283
- const data = await runFrisk(config, scanOpts);
3504
+ const data = await runFrisk(config, scanOpts, opts.source);
3284
3505
  success(data, "deps", "frisk", startTime);
3285
3506
  } catch (err) {
3286
3507
  fail(err, "deps", "frisk", startTime);
@@ -3346,7 +3567,7 @@ function registerDepsCommands(program2) {
3346
3567
  fail(err, "deps", "license-check", startTime);
3347
3568
  }
3348
3569
  });
3349
- deps.command("connectivity").description("Test network access to Artifactory and OSV.dev, report available tier").action(async (_opts, cmd) => {
3570
+ deps.command("connectivity").description("Test network access to Artifactory, OSV.dev, and Sonatype OSS Index, report available tier").action(async (_opts, cmd) => {
3350
3571
  const startTime = Date.now();
3351
3572
  try {
3352
3573
  const globalOpts = cmd.optsWithGlobals();
@@ -3603,7 +3824,7 @@ function registerConfigCommands(program2) {
3603
3824
  if (cfg.ado.baseUrl) {
3604
3825
  try {
3605
3826
  const collection = cfg.defaults.ado?.collection;
3606
- const path6 = collection ? `/${encodeURIComponent(collection)}/_apis/connectionData?api-version=7.1` : "/_apis/projectCollections?api-version=7.1";
3827
+ const path6 = collection ? `/${encodeURIComponent(collection)}/_apis/connectionData?api-version=7.1-preview.1` : "/_apis/projectCollections?api-version=7.1";
3607
3828
  await http.ado(path6);
3608
3829
  results.ado = { ok: true, message: "connected" };
3609
3830
  } catch (err) {
@@ -3612,6 +3833,67 @@ function registerConfigCommands(program2) {
3612
3833
  } else {
3613
3834
  results.ado = { ok: null, message: "not configured" };
3614
3835
  }
3836
+ if (cfg.jenkins.baseUrl && cfg.jenkins.username && cfg.jenkins.apiToken) {
3837
+ try {
3838
+ await http.jenkins("/api/json", { params: { tree: "nodeName" } });
3839
+ results.jenkins = { ok: true, message: "connected" };
3840
+ } catch (err) {
3841
+ results.jenkins = { ok: false, message: err instanceof Error ? err.message : String(err) };
3842
+ }
3843
+ } else {
3844
+ results.jenkins = { ok: null, message: "not configured" };
3845
+ }
3846
+ if (cfg.artifactory.baseUrl) {
3847
+ try {
3848
+ await http.artifactoryText("/api/system/ping");
3849
+ results.artifactory = { ok: true, message: "connected" };
3850
+ } catch (err) {
3851
+ results.artifactory = { ok: false, message: err instanceof Error ? err.message : String(err) };
3852
+ }
3853
+ } else {
3854
+ results.artifactory = { ok: null, message: "not configured" };
3855
+ }
3856
+ if (cfg.udeploy.baseUrl && (cfg.udeploy.pat || cfg.udeploy.username && cfg.udeploy.password)) {
3857
+ try {
3858
+ await http.udeploy("/cli/application");
3859
+ results.udeploy = { ok: true, message: "connected" };
3860
+ } catch (err) {
3861
+ results.udeploy = { ok: false, message: err instanceof Error ? err.message : String(err) };
3862
+ }
3863
+ } else {
3864
+ results.udeploy = { ok: null, message: "not configured" };
3865
+ }
3866
+ if (cfg.checkmarx.baseUrl && cfg.checkmarx.username && cfg.checkmarx.password) {
3867
+ try {
3868
+ await http.checkmarx("/cxrestapi/projects");
3869
+ results.checkmarx = { ok: true, message: "connected" };
3870
+ } catch (err) {
3871
+ results.checkmarx = { ok: false, message: err instanceof Error ? err.message : String(err) };
3872
+ }
3873
+ } else {
3874
+ results.checkmarx = { ok: null, message: "not configured" };
3875
+ }
3876
+ const snConfigured = cfg.servicenow.baseUrl && cfg.servicenow.username && (cfg.servicenow.password || cfg.servicenow.apiToken);
3877
+ if (snConfigured) {
3878
+ try {
3879
+ await http.servicenow("/api/now/table/change_request", { params: { sysparm_limit: 1 } });
3880
+ results.servicenow = { ok: true, message: "connected" };
3881
+ } catch (err) {
3882
+ results.servicenow = { ok: false, message: err instanceof Error ? err.message : String(err) };
3883
+ }
3884
+ } else {
3885
+ results.servicenow = { ok: null, message: "not configured" };
3886
+ }
3887
+ if (cfg.contrast.apiKey && cfg.contrast.serviceKey && cfg.contrast.username && cfg.contrast.orgUuid) {
3888
+ try {
3889
+ await http.contrast(`/Contrast/api/ng/${cfg.contrast.orgUuid}/applications`, { params: { limit: 1 } });
3890
+ results.contrast = { ok: true, message: "connected" };
3891
+ } catch (err) {
3892
+ results.contrast = { ok: false, message: err instanceof Error ? err.message : String(err) };
3893
+ }
3894
+ } else {
3895
+ results.contrast = { ok: null, message: "not configured" };
3896
+ }
3615
3897
  success(results, "config", "test", start);
3616
3898
  } catch (err) {
3617
3899
  fail(err, "config", "test", start);
@@ -3705,13 +3987,37 @@ function registerConfigCommands(program2) {
3705
3987
  } else {
3706
3988
  try {
3707
3989
  const collection = cfg.defaults.ado?.collection;
3708
- const path6 = collection ? `/${encodeURIComponent(collection)}/_apis/connectionData?api-version=7.1` : "/_apis/projectCollections?api-version=7.1";
3990
+ const path6 = collection ? `/${encodeURIComponent(collection)}/_apis/connectionData?api-version=7.1-preview.1` : "/_apis/projectCollections?api-version=7.1";
3709
3991
  await http.ado(path6, { timeoutMs: 1e4 });
3710
3992
  results.ado = { status: "valid", message: "ok" };
3711
3993
  } catch (err) {
3712
3994
  results.ado = categorize2(err);
3713
3995
  }
3714
3996
  }
3997
+ if (!cfg.jenkins.apiToken || !cfg.jenkins.username) {
3998
+ results.jenkins = { status: "blank", message: "not configured" };
3999
+ } else if (!cfg.jenkins.baseUrl) {
4000
+ results.jenkins = { status: "error", message: "baseUrl not configured" };
4001
+ } else {
4002
+ try {
4003
+ await http.jenkins("/api/json", { params: { tree: "nodeName" }, timeoutMs: 1e4 });
4004
+ results.jenkins = { status: "valid", message: "ok" };
4005
+ } catch (err) {
4006
+ results.jenkins = categorize2(err);
4007
+ }
4008
+ }
4009
+ if (!cfg.udeploy.pat && !(cfg.udeploy.username && cfg.udeploy.password)) {
4010
+ results.udeploy = { status: "blank", message: "not configured" };
4011
+ } else if (!cfg.udeploy.baseUrl) {
4012
+ results.udeploy = { status: "error", message: "baseUrl not configured" };
4013
+ } else {
4014
+ try {
4015
+ await http.udeploy("/cli/application", { timeoutMs: 1e4 });
4016
+ results.udeploy = { status: "valid", message: "ok" };
4017
+ } catch (err) {
4018
+ results.udeploy = categorize2(err);
4019
+ }
4020
+ }
3715
4021
  const artResult = await checkArtifactoryConnectivity(cfg.artifactory);
3716
4022
  if (!artResult.configured) {
3717
4023
  results.artifactory = { status: "blank", message: artResult.error ?? "not configured" };
@@ -3722,11 +4028,48 @@ function registerConfigCommands(program2) {
3722
4028
  } else {
3723
4029
  results.artifactory = { status: "valid", message: "ok" };
3724
4030
  }
4031
+ if (!cfg.checkmarx.username || !cfg.checkmarx.password) {
4032
+ results.checkmarx = { status: "blank", message: "not configured" };
4033
+ } else if (!cfg.checkmarx.baseUrl) {
4034
+ results.checkmarx = { status: "error", message: "baseUrl not configured" };
4035
+ } else {
4036
+ try {
4037
+ await http.checkmarx("/cxrestapi/projects", { timeoutMs: 1e4 });
4038
+ results.checkmarx = { status: "valid", message: "ok" };
4039
+ } catch (err) {
4040
+ results.checkmarx = categorize2(err);
4041
+ }
4042
+ }
4043
+ const snCreds = cfg.servicenow.username && (cfg.servicenow.password || cfg.servicenow.apiToken);
4044
+ if (!snCreds) {
4045
+ results.servicenow = { status: "blank", message: "not configured" };
4046
+ } else if (!cfg.servicenow.baseUrl) {
4047
+ results.servicenow = { status: "error", message: "baseUrl not configured" };
4048
+ } else {
4049
+ try {
4050
+ await http.servicenow("/api/now/table/change_request", { params: { sysparm_limit: 1 }, timeoutMs: 1e4 });
4051
+ results.servicenow = { status: "valid", message: "ok" };
4052
+ } catch (err) {
4053
+ results.servicenow = categorize2(err);
4054
+ }
4055
+ }
4056
+ if (!cfg.contrast.apiKey || !cfg.contrast.serviceKey || !cfg.contrast.username) {
4057
+ results.contrast = { status: "blank", message: "not configured" };
4058
+ } else if (!cfg.contrast.orgUuid) {
4059
+ results.contrast = { status: "error", message: "orgUuid not configured" };
4060
+ } else {
4061
+ try {
4062
+ await http.contrast(`/Contrast/api/ng/${cfg.contrast.orgUuid}/applications`, { params: { limit: 1 }, timeoutMs: 1e4 });
4063
+ results.contrast = { status: "valid", message: "ok" };
4064
+ } catch (err) {
4065
+ results.contrast = categorize2(err);
4066
+ }
4067
+ }
3725
4068
  const statuses = Object.values(results).map((r) => r.status);
3726
4069
  if (statuses.includes("invalid")) process.exitCode = ExitCode.AUTH_ERROR;
3727
4070
  else if (statuses.includes("error")) process.exitCode = ExitCode.NETWORK_ERROR;
3728
4071
  if (cmdOpts.output === "table") {
3729
- const services = ["jira", "bitbucket", "confluence", "sonar", "sde", "ado", "artifactory"];
4072
+ const services = ["jira", "bitbucket", "confluence", "sonar", "sde", "ado", "jenkins", "udeploy", "artifactory", "checkmarx", "servicenow", "contrast"];
3730
4073
  const labelWidth = 14;
3731
4074
  const statusWidth = 9;
3732
4075
  for (const svc of services) {
@@ -3740,7 +4083,7 @@ function registerConfigCommands(program2) {
3740
4083
  process.stdout.write("\n");
3741
4084
  } else {
3742
4085
  if (opts.pretty) {
3743
- const services = ["jira", "bitbucket", "confluence", "sonar", "sde", "ado", "artifactory"];
4086
+ const services = ["jira", "bitbucket", "confluence", "sonar", "sde", "ado", "jenkins", "udeploy", "artifactory", "checkmarx", "servicenow", "contrast"];
3744
4087
  const labelWidth = 14;
3745
4088
  const statusWidth = 9;
3746
4089
  for (const svc of services) {
@@ -3951,6 +4294,236 @@ async function initGlobalConfig(start) {
3951
4294
  }
3952
4295
  }
3953
4296
  }
4297
+ 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");
4298
+ const useJenkins = await confirm({
4299
+ message: "Configure Jenkins Data Center for pipeline operations?",
4300
+ default: false
4301
+ });
4302
+ let jenkinsBaseUrl = "";
4303
+ let jenkinsUsername = "";
4304
+ let jenkinsApiToken = "";
4305
+ if (useJenkins) {
4306
+ jenkinsBaseUrl = await input({
4307
+ message: "Jenkins base URL (e.g. https://jenkins.your-company.com):",
4308
+ default: ""
4309
+ });
4310
+ jenkinsUsername = await input({
4311
+ message: "Jenkins username:",
4312
+ default: ""
4313
+ });
4314
+ jenkinsApiToken = await password({
4315
+ message: "Jenkins API token:"
4316
+ });
4317
+ }
4318
+ 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");
4319
+ const useUdeploy = await confirm({
4320
+ message: "Configure IBM UrbanCode Deploy for component imports and deployment processes?",
4321
+ default: false
4322
+ });
4323
+ let udeployBaseUrl = "";
4324
+ let udeployPat = "";
4325
+ let udeployUsername = "";
4326
+ let udeployPassword = "";
4327
+ let udeployDefaultApp = "";
4328
+ let udeployDefaultEnv = "";
4329
+ if (useUdeploy) {
4330
+ udeployBaseUrl = await input({
4331
+ message: "UDeploy base URL (e.g. https://ucd.company.com:8443):",
4332
+ default: ""
4333
+ });
4334
+ udeployUsername = await input({
4335
+ message: "UDeploy username (leave blank to use a PAT instead):",
4336
+ default: ""
4337
+ });
4338
+ if (udeployUsername) {
4339
+ udeployPassword = await password({
4340
+ message: "UDeploy password:",
4341
+ validate: (v) => v.length > 0 || "Password cannot be blank"
4342
+ });
4343
+ } else {
4344
+ udeployPat = await password({
4345
+ message: "UDeploy personal access token:"
4346
+ });
4347
+ }
4348
+ udeployDefaultApp = await input({
4349
+ message: "Default application name (optional):",
4350
+ default: ""
4351
+ });
4352
+ udeployDefaultEnv = await input({
4353
+ message: "Default environment name (optional):",
4354
+ default: ""
4355
+ });
4356
+ const hasCredentials = udeployBaseUrl && (udeployPat || udeployUsername && udeployPassword);
4357
+ if (hasCredentials) {
4358
+ process.stderr.write("\n Verifying connection...\n");
4359
+ try {
4360
+ const tempConfig = {
4361
+ ...loadConfig(),
4362
+ udeploy: {
4363
+ baseUrl: udeployBaseUrl,
4364
+ pat: udeployPat || void 0,
4365
+ username: udeployUsername || void 0,
4366
+ password: udeployPassword || void 0
4367
+ }
4368
+ };
4369
+ const tempHttp = createHttpClient(tempConfig);
4370
+ await tempHttp.udeploy("/cli/application");
4371
+ process.stderr.write(" Connected.\n");
4372
+ } catch (err) {
4373
+ warn(`Could not connect to UDeploy: ${err instanceof Error ? err.message : String(err)}`);
4374
+ warn("Config will be saved anyway. Check your URL and credentials and re-run pncli config init or pncli config test.");
4375
+ }
4376
+ }
4377
+ }
4378
+ 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");
4379
+ const useCheckmarx = await confirm({
4380
+ message: "Configure Checkmarx CxSAST for vulnerability scanning?",
4381
+ default: false
4382
+ });
4383
+ let checkmarxBaseUrl = "";
4384
+ let checkmarxUsername = "";
4385
+ let checkmarxPassword = "";
4386
+ if (useCheckmarx) {
4387
+ checkmarxBaseUrl = await input({
4388
+ message: "Checkmarx base URL (e.g. https://cx.company.com):",
4389
+ default: ""
4390
+ });
4391
+ checkmarxUsername = await input({
4392
+ message: "Checkmarx username:",
4393
+ default: ""
4394
+ });
4395
+ checkmarxPassword = await password({
4396
+ message: "Checkmarx password:",
4397
+ validate: (v) => v.length > 0 || "Password cannot be blank"
4398
+ });
4399
+ if (checkmarxBaseUrl && checkmarxUsername && checkmarxPassword) {
4400
+ process.stderr.write("\n Verifying connection...\n");
4401
+ try {
4402
+ const tempConfig = {
4403
+ ...loadConfig(),
4404
+ checkmarx: {
4405
+ baseUrl: checkmarxBaseUrl,
4406
+ username: checkmarxUsername,
4407
+ password: checkmarxPassword
4408
+ }
4409
+ };
4410
+ const tempHttp = createHttpClient(tempConfig);
4411
+ await tempHttp.checkmarx("/cxrestapi/projects");
4412
+ process.stderr.write(" Connected.\n");
4413
+ } catch (err) {
4414
+ warn(`Could not connect to Checkmarx: ${err instanceof Error ? err.message : String(err)}`);
4415
+ warn("Config will be saved anyway. Check your URL and credentials and re-run pncli config init or pncli config test.");
4416
+ }
4417
+ }
4418
+ }
4419
+ process.stderr.write("\n\u2500\u2500 ServiceNow \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");
4420
+ const useServiceNow = await confirm({
4421
+ message: "Configure ServiceNow for change management?",
4422
+ default: false
4423
+ });
4424
+ let servicenowBaseUrl = "";
4425
+ let servicenowUsername = "";
4426
+ let servicenowPassword = "";
4427
+ let servicenowApiToken = "";
4428
+ if (useServiceNow) {
4429
+ servicenowBaseUrl = await input({
4430
+ message: "ServiceNow instance URL (e.g. https://mycompany.service-now.com):",
4431
+ default: ""
4432
+ });
4433
+ servicenowUsername = await input({
4434
+ message: "ServiceNow username:",
4435
+ default: ""
4436
+ });
4437
+ const snUseToken = await confirm({
4438
+ message: "Authenticate with an API token instead of a password?",
4439
+ default: false
4440
+ });
4441
+ if (snUseToken) {
4442
+ servicenowApiToken = await password({
4443
+ message: "ServiceNow API token:",
4444
+ validate: (v) => v.length > 0 || "Token cannot be blank"
4445
+ });
4446
+ } else {
4447
+ servicenowPassword = await password({
4448
+ message: "ServiceNow password:",
4449
+ validate: (v) => v.length > 0 || "Password cannot be blank"
4450
+ });
4451
+ }
4452
+ const snHasCreds = servicenowBaseUrl && servicenowUsername && (servicenowPassword || servicenowApiToken);
4453
+ if (snHasCreds) {
4454
+ process.stderr.write("\n Verifying connection...\n");
4455
+ try {
4456
+ const tempConfig = {
4457
+ ...loadConfig(),
4458
+ servicenow: {
4459
+ baseUrl: servicenowBaseUrl,
4460
+ username: servicenowUsername,
4461
+ password: servicenowPassword || void 0,
4462
+ apiToken: servicenowApiToken || void 0
4463
+ }
4464
+ };
4465
+ const tempHttp = createHttpClient(tempConfig);
4466
+ await tempHttp.servicenow("/api/now/table/change_request", { params: { sysparm_limit: 1 } });
4467
+ process.stderr.write(" Connected.\n");
4468
+ } catch (err) {
4469
+ warn(`Could not connect to ServiceNow: ${err instanceof Error ? err.message : String(err)}`);
4470
+ warn("Config will be saved anyway. Check your URL and credentials and re-run pncli config init or pncli config test.");
4471
+ }
4472
+ }
4473
+ }
4474
+ 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
+ const useContrast = await confirm({
4476
+ message: "Configure Contrast Security IAST for vulnerability findings?",
4477
+ default: false
4478
+ });
4479
+ let contrastBaseUrl = "";
4480
+ let contrastOrgUuid = "";
4481
+ let contrastUsername = "";
4482
+ let contrastApiKey = "";
4483
+ let contrastServiceKey = "";
4484
+ if (useContrast) {
4485
+ process.stderr.write(" Find your credentials in Contrast under User Settings \u2192 Your Keys.\n");
4486
+ contrastBaseUrl = await input({
4487
+ message: "Contrast base URL (leave blank for cloud: https://app.contrastsecurity.com):",
4488
+ default: ""
4489
+ });
4490
+ contrastOrgUuid = await input({
4491
+ message: "Organization UUID:",
4492
+ default: ""
4493
+ });
4494
+ contrastUsername = await input({
4495
+ message: "Username (email):",
4496
+ default: ""
4497
+ });
4498
+ contrastApiKey = await password({
4499
+ message: "API key:"
4500
+ });
4501
+ contrastServiceKey = await password({
4502
+ message: "Service key:"
4503
+ });
4504
+ const contrastHasCreds = contrastOrgUuid && contrastUsername && contrastApiKey && contrastServiceKey;
4505
+ if (contrastHasCreds) {
4506
+ process.stderr.write("\n Verifying connection...\n");
4507
+ try {
4508
+ const tempConfig = {
4509
+ ...loadConfig(),
4510
+ contrast: {
4511
+ baseUrl: contrastBaseUrl || void 0,
4512
+ orgUuid: contrastOrgUuid,
4513
+ username: contrastUsername,
4514
+ apiKey: contrastApiKey,
4515
+ serviceKey: contrastServiceKey
4516
+ }
4517
+ };
4518
+ const tempHttp = createHttpClient(tempConfig);
4519
+ await tempHttp.contrast(`/Contrast/api/ng/${contrastOrgUuid}/applications`, { params: { limit: 1 } });
4520
+ process.stderr.write(" Connected.\n");
4521
+ } catch (err) {
4522
+ warn(`Could not connect to Contrast: ${err instanceof Error ? err.message : String(err)}`);
4523
+ warn("Config will be saved anyway. Check your credentials and re-run pncli config init or pncli config test.");
4524
+ }
4525
+ }
4526
+ }
3954
4527
  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");
3955
4528
  const jiraProject = await input({
3956
4529
  message: "Default Jira project key (optional):",
@@ -4022,6 +4595,45 @@ async function initGlobalConfig(start) {
4022
4595
  ...adoDiscoveredTypes.length ? { discoveredTypes: adoDiscoveredTypes } : {}
4023
4596
  }
4024
4597
  } : {},
4598
+ ...useJenkins && jenkinsBaseUrl ? {
4599
+ jenkins: {
4600
+ baseUrl: jenkinsBaseUrl,
4601
+ ...jenkinsUsername ? { username: jenkinsUsername } : {},
4602
+ ...jenkinsApiToken ? { apiToken: jenkinsApiToken } : {}
4603
+ }
4604
+ } : {},
4605
+ ...useUdeploy && udeployBaseUrl ? {
4606
+ udeploy: {
4607
+ baseUrl: udeployBaseUrl,
4608
+ ...udeployPat ? { pat: udeployPat } : {},
4609
+ ...udeployUsername ? { username: udeployUsername } : {},
4610
+ ...udeployPassword ? { password: udeployPassword } : {}
4611
+ }
4612
+ } : {},
4613
+ ...useCheckmarx && checkmarxBaseUrl ? {
4614
+ checkmarx: {
4615
+ baseUrl: checkmarxBaseUrl,
4616
+ username: checkmarxUsername || void 0,
4617
+ password: checkmarxPassword || void 0
4618
+ }
4619
+ } : {},
4620
+ ...useServiceNow && servicenowBaseUrl ? {
4621
+ servicenow: {
4622
+ baseUrl: servicenowBaseUrl,
4623
+ username: servicenowUsername || void 0,
4624
+ ...servicenowApiToken ? { apiToken: servicenowApiToken } : {},
4625
+ ...servicenowPassword ? { password: servicenowPassword } : {}
4626
+ }
4627
+ } : {},
4628
+ ...useContrast && contrastOrgUuid ? {
4629
+ contrast: {
4630
+ ...contrastBaseUrl ? { baseUrl: contrastBaseUrl } : {},
4631
+ orgUuid: contrastOrgUuid,
4632
+ username: contrastUsername || void 0,
4633
+ apiKey: contrastApiKey || void 0,
4634
+ serviceKey: contrastServiceKey || void 0
4635
+ }
4636
+ } : {},
4025
4637
  defaults: {
4026
4638
  jira: {
4027
4639
  project: jiraProject || void 0
@@ -4041,6 +4653,12 @@ async function initGlobalConfig(start) {
4041
4653
  collection: adoCollection || void 0,
4042
4654
  project: adoProject || void 0
4043
4655
  }
4656
+ } : {},
4657
+ ...useUdeploy && (udeployDefaultApp || udeployDefaultEnv) ? {
4658
+ udeploy: {
4659
+ application: udeployDefaultApp || void 0,
4660
+ environment: udeployDefaultEnv || void 0
4661
+ }
4044
4662
  } : {}
4045
4663
  }
4046
4664
  });
@@ -4471,7 +5089,7 @@ function registerAdoWorkCommands(ado) {
4471
5089
  fail(err, "ado", "work-get", start);
4472
5090
  }
4473
5091
  });
4474
- work.command("create").description("Create a work item").requiredOption("--type <type>", "Work item type (e.g. Bug, Task, User Story)").requiredOption("--title <title>", "Work item title").option("--description <text>", "Description").option("--assignee <user>", "Assigned to (display name or email)").option("--priority <n>", "Priority (1-4)").option("--field <name=value>", "Additional field (repeatable)", (v, acc) => {
5092
+ work.command("create").description("Create a work item").requiredOption("--type <type>", "Work item type (e.g. Bug, Task, User Story)").requiredOption("--title <title>", "Work item title").option("--description <text>", "Description").option("--assignee <user>", "Assigned to (display name or email)").option("--priority <n>", "Priority (1-4)").option("--parent <id>", "Parent work item ID \u2014 creates a parent link after creation").option("--field <name=value>", "Additional field (repeatable)", (v, acc) => {
4475
5093
  acc.push(v);
4476
5094
  return acc;
4477
5095
  }, []).action(async (opts) => {
@@ -4493,6 +5111,17 @@ function registerAdoWorkCommands(ado) {
4493
5111
  ];
4494
5112
  const data = await workClient.createWorkItem(collection, project, opts.type, patch);
4495
5113
  success(data, "ado", "work-create", start);
5114
+ if (opts.parent) {
5115
+ try {
5116
+ const baseUrl = config.ado.baseUrl?.replace(/\/$/, "");
5117
+ const targetUrl = `${baseUrl}/${encodeURIComponent(collection)}/_apis/wit/workitems/${opts.parent}`;
5118
+ const linkPatch = [{ op: "add", path: "/relations/-", value: { rel: "System.LinkTypes.Hierarchy-Reverse", url: targetUrl } }];
5119
+ await workClient.updateWorkItem(collection, data.id, linkPatch);
5120
+ } catch (linkErr) {
5121
+ warn(`Created #${data.id} but failed to link to parent ${opts.parent}: ${linkErr instanceof Error ? linkErr.message : linkErr}`);
5122
+ }
5123
+ }
5124
+ return;
4496
5125
  } catch (err) {
4497
5126
  fail(err, "ado", "work-create", start);
4498
5127
  }
@@ -4558,7 +5187,7 @@ function registerAdoWorkCommands(ado) {
4558
5187
  fail(err, "ado", "work-link", start);
4559
5188
  }
4560
5189
  });
4561
- work.command("search").description("Run a WIQL query").requiredOption("--wiql <query>", "WIQL query string").action(async (opts) => {
5190
+ work.command("search").description("Run a WIQL query (consider --output-file for large results)").requiredOption("--wiql <query>", "WIQL query string").action(async (opts) => {
4562
5191
  const start = Date.now();
4563
5192
  try {
4564
5193
  const { collection, project, workClient } = getAdoContext(ado);
@@ -4617,7 +5246,7 @@ function registerAdoWorkCommands(ado) {
4617
5246
  fail(err, "ado", "work-list-states", start);
4618
5247
  }
4619
5248
  });
4620
- work.command("fields").description("List work item fields available in the collection").option("--type <type>", "Scope to fields for a specific work item type (e.g. Bug)").option("--custom-only", "Exclude System.* and Microsoft.VSTS.* fields").option("--discover", "Fetch from server (always true for this command)").option("--save", "Save discovered fields and aliases to ~/.pncli/config.json").action(async (opts) => {
5249
+ work.command("fields").description("List work item fields available in the collection (consider --output-file for large results)").option("--type <type>", "Scope to fields for a specific work item type (e.g. Bug)").option("--custom-only", "Exclude System.* and Microsoft.VSTS.* fields").option("--discover", "Fetch from server (always true for this command)").option("--save", "Save discovered fields and aliases to ~/.pncli/config.json").action(async (opts) => {
4621
5250
  const start = Date.now();
4622
5251
  try {
4623
5252
  const { collection, project, workClient } = getAdoContext(ado);
@@ -4868,11 +5497,17 @@ function registerAdoRepoCommands(ado) {
4868
5497
  fail(err, "ado", "repo-list-files", start);
4869
5498
  }
4870
5499
  });
4871
- repo.command("diff").description("Show files changed in a pull request with change types and commit metadata").requiredOption("--pr <n>", "Pull request ID").action(async (opts) => {
5500
+ repo.command("diff").description("Show files changed in a pull request with change types and commit metadata").requiredOption("--pr <n>", "Pull request ID").option("--path <p>", "Filter diff to a specific file path").action(async (opts) => {
4872
5501
  const start = Date.now();
4873
5502
  try {
4874
5503
  const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
4875
5504
  const data = await gitClient.getPRDiff(collection, project, repo2, parseInt(opts.pr, 10));
5505
+ if (opts.path) {
5506
+ if (opts.path) {
5507
+ const needle = opts.path.startsWith("/") ? opts.path : `/${opts.path}`;
5508
+ data.changes = (data.changes ?? []).filter((c) => c.item.path === needle);
5509
+ }
5510
+ }
4876
5511
  success(data, "ado", "repo-diff", start);
4877
5512
  } catch (err) {
4878
5513
  fail(err, "ado", "repo-diff", start);
@@ -4903,11 +5538,11 @@ function registerAdoRepoCommands(ado) {
4903
5538
  const start = Date.now();
4904
5539
  try {
4905
5540
  const globalOpts = ado.optsWithGlobals();
4906
- const { loadConfig: lc } = await import("./config-IJ57IIQA.js");
5541
+ const { loadConfig: lc } = await import("./config-3VKBM32K.js");
4907
5542
  const config = lc({ configPath: globalOpts.config });
4908
5543
  const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
4909
- const { AdoCoreClient: AdoCoreClient2 } = await import("./core-AJYPJ2Z7.js");
4910
- const { createHttpClient: createHttpClient2 } = await import("./http-FC24JSK7.js");
5544
+ const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
5545
+ const { createHttpClient: createHttpClient2 } = await import("./http-GVHZQCEY.js");
4911
5546
  const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
4912
5547
  const coreClient = new AdoCoreClient2(http);
4913
5548
  const me = await coreClient.getConnectionData(collection);
@@ -5035,7 +5670,7 @@ function registerAdoPipelineCommands(ado) {
5035
5670
  fail(err, "ado", "pipeline-cancel-run", start);
5036
5671
  }
5037
5672
  });
5038
- pipeline.command("logs").description("List or retrieve build logs").requiredOption("--id <n>", "Build ID").option("--log-id <n>", "Specific log ID (omit to list all logs)").action(async (opts) => {
5673
+ pipeline.command("logs").description("List or retrieve build logs (consider --output-file for large logs)").requiredOption("--id <n>", "Build ID").option("--log-id <n>", "Specific log ID (omit to list all logs)").action(async (opts) => {
5039
5674
  const start = Date.now();
5040
5675
  try {
5041
5676
  const { collection, project, buildClient } = getAdoContext(ado);
@@ -5082,18 +5717,899 @@ function registerAdoCommands(program2) {
5082
5717
  registerAdoPipelineCommands(ado);
5083
5718
  }
5084
5719
 
5720
+ // src/services/jenkins/client.ts
5721
+ function jobPath(name) {
5722
+ return name.split("/").map(encodeURIComponent).join("/job/");
5723
+ }
5724
+ var JenkinsClient = class {
5725
+ constructor(http) {
5726
+ this.http = http;
5727
+ }
5728
+ http;
5729
+ async listJobs() {
5730
+ const data = await this.http.jenkins(
5731
+ "/api/json",
5732
+ { params: { tree: "jobs[name,url,color,description,buildable,lastBuild[number,url,result]]" } }
5733
+ );
5734
+ return data.jobs;
5735
+ }
5736
+ async getJob(name) {
5737
+ return this.http.jenkins(
5738
+ `/job/${jobPath(name)}/api/json`,
5739
+ { params: { tree: "name,url,color,description,buildable,nextBuildNumber,inQueue,lastBuild[number,url,result],builds[number,url,result,duration,timestamp,building,displayName]{0,10}" } }
5740
+ );
5741
+ }
5742
+ async triggerBuild(name, params) {
5743
+ const hasParams = params && Object.keys(params).length > 0;
5744
+ const endpoint = hasParams ? `/job/${jobPath(name)}/buildWithParameters` : `/job/${jobPath(name)}/build`;
5745
+ const res = await this.http.jenkinsRaw(endpoint, {
5746
+ method: "POST",
5747
+ params: hasParams ? params : void 0
5748
+ });
5749
+ const location = res.headers.get("Location") ?? "";
5750
+ const match = location.match(/\/queue\/item\/(\d+)\/?$/);
5751
+ if (!match) throw new PncliError("Build queued but Jenkins did not return a Location header \u2014 cannot track queue item");
5752
+ const queueItemId = parseInt(match[1], 10);
5753
+ return { queueItemId };
5754
+ }
5755
+ async getQueueItem(queueItemId) {
5756
+ return this.http.jenkins(`/queue/item/${queueItemId}/api/json`);
5757
+ }
5758
+ async listBuilds(name, top = 25) {
5759
+ const data = await this.http.jenkins(
5760
+ `/job/${jobPath(name)}/api/json`,
5761
+ { params: { tree: `builds[number,url,result,duration,timestamp,building,displayName,fullDisplayName,description,id,queueId]{0,${top}}` } }
5762
+ );
5763
+ return data.builds ?? [];
5764
+ }
5765
+ async getBuild(name, buildNumber) {
5766
+ return this.http.jenkins(
5767
+ `/job/${jobPath(name)}/${buildNumber}/api/json`
5768
+ );
5769
+ }
5770
+ async getConsoleLog(name, buildNumber) {
5771
+ const res = await this.http.jenkinsRaw(
5772
+ `/job/${jobPath(name)}/${buildNumber}/consoleText`,
5773
+ { method: "GET" }
5774
+ );
5775
+ return res.text;
5776
+ }
5777
+ };
5778
+
5779
+ // src/services/jenkins/commands.ts
5780
+ function getClient6(program2) {
5781
+ const opts = program2.optsWithGlobals();
5782
+ const config = loadConfig({ configPath: opts.config });
5783
+ if (!config.jenkins.baseUrl) throw new PncliError("Jenkins not configured. Run: pncli config init");
5784
+ const http = createHttpClient(config, Boolean(opts.dryRun));
5785
+ return new JenkinsClient(http);
5786
+ }
5787
+ async function pollQueueItem(client, queueItemId, pollMs, timeoutMs) {
5788
+ const deadline = Date.now() + timeoutMs;
5789
+ while (Date.now() < deadline) {
5790
+ const item = await client.getQueueItem(queueItemId);
5791
+ if (item.executable?.number !== void 0) return item.executable.number;
5792
+ if (item.why === null && !item.blocked && !item.buildable) {
5793
+ throw new PncliError("Build was cancelled while in queue", 1);
5794
+ }
5795
+ await new Promise((r) => setTimeout(r, pollMs));
5796
+ }
5797
+ throw new PncliError(`Timed out waiting for build to leave queue after ${timeoutMs / 1e3}s`, 1);
5798
+ }
5799
+ async function pollBuildComplete(client, jobName, buildNumber, pollMs, timeoutMs) {
5800
+ const deadline = Date.now() + timeoutMs;
5801
+ while (Date.now() < deadline) {
5802
+ const build = await client.getBuild(jobName, buildNumber);
5803
+ if (!build.building) return build;
5804
+ await new Promise((r) => setTimeout(r, pollMs));
5805
+ }
5806
+ throw new PncliError(`Timed out waiting for build #${buildNumber} to complete after ${timeoutMs / 1e3}s`, 1);
5807
+ }
5808
+ function registerJenkinsCommands(program2) {
5809
+ const jenkins = program2.command("jenkins").description("Jenkins Data Center operations");
5810
+ const pipeline = jenkins.command("pipeline").description("Pipeline (job/build) operations");
5811
+ pipeline.command("list").description("List all jobs on the Jenkins instance").action(async () => {
5812
+ const start = Date.now();
5813
+ try {
5814
+ const client = getClient6(program2);
5815
+ const data = await client.listJobs();
5816
+ success(data, "jenkins", "pipeline-list", start);
5817
+ } catch (err) {
5818
+ fail(err, "jenkins", "pipeline-list", start);
5819
+ }
5820
+ });
5821
+ pipeline.command("get").description("Get details for a specific job").requiredOption("--name <job>", "Job name").action(async (opts) => {
5822
+ const start = Date.now();
5823
+ try {
5824
+ const client = getClient6(program2);
5825
+ const data = await client.getJob(opts.name);
5826
+ success(data, "jenkins", "pipeline-get", start);
5827
+ } catch (err) {
5828
+ fail(err, "jenkins", "pipeline-get", start);
5829
+ }
5830
+ });
5831
+ pipeline.command("run").description("Trigger a build").requiredOption("--name <job>", "Job name").option("--parameter <k=v>", "Build parameter (repeatable)", (v, acc) => {
5832
+ acc.push(v);
5833
+ return acc;
5834
+ }, []).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) => {
5835
+ const start = Date.now();
5836
+ try {
5837
+ const client = getClient6(program2);
5838
+ for (const p of opts.parameter) {
5839
+ if (!p.includes("=")) throw new PncliError(`Invalid --parameter "${p}". Expected format: key=value`, 1);
5840
+ }
5841
+ const params = opts.parameter.length > 0 ? Object.fromEntries(opts.parameter.map((p) => {
5842
+ const eq = p.indexOf("=");
5843
+ return [p.slice(0, eq), p.slice(eq + 1)];
5844
+ })) : void 0;
5845
+ const { queueItemId } = await client.triggerBuild(opts.name, params);
5846
+ log(`Queued build (queue item #${queueItemId})`);
5847
+ if (!opts.wait) {
5848
+ success({ queueItemId }, "jenkins", "pipeline-run", start);
5849
+ return;
5850
+ }
5851
+ const timeoutSec = parseInt(opts.timeout, 10);
5852
+ const pollSec = parseInt(opts.poll, 10);
5853
+ if (isNaN(timeoutSec) || timeoutSec <= 0) throw new PncliError(`Invalid --timeout "${opts.timeout}". Expected a positive number of seconds.`, 1);
5854
+ if (isNaN(pollSec) || pollSec <= 0) throw new PncliError(`Invalid --poll "${opts.poll}". Expected a positive number of seconds.`, 1);
5855
+ log(`Waiting for build to start (timeout ${timeoutSec}s)...`);
5856
+ const buildNumber = await pollQueueItem(client, queueItemId, pollSec * 1e3, timeoutSec * 1e3);
5857
+ log(`Build #${buildNumber} started \u2014 waiting for completion...`);
5858
+ const build = await pollBuildComplete(client, opts.name, buildNumber, pollSec * 1e3, timeoutSec * 1e3);
5859
+ if (build.result !== null && build.result !== "SUCCESS") {
5860
+ process.exitCode = ExitCode.GENERAL_ERROR;
5861
+ }
5862
+ success(build, "jenkins", "pipeline-run", start);
5863
+ } catch (err) {
5864
+ fail(err, "jenkins", "pipeline-run", start);
5865
+ }
5866
+ });
5867
+ pipeline.command("list-runs").description("List recent builds for a job").requiredOption("--name <job>", "Job name").option("--top <n>", "Maximum number of builds to return (default 25)", "25").action(async (opts) => {
5868
+ const start = Date.now();
5869
+ try {
5870
+ const client = getClient6(program2);
5871
+ const top = parseInt(opts.top, 10);
5872
+ if (isNaN(top) || top <= 0) throw new PncliError(`Invalid --top "${opts.top}". Expected a positive integer.`, 1);
5873
+ const data = await client.listBuilds(opts.name, top);
5874
+ success(data, "jenkins", "pipeline-list-runs", start);
5875
+ } catch (err) {
5876
+ fail(err, "jenkins", "pipeline-list-runs", start);
5877
+ }
5878
+ });
5879
+ pipeline.command("get-run").description("Get details for a specific build").requiredOption("--name <job>", "Job name").requiredOption("--number <n>", "Build number").action(async (opts) => {
5880
+ const start = Date.now();
5881
+ try {
5882
+ const client = getClient6(program2);
5883
+ const buildNumber = parseInt(opts.number, 10);
5884
+ if (isNaN(buildNumber)) throw new PncliError(`Invalid --number "${opts.number}". Expected an integer.`, 1);
5885
+ const data = await client.getBuild(opts.name, buildNumber);
5886
+ success(data, "jenkins", "pipeline-get-run", start);
5887
+ } catch (err) {
5888
+ fail(err, "jenkins", "pipeline-get-run", start);
5889
+ }
5890
+ });
5891
+ pipeline.command("logs").description("Fetch console log for a build (consider --output-file for large logs)").requiredOption("--name <job>", "Job name").requiredOption("--number <n>", "Build number").action(async (opts) => {
5892
+ const start = Date.now();
5893
+ try {
5894
+ const client = getClient6(program2);
5895
+ const buildNumber = parseInt(opts.number, 10);
5896
+ if (isNaN(buildNumber)) throw new PncliError(`Invalid --number "${opts.number}". Expected an integer.`, 1);
5897
+ const text = await client.getConsoleLog(opts.name, buildNumber);
5898
+ success({ log: text }, "jenkins", "pipeline-logs", start);
5899
+ } catch (err) {
5900
+ fail(err, "jenkins", "pipeline-logs", start);
5901
+ }
5902
+ });
5903
+ }
5904
+
5905
+ // src/services/artifactory/client.ts
5906
+ var API9 = "api";
5907
+ var ArtifactoryClient = class {
5908
+ constructor(http) {
5909
+ this.http = http;
5910
+ }
5911
+ http;
5912
+ async ping() {
5913
+ return this.http.artifactoryText(`${API9}/system/ping`);
5914
+ }
5915
+ async listRepos(opts = {}) {
5916
+ return this.http.artifactory(`${API9}/repositories`, {
5917
+ params: {
5918
+ type: opts.type,
5919
+ packageType: opts.packageType
5920
+ }
5921
+ });
5922
+ }
5923
+ async getStorageInfo(repo, path6) {
5924
+ const cleanPath = path6.startsWith("/") ? path6.slice(1) : path6;
5925
+ return this.http.artifactory(`${API9}/storage/${repo}/${cleanPath}`);
5926
+ }
5927
+ async getProperties(repo, path6) {
5928
+ const cleanPath = path6.startsWith("/") ? path6.slice(1) : path6;
5929
+ return this.http.artifactory(`${API9}/storage/${repo}/${cleanPath}`, {
5930
+ params: { properties: "" }
5931
+ });
5932
+ }
5933
+ async setProperties(repo, path6, properties, opts = {}) {
5934
+ const cleanPath = path6.startsWith("/") ? path6.slice(1) : path6;
5935
+ const propString = Object.entries(properties).map(([k, v]) => `${k}=${v}`).join(";");
5936
+ await this.http.artifactory(`${API9}/storage/${repo}/${cleanPath}`, {
5937
+ method: "PUT",
5938
+ params: {
5939
+ properties: propString,
5940
+ ...opts.recursive ? { recursive: 1 } : {}
5941
+ }
5942
+ });
5943
+ }
5944
+ async searchAql(opts = {}) {
5945
+ const criteria = [];
5946
+ if (opts.repo) criteria.push(`"repo": {"$eq": "${opts.repo}"}`);
5947
+ if (opts.name) criteria.push(`"name": {"$match": "${opts.name}"}`);
5948
+ if (opts.path) criteria.push(`"path": {"$match": "${opts.path}"}`);
5949
+ if (opts.after) criteria.push(`"created": {"$gt": "${opts.after}"}`);
5950
+ if (opts.before) criteria.push(`"created": {"$lt": "${opts.before}"}`);
5951
+ const findClause = criteria.length > 0 ? `{${criteria.join(",")}}` : "{}";
5952
+ const includeFields = opts.includeProperties ? '.include("repo","path","name","type","size","created","modified","sha256","properties")' : '.include("repo","path","name","type","size","created","modified","sha256")';
5953
+ const sortClause = '.sort({"$desc": ["created"]})';
5954
+ const limitClause = opts.limit ? `.limit(${opts.limit})` : ".limit(100)";
5955
+ const aql = `items.find(${findClause})${includeFields}${sortClause}${limitClause}`;
5956
+ return this.http.artifactory(`${API9}/search/aql`, {
5957
+ method: "POST",
5958
+ body: aql,
5959
+ headers: { "Content-Type": "text/plain" }
5960
+ });
5961
+ }
5962
+ async listBuilds() {
5963
+ return this.http.artifactory(`${API9}/build`);
5964
+ }
5965
+ async listBuildRuns(buildName) {
5966
+ return this.http.artifactory(`${API9}/build/${encodeURIComponent(buildName)}`);
5967
+ }
5968
+ async getBuildInfo(buildName, buildNumber) {
5969
+ return this.http.artifactory(
5970
+ `${API9}/build/${encodeURIComponent(buildName)}/${encodeURIComponent(buildNumber)}`
5971
+ );
5972
+ }
5973
+ };
5974
+
5975
+ // src/services/artifactory/commands.ts
5976
+ function getClient7(program2) {
5977
+ const opts = program2.optsWithGlobals();
5978
+ const config = loadConfig({ configPath: opts.config });
5979
+ if (!config.artifactory.baseUrl) throw new PncliError("Artifactory not configured. Run: pncli config init");
5980
+ if (!config.artifactory.token) throw new PncliError("Artifactory token not configured. Run: pncli config init");
5981
+ const http = createHttpClient(config, Boolean(opts.dryRun));
5982
+ return new ArtifactoryClient(http);
5983
+ }
5984
+ function registerArtifactoryCommands(program2) {
5985
+ const art = program2.command("artifactory").description("Artifactory operations");
5986
+ art.command("ping").description("Check Artifactory connectivity and authentication").action(async () => {
5987
+ const start = Date.now();
5988
+ try {
5989
+ const client = getClient7(program2);
5990
+ const data = await client.ping();
5991
+ success(data, "artifactory", "ping", start);
5992
+ } catch (err) {
5993
+ fail(err, "artifactory", "ping", start);
5994
+ }
5995
+ });
5996
+ art.command("repos").description("List repositories").option("--type <type>", "Filter by type: local, virtual, remote, federated").option("--package-type <type>", "Filter by package type: npm, maven, nuget, docker, pypi, etc.").action(async (opts) => {
5997
+ const start = Date.now();
5998
+ try {
5999
+ const client = getClient7(program2);
6000
+ const data = await client.listRepos({ type: opts.type, packageType: opts.packageType });
6001
+ success(data, "artifactory", "repos", start);
6002
+ } catch (err) {
6003
+ fail(err, "artifactory", "repos", start);
6004
+ }
6005
+ });
6006
+ art.command("artifact-info <repo> <path>").description("Get metadata for an artifact or folder (checksums, size, timestamps)").action(async (repo, path6) => {
6007
+ const start = Date.now();
6008
+ try {
6009
+ const client = getClient7(program2);
6010
+ const data = await client.getStorageInfo(repo, path6);
6011
+ success(data, "artifactory", "artifact-info", start);
6012
+ } catch (err) {
6013
+ fail(err, "artifactory", "artifact-info", start);
6014
+ }
6015
+ });
6016
+ art.command("properties-get <repo> <path>").description("Read custom properties on an artifact").action(async (repo, path6) => {
6017
+ const start = Date.now();
6018
+ try {
6019
+ const client = getClient7(program2);
6020
+ const data = await client.getProperties(repo, path6);
6021
+ success(data, "artifactory", "properties-get", start);
6022
+ } catch (err) {
6023
+ fail(err, "artifactory", "properties-get", start);
6024
+ }
6025
+ });
6026
+ art.command("properties-set <repo> <path> [keyvals...]").description("Set custom properties on an artifact (format: key=value key=value...)").option("--recursive", "Apply recursively to folder contents", false).action(async (repo, path6, keyvals, opts) => {
6027
+ const start = Date.now();
6028
+ try {
6029
+ if (!keyvals.length) throw new PncliError("Provide at least one key=value pair");
6030
+ const properties = {};
6031
+ for (const kv of keyvals) {
6032
+ const eq = kv.indexOf("=");
6033
+ if (eq < 1) throw new PncliError(`Invalid property format: "${kv}" \u2014 expected key=value`);
6034
+ properties[kv.slice(0, eq)] = kv.slice(eq + 1);
6035
+ }
6036
+ const client = getClient7(program2);
6037
+ await client.setProperties(repo, path6, properties, { recursive: opts.recursive });
6038
+ success({ repo, path: path6, properties }, "artifactory", "properties-set", start);
6039
+ } catch (err) {
6040
+ fail(err, "artifactory", "properties-set", start);
6041
+ }
6042
+ });
6043
+ art.command("search").description("Search artifacts using AQL (Artifactory Query Language) (consider --output-file for large results)").option("--repo <name>", "Filter by repository key").option("--name <pattern>", "Filter by artifact name (supports * and ? wildcards)").option("--path <pattern>", "Filter by artifact path (supports * and ? wildcards)").option("--after <date>", "Created after this date (ISO 8601, e.g. 2024-01-01)").option("--before <date>", "Created before this date (ISO 8601)").option("--limit <n>", "Maximum results to return (default: 100)", "100").option("--properties", "Include artifact properties in results", false).action(async (opts) => {
6044
+ const start = Date.now();
6045
+ try {
6046
+ if (!opts.repo && !opts.name && !opts.path) {
6047
+ throw new PncliError("Provide at least one filter: --repo, --name, or --path");
6048
+ }
6049
+ const client = getClient7(program2);
6050
+ const data = await client.searchAql({
6051
+ repo: opts.repo,
6052
+ name: opts.name,
6053
+ path: opts.path,
6054
+ after: opts.after,
6055
+ before: opts.before,
6056
+ limit: Number.isNaN(parseInt(opts.limit, 10)) ? 100 : parseInt(opts.limit, 10),
6057
+ includeProperties: opts.properties
6058
+ });
6059
+ success(data, "artifactory", "search", start);
6060
+ } catch (err) {
6061
+ fail(err, "artifactory", "search", start);
6062
+ }
6063
+ });
6064
+ art.command("builds").description("List all build names in Artifactory").action(async () => {
6065
+ const start = Date.now();
6066
+ try {
6067
+ const client = getClient7(program2);
6068
+ const data = await client.listBuilds();
6069
+ success(data, "artifactory", "builds", start);
6070
+ } catch (err) {
6071
+ fail(err, "artifactory", "builds", start);
6072
+ }
6073
+ });
6074
+ art.command("build-runs <name>").description("List all run numbers for a build").action(async (name) => {
6075
+ const start = Date.now();
6076
+ try {
6077
+ const client = getClient7(program2);
6078
+ const data = await client.listBuildRuns(name);
6079
+ success(data, "artifactory", "build-runs", start);
6080
+ } catch (err) {
6081
+ fail(err, "artifactory", "build-runs", start);
6082
+ }
6083
+ });
6084
+ art.command("build-info <name> <number>").description("Get build info (artifacts, dependencies, VCS revision) for a specific run").action(async (name, number) => {
6085
+ const start = Date.now();
6086
+ try {
6087
+ const client = getClient7(program2);
6088
+ const data = await client.getBuildInfo(name, number);
6089
+ success(data, "artifactory", "build-info", start);
6090
+ } catch (err) {
6091
+ fail(err, "artifactory", "build-info", start);
6092
+ }
6093
+ });
6094
+ }
6095
+
6096
+ // src/services/udeploy/client.ts
6097
+ var UdeployClient = class {
6098
+ constructor(http) {
6099
+ this.http = http;
6100
+ }
6101
+ http;
6102
+ listApplications() {
6103
+ return this.http.udeploy("/cli/application");
6104
+ }
6105
+ listEnvironments(application) {
6106
+ return this.http.udeploy("/cli/application/environmentsInApplication", {
6107
+ params: { application }
6108
+ });
6109
+ }
6110
+ listComponents() {
6111
+ return this.http.udeploy("/cli/component");
6112
+ }
6113
+ listVersions(component) {
6114
+ return this.http.udeploy("/cli/component/versions", {
6115
+ params: { component }
6116
+ });
6117
+ }
6118
+ createVersion(component, name) {
6119
+ return this.http.udeploy("/cli/version/createVersion", {
6120
+ method: "POST",
6121
+ params: { component, name }
6122
+ });
6123
+ }
6124
+ finishImporting(component, version) {
6125
+ return this.http.udeploy("/cli/version/finishedImporting", {
6126
+ method: "POST",
6127
+ params: { component, version }
6128
+ });
6129
+ }
6130
+ runProcess(opts) {
6131
+ return this.http.udeploy("/cli/applicationProcessRequest/request", {
6132
+ method: "PUT",
6133
+ body: {
6134
+ application: opts.application,
6135
+ applicationProcess: opts.applicationProcess,
6136
+ environment: opts.environment,
6137
+ onlyChanged: opts.onlyChanged ?? false,
6138
+ ...opts.versions ? { versions: opts.versions } : {},
6139
+ ...opts.snapshot ? { snapshot: opts.snapshot } : {},
6140
+ ...opts.properties ? { properties: opts.properties } : {}
6141
+ }
6142
+ });
6143
+ }
6144
+ getRequestStatus(requestId) {
6145
+ return this.http.udeploy("/cli/applicationProcessRequest/requestStatus", {
6146
+ params: { requestId }
6147
+ });
6148
+ }
6149
+ getRequestInfo(requestId) {
6150
+ return this.http.udeploy("/cli/applicationProcessRequest/requestInfo", {
6151
+ params: { requestId }
6152
+ });
6153
+ }
6154
+ };
6155
+
6156
+ // src/services/udeploy/commands.ts
6157
+ var POLL_INTERVAL_MS = 5e3;
6158
+ var DEFAULT_TIMEOUT_MS = 10 * 60 * 1e3;
6159
+ function getClient8(program2) {
6160
+ const opts = program2.optsWithGlobals();
6161
+ const config = loadConfig({ configPath: opts.config });
6162
+ if (!config.udeploy.baseUrl) {
6163
+ throw new PncliError("UDeploy baseUrl not configured. Run: pncli config init", 78);
6164
+ }
6165
+ const http = createHttpClient(config, opts.dryRun);
6166
+ return { client: new UdeployClient(http), config };
6167
+ }
6168
+ function resolveApplication(config, cliValue) {
6169
+ const app = cliValue ?? config.defaults.udeploy.application;
6170
+ if (!app) throw new PncliError("--application is required (or set defaults.udeploy.application in config)", 2);
6171
+ return app;
6172
+ }
6173
+ function resolveEnvironment(config, cliValue) {
6174
+ const env = cliValue ?? config.defaults.udeploy.environment;
6175
+ if (!env) throw new PncliError("--environment is required (or set defaults.udeploy.environment in config)", 2);
6176
+ return env;
6177
+ }
6178
+ function registerUdeployCommands(program2) {
6179
+ 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");
6180
+ udeploy.command("apps").description("List all applications").action(async () => {
6181
+ const start = Date.now();
6182
+ try {
6183
+ const { client } = getClient8(program2);
6184
+ const apps = await client.listApplications();
6185
+ success(apps, "udeploy", "apps", start);
6186
+ } catch (err) {
6187
+ fail(err, "udeploy", "apps", start);
6188
+ }
6189
+ });
6190
+ udeploy.command("environments").description("List environments for an application").action(async () => {
6191
+ const start = Date.now();
6192
+ try {
6193
+ const { client, config } = getClient8(program2);
6194
+ const parentOpts = udeploy.opts();
6195
+ const application = resolveApplication(config, parentOpts.application);
6196
+ const envs = await client.listEnvironments(application);
6197
+ success(envs, "udeploy", "environments", start);
6198
+ } catch (err) {
6199
+ fail(err, "udeploy", "environments", start);
6200
+ }
6201
+ });
6202
+ udeploy.command("components").description("List all components").action(async () => {
6203
+ const start = Date.now();
6204
+ try {
6205
+ const { client } = getClient8(program2);
6206
+ const components = await client.listComponents();
6207
+ success(components, "udeploy", "components", start);
6208
+ } catch (err) {
6209
+ fail(err, "udeploy", "components", start);
6210
+ }
6211
+ });
6212
+ udeploy.command("versions").description("List versions for a component").requiredOption("--component <name>", "Component name or ID").action(async (cmdOpts) => {
6213
+ const start = Date.now();
6214
+ try {
6215
+ const { client } = getClient8(program2);
6216
+ const versions = await client.listVersions(cmdOpts.component);
6217
+ success(versions, "udeploy", "versions", start);
6218
+ } catch (err) {
6219
+ fail(err, "udeploy", "versions", start);
6220
+ }
6221
+ });
6222
+ 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) => {
6223
+ const start = Date.now();
6224
+ try {
6225
+ const { client } = getClient8(program2);
6226
+ const created = await client.createVersion(cmdOpts.component, cmdOpts.name);
6227
+ if (cmdOpts.finish) {
6228
+ await client.finishImporting(cmdOpts.component, cmdOpts.name);
6229
+ }
6230
+ success({ ...created, finishedImporting: cmdOpts.finish }, "udeploy", "import-version", start);
6231
+ } catch (err) {
6232
+ fail(err, "udeploy", "import-version", start);
6233
+ }
6234
+ });
6235
+ 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) => {
6236
+ const start = Date.now();
6237
+ try {
6238
+ const { client, config } = getClient8(program2);
6239
+ const parentOpts = udeploy.opts();
6240
+ const application = resolveApplication(config, parentOpts.application);
6241
+ const environment = resolveEnvironment(config, parentOpts.environment);
6242
+ const components = cmdOpts.component;
6243
+ const versions = cmdOpts.componentVersion;
6244
+ const versionEntries = components.map((c, i) => ({
6245
+ component: c,
6246
+ version: versions[i] ?? "latest"
6247
+ }));
6248
+ const result = await client.runProcess({
6249
+ application,
6250
+ applicationProcess: cmdOpts.process,
6251
+ environment,
6252
+ onlyChanged: cmdOpts.onlyChanged,
6253
+ ...versionEntries.length > 0 ? { versions: versionEntries } : {},
6254
+ ...cmdOpts.snapshot ? { snapshot: cmdOpts.snapshot } : {}
6255
+ });
6256
+ if (!cmdOpts.wait) {
6257
+ success(result, "udeploy", "run", start);
6258
+ return;
6259
+ }
6260
+ const timeoutMs = parseInt(cmdOpts.timeout, 10);
6261
+ const requestId = result.requestId;
6262
+ log(`Process started. Request ID: ${requestId}. Polling for completion...`);
6263
+ while (true) {
6264
+ const elapsed = Date.now() - start;
6265
+ if (elapsed >= timeoutMs) {
6266
+ throw new PncliError(`Timed out after ${timeoutMs}ms waiting for request ${requestId}`, 1);
6267
+ }
6268
+ await sleep(POLL_INTERVAL_MS);
6269
+ const status = await client.getRequestStatus(requestId);
6270
+ if (status.status === "CLOSED") {
6271
+ const outcome = { requestId, status: status.status, result: status.result, elapsed_ms: Date.now() - start };
6272
+ if (status.result === "SUCCEEDED") {
6273
+ success(outcome, "udeploy", "run", start);
6274
+ } else {
6275
+ throw new PncliError(
6276
+ `Deployment ${status.result ?? "failed"} (requestId: ${requestId})`,
6277
+ 1
6278
+ );
6279
+ }
6280
+ return;
6281
+ }
6282
+ log(`Status: ${status.status} (${Math.round((Date.now() - start) / 1e3)}s elapsed)`);
6283
+ }
6284
+ } catch (err) {
6285
+ fail(err, "udeploy", "run", start);
6286
+ }
6287
+ });
6288
+ 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) => {
6289
+ const start = Date.now();
6290
+ try {
6291
+ const { client } = getClient8(program2);
6292
+ const status = await client.getRequestStatus(cmdOpts.requestId);
6293
+ success(status, "udeploy", "request-status", start);
6294
+ } catch (err) {
6295
+ fail(err, "udeploy", "request-status", start);
6296
+ }
6297
+ });
6298
+ 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) => {
6299
+ const start = Date.now();
6300
+ try {
6301
+ const { client } = getClient8(program2);
6302
+ const info = await client.getRequestInfo(cmdOpts.requestId);
6303
+ success(info, "udeploy", "request-info", start);
6304
+ } catch (err) {
6305
+ fail(err, "udeploy", "request-info", start);
6306
+ }
6307
+ });
6308
+ }
6309
+ function collect(val, prev) {
6310
+ return [...prev, val];
6311
+ }
6312
+ function sleep(ms) {
6313
+ return new Promise((resolve) => setTimeout(resolve, ms));
6314
+ }
6315
+
6316
+ // src/services/checkmarx/client.ts
6317
+ var CheckmarxClient = class {
6318
+ constructor(http) {
6319
+ this.http = http;
6320
+ }
6321
+ http;
6322
+ async listProjects() {
6323
+ return this.http.checkmarx("/cxrestapi/projects");
6324
+ }
6325
+ async getProject(id) {
6326
+ return this.http.checkmarx(`/cxrestapi/projects/${id}`);
6327
+ }
6328
+ async listScans(opts = {}) {
6329
+ return this.http.checkmarx("/cxrestapi/sast/scans", {
6330
+ params: {
6331
+ projectId: opts.projectId,
6332
+ last: opts.last
6333
+ }
6334
+ });
6335
+ }
6336
+ async getScan(id) {
6337
+ return this.http.checkmarx(`/cxrestapi/sast/scans/${id}`);
6338
+ }
6339
+ async getScanResultsStatistics(scanId) {
6340
+ return this.http.checkmarx(`/cxrestapi/sast/scans/${scanId}/resultsStatistics`);
6341
+ }
6342
+ };
6343
+
6344
+ // src/services/checkmarx/commands.ts
6345
+ function getClient9(program2) {
6346
+ const opts = program2.optsWithGlobals();
6347
+ const config = loadConfig({ configPath: opts.config });
6348
+ if (!config.checkmarx.baseUrl) throw new PncliError("Checkmarx not configured. Run: pncli config init");
6349
+ const http = createHttpClient(config, Boolean(opts.dryRun));
6350
+ return new CheckmarxClient(http);
6351
+ }
6352
+ function registerCheckmarxCommands(program2) {
6353
+ const cx = program2.command("checkmarx").description("Checkmarx CxSAST operations");
6354
+ const project = cx.command("project").description("Project operations");
6355
+ const scan = cx.command("scan").description("Scan operations");
6356
+ project.command("list").description("List all projects").action(async () => {
6357
+ const start = Date.now();
6358
+ try {
6359
+ const data = await getClient9(program2).listProjects();
6360
+ success(data, "checkmarx", "project-list", start);
6361
+ } catch (err) {
6362
+ fail(err, "checkmarx", "project-list", start);
6363
+ }
6364
+ });
6365
+ project.command("get").description("Get a project by ID").requiredOption("--id <id>", "Project ID").action(async (opts) => {
6366
+ const start = Date.now();
6367
+ try {
6368
+ const id = parseInt(opts.id, 10);
6369
+ if (isNaN(id)) throw new PncliError(`Invalid --id "${opts.id}". Expected an integer.`, 1);
6370
+ const data = await getClient9(program2).getProject(id);
6371
+ success(data, "checkmarx", "project-get", start);
6372
+ } catch (err) {
6373
+ fail(err, "checkmarx", "project-get", start);
6374
+ }
6375
+ });
6376
+ scan.command("list").description("List scans").option("--project <id>", "Filter by project ID").option("--last <n>", "Return only the last N scans per project").action(async (opts) => {
6377
+ const start = Date.now();
6378
+ try {
6379
+ const projectId = opts.project ? parseInt(opts.project, 10) : void 0;
6380
+ const last = opts.last ? parseInt(opts.last, 10) : void 0;
6381
+ if (opts.project && isNaN(projectId)) throw new PncliError(`Invalid --project "${opts.project}". Expected an integer.`, 1);
6382
+ if (opts.last && isNaN(last)) throw new PncliError(`Invalid --last "${opts.last}". Expected an integer.`, 1);
6383
+ const data = await getClient9(program2).listScans({ projectId, last });
6384
+ success(data, "checkmarx", "scan-list", start);
6385
+ } catch (err) {
6386
+ fail(err, "checkmarx", "scan-list", start);
6387
+ }
6388
+ });
6389
+ scan.command("get").description("Get a scan by ID").requiredOption("--id <id>", "Scan ID").action(async (opts) => {
6390
+ const start = Date.now();
6391
+ try {
6392
+ const id = parseInt(opts.id, 10);
6393
+ if (isNaN(id)) throw new PncliError(`Invalid --id "${opts.id}". Expected an integer.`, 1);
6394
+ const data = await getClient9(program2).getScan(id);
6395
+ success(data, "checkmarx", "scan-get", start);
6396
+ } catch (err) {
6397
+ fail(err, "checkmarx", "scan-get", start);
6398
+ }
6399
+ });
6400
+ scan.command("stats").description("Get results statistics for a scan").requiredOption("--id <id>", "Scan ID").action(async (opts) => {
6401
+ const start = Date.now();
6402
+ try {
6403
+ const id = parseInt(opts.id, 10);
6404
+ if (isNaN(id)) throw new PncliError(`Invalid --id "${opts.id}". Expected an integer.`, 1);
6405
+ const data = await getClient9(program2).getScanResultsStatistics(id);
6406
+ success(data, "checkmarx", "scan-stats", start);
6407
+ } catch (err) {
6408
+ fail(err, "checkmarx", "scan-stats", start);
6409
+ }
6410
+ });
6411
+ }
6412
+
6413
+ // src/services/servicenow/commands.ts
6414
+ function getHttp(program2) {
6415
+ const opts = program2.optsWithGlobals();
6416
+ const config = loadConfig({ configPath: opts.config });
6417
+ if (!config.servicenow.baseUrl) throw new PncliError("ServiceNow not configured. Run: pncli config init");
6418
+ return createHttpClient(config, Boolean(opts.dryRun));
6419
+ }
6420
+ function registerServiceNowCommands(program2) {
6421
+ const sn = program2.command("servicenow").description("ServiceNow operations");
6422
+ const change = sn.command("change").description("Change request operations");
6423
+ change.command("list").description("List change requests").option("--state <state>", "Filter by state (e.g. -1=Pending, 1=Open, 2=Work in Progress, 3=Closed Complete)").option("--assigned-to <user>", "Filter by assigned user sys_id or display name").option("--limit <n>", "Maximum number of results", "25").option("--fields <fields>", "Comma-separated field names to return").action(async (opts) => {
6424
+ const start = Date.now();
6425
+ try {
6426
+ const http = getHttp(program2);
6427
+ const params = {
6428
+ sysparm_limit: opts.limit ? parseInt(opts.limit, 10) : 25,
6429
+ sysparm_display_value: "true"
6430
+ };
6431
+ const sanitize = (v) => v.replace(/\^/g, "");
6432
+ const queries = [];
6433
+ if (opts.state !== void 0) queries.push(`state=${sanitize(opts.state)}`);
6434
+ if (opts.assignedTo) queries.push(`assigned_to=${sanitize(opts.assignedTo)}`);
6435
+ if (queries.length) params["sysparm_query"] = queries.join("^");
6436
+ if (opts.fields) params["sysparm_fields"] = opts.fields;
6437
+ const data = await http.servicenow("/api/now/table/change_request", { params });
6438
+ success(data.result, "servicenow", "change list", start);
6439
+ } catch (err) {
6440
+ fail(err, "servicenow", "change list", start);
6441
+ }
6442
+ });
6443
+ change.command("get").description("Get a change request by sys_id or number").requiredOption("--id <id>", "Change request sys_id or number (e.g. CHG0001234)").action(async (opts) => {
6444
+ const start = Date.now();
6445
+ try {
6446
+ const http = getHttp(program2);
6447
+ if (opts.id.toUpperCase().startsWith("CHG")) {
6448
+ const list = await http.servicenow("/api/now/table/change_request", {
6449
+ params: { sysparm_query: `number=${opts.id}`, sysparm_display_value: "true", sysparm_limit: 1 }
6450
+ });
6451
+ if (!list.result.length) throw new PncliError(`Change request not found: ${opts.id}`, 1);
6452
+ success(list.result[0], "servicenow", "change get", start);
6453
+ return;
6454
+ }
6455
+ const data = await http.servicenow(`/api/now/table/change_request/${opts.id}`, {
6456
+ params: { sysparm_display_value: "true" }
6457
+ });
6458
+ success(data.result, "servicenow", "change get", start);
6459
+ } catch (err) {
6460
+ fail(err, "servicenow", "change get", start);
6461
+ }
6462
+ });
6463
+ change.command("create").description("Create a change request").requiredOption("--short-description <text>", "Short description (title)").option("--description <text>", "Full description").option("--type <type>", "Change type: normal, standard, or emergency", "normal").option("--priority <n>", "Priority: 1=Critical, 2=High, 3=Moderate, 4=Low").option("--assigned-to <user>", "Assigned user sys_id").option("--assignment-group <group>", "Assignment group sys_id").option("--start-date <datetime>", "Planned start date (yyyy-MM-dd HH:mm:ss)").option("--end-date <datetime>", "Planned end date (yyyy-MM-dd HH:mm:ss)").action(async (opts) => {
6464
+ const start = Date.now();
6465
+ try {
6466
+ const http = getHttp(program2);
6467
+ const body = {
6468
+ short_description: opts.shortDescription,
6469
+ type: opts.type ?? "normal"
6470
+ };
6471
+ if (opts.description) body["description"] = opts.description;
6472
+ if (opts.priority) body["priority"] = opts.priority;
6473
+ if (opts.assignedTo) body["assigned_to"] = opts.assignedTo;
6474
+ if (opts.assignmentGroup) body["assignment_group"] = opts.assignmentGroup;
6475
+ if (opts.startDate) body["start_date"] = opts.startDate;
6476
+ if (opts.endDate) body["end_date"] = opts.endDate;
6477
+ const data = await http.servicenow("/api/now/table/change_request", {
6478
+ method: "POST",
6479
+ body
6480
+ });
6481
+ success(data.result, "servicenow", "change create", start);
6482
+ } catch (err) {
6483
+ fail(err, "servicenow", "change create", start);
6484
+ }
6485
+ });
6486
+ change.command("update").description("Update a change request").requiredOption("--id <sys_id>", "Change request sys_id").option("--short-description <text>", "New short description").option("--description <text>", "New description").option("--state <state>", "New state value").option("--priority <n>", "New priority").option("--assigned-to <user>", "New assigned user sys_id").option("--start-date <datetime>", "New planned start date").option("--end-date <datetime>", "New planned end date").action(async (opts) => {
6487
+ const start = Date.now();
6488
+ try {
6489
+ const http = getHttp(program2);
6490
+ const body = {};
6491
+ if (opts.shortDescription) body["short_description"] = opts.shortDescription;
6492
+ if (opts.description) body["description"] = opts.description;
6493
+ if (opts.state !== void 0) body["state"] = opts.state;
6494
+ if (opts.priority) body["priority"] = opts.priority;
6495
+ if (opts.assignedTo) body["assigned_to"] = opts.assignedTo;
6496
+ if (opts.startDate) body["start_date"] = opts.startDate;
6497
+ if (opts.endDate) body["end_date"] = opts.endDate;
6498
+ if (Object.keys(body).length === 0) throw new PncliError("No fields to update. Provide at least one option besides --id.", 1);
6499
+ const data = await http.servicenow(`/api/now/table/change_request/${opts.id}`, {
6500
+ method: "PATCH",
6501
+ body
6502
+ });
6503
+ success(data.result, "servicenow", "change update", start);
6504
+ } catch (err) {
6505
+ fail(err, "servicenow", "change update", start);
6506
+ }
6507
+ });
6508
+ change.command("close").description("Close a change request (state=3)").requiredOption("--id <sys_id>", "Change request sys_id").option("--close-notes <text>", "Closure notes").option("--close-code <code>", "Close code (e.g. successful, unsuccessful)").action(async (opts) => {
6509
+ const start = Date.now();
6510
+ try {
6511
+ const http = getHttp(program2);
6512
+ const body = { state: 3 };
6513
+ if (opts.closeNotes) body["close_notes"] = opts.closeNotes;
6514
+ if (opts.closeCode) body["close_code"] = opts.closeCode;
6515
+ const data = await http.servicenow(`/api/now/table/change_request/${opts.id}`, {
6516
+ method: "PATCH",
6517
+ body
6518
+ });
6519
+ success(data.result, "servicenow", "change close", start);
6520
+ } catch (err) {
6521
+ fail(err, "servicenow", "change close", start);
6522
+ }
6523
+ });
6524
+ }
6525
+
6526
+ // src/services/contrast/commands.ts
6527
+ function getHttpAndOrg(program2) {
6528
+ const opts = program2.optsWithGlobals();
6529
+ const config = loadConfig({ configPath: opts.config });
6530
+ if (!config.contrast.apiKey || !config.contrast.serviceKey || !config.contrast.username) throw new PncliError("Contrast not configured. Run: pncli config init");
6531
+ if (!config.contrast.orgUuid) throw new PncliError("Contrast org UUID not configured. Run: pncli config set contrast.orgUuid <uuid>");
6532
+ const http = createHttpClient(config, Boolean(opts.dryRun));
6533
+ return { http, orgUuid: config.contrast.orgUuid };
6534
+ }
6535
+ function registerContrastCommands(program2) {
6536
+ const contrast = program2.command("contrast").description("Contrast IAST operations");
6537
+ const apps = contrast.command("apps").description("Application operations");
6538
+ apps.command("list").description("List applications in the organization").option("--limit <n>", "Maximum number of results", "25").option("--offset <n>", "Pagination offset", "0").action(async (opts) => {
6539
+ const start = Date.now();
6540
+ try {
6541
+ const { http, orgUuid } = getHttpAndOrg(program2);
6542
+ const data = await http.contrast(`/Contrast/api/ng/${orgUuid}/applications`, {
6543
+ params: {
6544
+ limit: opts.limit ? parseInt(opts.limit, 10) : 25,
6545
+ offset: opts.offset ? parseInt(opts.offset, 10) : 0
6546
+ }
6547
+ });
6548
+ success(data, "contrast", "apps list", start);
6549
+ } catch (err) {
6550
+ fail(err, "contrast", "apps list", start);
6551
+ }
6552
+ });
6553
+ const findings = contrast.command("findings").description("Vulnerability findings operations");
6554
+ findings.command("list").description("List vulnerability findings for an application").requiredOption("--app <app-id>", "Application ID (UUID)").option("--severity <level>", "Filter by severity: CRITICAL, HIGH, MEDIUM, LOW, NOTE").option("--status <status>", "Filter by status: REPORTED, CONFIRMED, SUSPICIOUS, NOT_A_PROBLEM, REMEDIATED, FIXED").option("--limit <n>", "Maximum number of results", "25").option("--offset <n>", "Pagination offset", "0").action(async (opts) => {
6555
+ const start = Date.now();
6556
+ try {
6557
+ const { http, orgUuid } = getHttpAndOrg(program2);
6558
+ const params = {
6559
+ limit: opts.limit ? parseInt(opts.limit, 10) : 25,
6560
+ offset: opts.offset ? parseInt(opts.offset, 10) : 0
6561
+ };
6562
+ if (opts.severity) params["severities"] = opts.severity;
6563
+ if (opts.status) params["statuses"] = opts.status;
6564
+ const data = await http.contrast(
6565
+ `/Contrast/api/ng/${orgUuid}/traces/${opts.app}/filter`,
6566
+ { params }
6567
+ );
6568
+ success(data, "contrast", "findings list", start);
6569
+ } catch (err) {
6570
+ fail(err, "contrast", "findings list", start);
6571
+ }
6572
+ });
6573
+ findings.command("get").description("Get details for a specific vulnerability finding").requiredOption("--app <app-id>", "Application ID (UUID)").requiredOption("--trace <trace-id>", "Trace/finding UUID").action(async (opts) => {
6574
+ const start = Date.now();
6575
+ try {
6576
+ const { http, orgUuid } = getHttpAndOrg(program2);
6577
+ const data = await http.contrast(
6578
+ `/Contrast/api/ng/${orgUuid}/traces/${opts.app}/trace/${opts.trace}`
6579
+ );
6580
+ success(data, "contrast", "findings get", start);
6581
+ } catch (err) {
6582
+ fail(err, "contrast", "findings get", start);
6583
+ }
6584
+ });
6585
+ }
6586
+
5085
6587
  // src/services/skills/commands.ts
5086
6588
  import fs3 from "fs";
5087
6589
  import path5 from "path";
5088
- var GITHUB_API = "https://api.github.com/repos/kolatts/pncli/contents/.claude/skills";
5089
- var RAW_BASE = "https://raw.githubusercontent.com/kolatts/pncli/main/.claude/skills";
5090
- var RAW_COPILOT_INSTRUCTIONS = "https://raw.githubusercontent.com/kolatts/pncli/main/copilot-instructions.md";
6590
+ 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";
6593
+ var AGENT_PATHS = {
6594
+ "github-copilot": { project: ".agents/skills", user: path5.join(os.homedir(), ".copilot/skills") },
6595
+ "claude-code": { project: ".claude/skills", user: path5.join(os.homedir(), ".claude/skills") }
6596
+ };
5091
6597
  function registerSkillsCommands(program2) {
5092
6598
  const skills = program2.command("skills").description("Manage pncli Claude Code skills");
5093
- skills.command("install").description("Download latest pncli skills and copilot-instructions.md into the current repo").option("--target <dir>", "Target directory for skills", ".claude/skills").option("--skip-instructions", "Skip downloading copilot-instructions.md (always written to repo root)").action(async (opts) => {
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) => {
5094
6600
  const start = Date.now();
5095
6601
  try {
5096
- const targetDir = path5.resolve(opts.target);
6602
+ let targetDir;
6603
+ if (opts.target) {
6604
+ targetDir = path5.resolve(opts.target);
6605
+ } else {
6606
+ const agentConfig = AGENT_PATHS[opts.agent];
6607
+ if (!agentConfig) {
6608
+ throw new Error(`Unknown agent: "${opts.agent}". Use: ${Object.keys(AGENT_PATHS).join(" | ")}`);
6609
+ }
6610
+ const scopePath = opts.scope === "user" ? agentConfig.user : agentConfig.project;
6611
+ targetDir = path5.resolve(scopePath);
6612
+ }
5097
6613
  warn("Fetching skill list from GitHub...");
5098
6614
  const listRes = await fetch(GITHUB_API, {
5099
6615
  headers: { "Accept": "application/vnd.github.v3+json", "User-Agent": "pncli" }
@@ -5114,7 +6630,7 @@ function registerSkillsCommands(program2) {
5114
6630
  fs3.rmSync(existingDir, { recursive: true, force: true });
5115
6631
  }
5116
6632
  }
5117
- warn(`Found ${skillDirs.length} skills. Downloading...`);
6633
+ warn(`Found ${skillDirs.length} skills. Downloading to ${targetDir}...`);
5118
6634
  const installed = [];
5119
6635
  const failed = [];
5120
6636
  for (const skillName of skillDirs) {
@@ -5144,54 +6660,60 @@ function registerSkillsCommands(program2) {
5144
6660
  if (failed.length > 0) {
5145
6661
  warn(`Failed to download: ${failed.join(", ")}`);
5146
6662
  }
5147
- let instructionsWritten = false;
5148
- if (!opts.skipInstructions) {
5149
- warn("Downloading copilot-instructions.md...");
5150
- try {
5151
- const res = await fetch(RAW_COPILOT_INSTRUCTIONS, {
5152
- headers: { "User-Agent": "pncli" }
5153
- });
5154
- if (res.ok) {
5155
- const content = await res.text();
5156
- fs3.writeFileSync("copilot-instructions.md", content, "utf8");
5157
- instructionsWritten = true;
5158
- warn("Wrote copilot-instructions.md");
5159
- } else {
5160
- warn(`Failed to download copilot-instructions.md (${res.status})`);
5161
- }
5162
- } catch {
5163
- warn("Failed to download copilot-instructions.md");
5164
- }
5165
- }
5166
6663
  success({
5167
6664
  installed,
5168
6665
  failed,
5169
6666
  target: targetDir,
5170
6667
  total: installed.length,
5171
- copilotInstructions: instructionsWritten
6668
+ agent: opts.target ? "custom" : opts.agent,
6669
+ scope: opts.target ? "custom" : opts.scope
5172
6670
  }, "skills", "install", start);
5173
6671
  } catch (err) {
5174
6672
  fail(err, "skills", "install", start);
5175
6673
  }
5176
6674
  });
5177
- skills.command("list").description("List locally installed skills").option("--target <dir>", "Skills directory to scan", ".claude/skills").action((opts) => {
6675
+ skills.command("list").description("List locally installed skills").option("--agent <agent>", "Target agent host: github-copilot | claude-code", "github-copilot").option("--scope <scope>", "Installation scope: project | user", "project").option("--target <dir>", "Override skills directory to scan").action((opts) => {
5178
6676
  const start = Date.now();
5179
6677
  try {
5180
- const targetDir = path5.resolve(opts.target);
6678
+ let targetDir;
6679
+ if (opts.target) {
6680
+ targetDir = path5.resolve(opts.target);
6681
+ } else {
6682
+ const agentConfig = AGENT_PATHS[opts.agent];
6683
+ if (!agentConfig) {
6684
+ throw new Error(`Unknown agent: "${opts.agent}". Use: ${Object.keys(AGENT_PATHS).join(" | ")}`);
6685
+ }
6686
+ const scopePath = opts.scope === "user" ? agentConfig.user : agentConfig.project;
6687
+ targetDir = path5.resolve(scopePath);
6688
+ }
5181
6689
  if (!fs3.existsSync(targetDir)) {
5182
- success({ skills: [], message: "No skills directory found. Run: pncli skills install" }, "skills", "list", start);
6690
+ success({ skills: [], message: `No skills directory found at ${targetDir}. Run: pncli skills install` }, "skills", "list", start);
5183
6691
  return;
5184
6692
  }
5185
6693
  const skillDirs = fs3.readdirSync(targetDir).filter((name) => {
5186
6694
  const skillPath = path5.join(targetDir, name, "SKILL.md");
5187
6695
  return fs3.existsSync(skillPath);
5188
6696
  });
5189
- const skills2 = skillDirs.map((name) => {
6697
+ const skillsList = skillDirs.map((name) => {
5190
6698
  const content = fs3.readFileSync(path5.join(targetDir, name, "SKILL.md"), "utf8");
5191
6699
  const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
5192
6700
  const data = {};
6701
+ const metadata = {};
5193
6702
  if (frontmatter) {
6703
+ let inMetadata = false;
5194
6704
  for (const line of frontmatter[1].split("\n")) {
6705
+ if (line.trimEnd() === "metadata:") {
6706
+ inMetadata = true;
6707
+ continue;
6708
+ }
6709
+ if (inMetadata && line.startsWith(" ")) {
6710
+ const colonIdx2 = line.indexOf(":");
6711
+ if (colonIdx2 !== -1) {
6712
+ metadata[line.slice(0, colonIdx2).trim()] = line.slice(colonIdx2 + 1).trim();
6713
+ }
6714
+ continue;
6715
+ }
6716
+ inMetadata = false;
5195
6717
  const colonIdx = line.indexOf(":");
5196
6718
  if (colonIdx === -1) continue;
5197
6719
  data[line.slice(0, colonIdx).trim()] = line.slice(colonIdx + 1).trim();
@@ -5200,12 +6722,13 @@ function registerSkillsCommands(program2) {
5200
6722
  return {
5201
6723
  name: data.name || name,
5202
6724
  slug: name,
5203
- category: data.category || "other",
5204
- services: data.services || "",
5205
- providers: data.providers || "none"
6725
+ category: metadata.category || data.category || "other",
6726
+ services: metadata.services || data.services || "",
6727
+ providers: metadata.providers || data.providers || "none",
6728
+ userInvocable: data["user-invocable"] === "true"
5206
6729
  };
5207
6730
  });
5208
- success({ skills: skills2, total: skills2.length }, "skills", "list", start);
6731
+ success({ skills: skillsList, total: skillsList.length }, "skills", "list", start);
5209
6732
  } catch (err) {
5210
6733
  fail(err, "skills", "list", start);
5211
6734
  }
@@ -5219,13 +6742,14 @@ if (!process.env.PNCLI_VERIFY_TLS) {
5219
6742
  var require2 = createRequire(import.meta.url);
5220
6743
  var pkg = require2("../package.json");
5221
6744
  var TAGLINE = "One command does what three meetings couldn't.";
5222
- var program = new Command();
5223
- program.name("pncli").description(`The Paperwork Nightmare CLI \u2014 ${TAGLINE}`).version(`${pkg.version} \u2014 ${TAGLINE}`, "-v, --version").option("--pretty", "Human-readable formatted output", false).option("--verbose", "Include full response metadata", false).option("--dry-run", "Print API requests without executing", false).option("--config <path>", "Override global config file location");
6745
+ var program = new Command2();
6746
+ program.name("pncli").description(`The Paperwork Nightmare CLI \u2014 ${TAGLINE}`).version(`${pkg.version} \u2014 ${TAGLINE}`, "-v, --version").option("--pretty", "Human-readable formatted output", false).option("--verbose", "Include full response metadata", false).option("--dry-run", "Print API requests without executing", false).option("--config <path>", "Override global config file location").option("--output-file <path>", "Write JSON output to file instead of stdout");
5224
6747
  program.hook("preAction", (thisCommand) => {
5225
6748
  const opts = thisCommand.optsWithGlobals();
5226
6749
  setGlobalOptions({
5227
6750
  pretty: Boolean(opts.pretty),
5228
- verbose: Boolean(opts.verbose)
6751
+ verbose: Boolean(opts.verbose),
6752
+ outputFile: opts.outputFile
5229
6753
  });
5230
6754
  try {
5231
6755
  const config = loadConfig({ configPath: opts.config });
@@ -5242,6 +6766,12 @@ registerSdeCommands(program);
5242
6766
  registerDepsCommands(program);
5243
6767
  registerConfigCommands(program);
5244
6768
  registerAdoCommands(program);
6769
+ registerJenkinsCommands(program);
6770
+ registerArtifactoryCommands(program);
6771
+ registerUdeployCommands(program);
6772
+ registerCheckmarxCommands(program);
6773
+ registerServiceNowCommands(program);
6774
+ registerContrastCommands(program);
5245
6775
  registerSkillsCommands(program);
5246
6776
  program.addHelpText("after", `
5247
6777
  Services:
@@ -5253,6 +6783,12 @@ Services:
5253
6783
  sonar SonarQube Server (quality gates, issues, metrics, hotspots)
5254
6784
  sde SDElements (threat modeling, countermeasures, compliance)
5255
6785
  ado Azure DevOps Server (work items, repos, PRs, pipelines)
6786
+ jenkins Jenkins Data Center (jobs, builds, logs)
6787
+ artifactory Artifactory (repos, artifact search, build info, properties)
6788
+ udeploy IBM UrbanCode Deploy (component versions, deployment processes)
6789
+ checkmarx Checkmarx CxSAST (projects, scans, scan statistics)
6790
+ servicenow ServiceNow (change requests)
6791
+ contrast Contrast IAST (applications, vulnerability findings)
5256
6792
  config Manage pncli configuration
5257
6793
  skills Download and manage Claude Code skills
5258
6794
  `);