@kolatts/pncli 1.15.0 → 1.17.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
@@ -8,7 +8,7 @@ import {
8
8
  setRepoConfigValue,
9
9
  writeGlobalConfig,
10
10
  writeRepoConfig
11
- } from "./chunk-CFUUOW7B.js";
11
+ } from "./chunk-FFVQ4PA7.js";
12
12
  import {
13
13
  ExitCode,
14
14
  PncliError,
@@ -20,13 +20,13 @@ import {
20
20
  success,
21
21
  warn,
22
22
  writeRawOutput
23
- } from "./chunk-YHRFL7VX.js";
23
+ } from "./chunk-XPPEXJBP.js";
24
24
  import {
25
25
  AdoCoreClient
26
26
  } from "./chunk-VDL32PSG.js";
27
27
 
28
28
  // src/cli.ts
29
- import { Command as Command2 } from "commander";
29
+ import { Command as Command3 } from "commander";
30
30
  import { createRequire } from "module";
31
31
 
32
32
  // src/services/git/client.ts
@@ -455,6 +455,42 @@ function parseAdoRemote(remoteUrl, adoBaseUrl) {
455
455
  const collection = decodeURIComponent(parts[parts.length - 2]);
456
456
  return { collection, project, repo };
457
457
  }
458
+ function parseGitHubRemote(remoteUrl, baseUrl) {
459
+ let targetHost;
460
+ if (baseUrl) {
461
+ try {
462
+ const normalizedBase = /^https?:\/\//i.test(baseUrl) ? baseUrl : `https://${baseUrl}`;
463
+ const hostname = new URL(normalizedBase).hostname.toLowerCase();
464
+ targetHost = hostname === "api.github.com" ? "github.com" : hostname;
465
+ } catch {
466
+ targetHost = baseUrl.replace(/\/$/, "").replace(/^https?:\/\//i, "").split(/[:/]/)[0].toLowerCase();
467
+ }
468
+ } else {
469
+ targetHost = "github.com";
470
+ }
471
+ const sshMatch = remoteUrl.match(/^git@([^:]+):([^/]+)\/([^/]+?)(?:\.git)?$/);
472
+ if (sshMatch) {
473
+ const [, host, owner, repo] = sshMatch;
474
+ if (host.toLowerCase() === targetHost) {
475
+ return { owner, repo };
476
+ }
477
+ }
478
+ const sshProtoMatch = remoteUrl.match(/^ssh:\/\/git@([^/:]+)(?::\d+)?\/([^/]+)\/([^/]+?)(?:\.git)?$/);
479
+ if (sshProtoMatch) {
480
+ const [, host, owner, repo] = sshProtoMatch;
481
+ if (host.toLowerCase() === targetHost) {
482
+ return { owner, repo };
483
+ }
484
+ }
485
+ const httpsMatch = remoteUrl.match(/^https?:\/\/([^/:]+)(?::\d+)?\/([^/]+)\/([^/]+?)(?:\.git)?$/);
486
+ if (httpsMatch) {
487
+ const [, host, owner, repo] = httpsMatch;
488
+ if (host.toLowerCase() === targetHost) {
489
+ return { owner, repo };
490
+ }
491
+ }
492
+ return null;
493
+ }
458
494
  function getRemoteUrls(repoRoot) {
459
495
  try {
460
496
  const output = execSync2("git remote -v", { encoding: "utf8", cwd: repoRoot });
@@ -486,7 +522,15 @@ function getGitContext(config) {
486
522
  break;
487
523
  }
488
524
  }
489
- return { root, branch, project, repo, ado: adoContext };
525
+ let githubContext = null;
526
+ for (const url of remoteUrls) {
527
+ const parsed = parseGitHubRemote(url, config.github.baseUrl);
528
+ if (parsed) {
529
+ githubContext = parsed;
530
+ break;
531
+ }
532
+ }
533
+ return { root, branch, project, repo, ado: adoContext, github: githubContext };
490
534
  }
491
535
 
492
536
  // src/services/bitbucket/client.ts
@@ -832,6 +876,7 @@ function guessMimeType(filePath) {
832
876
 
833
877
  // src/services/jira/client.ts
834
878
  var API2 = "/rest/api/2";
879
+ var AGILE_API = "/rest/agile/1.0";
835
880
  var JiraClient = class {
836
881
  constructor(http) {
837
882
  this.http = http;
@@ -1027,6 +1072,42 @@ var JiraClient = class {
1027
1072
  }
1028
1073
  });
1029
1074
  }
1075
+ async listBoards(project) {
1076
+ return this.http.jiraPaginate(async (startAt, maxResults) => {
1077
+ return this.http.jira(
1078
+ `${AGILE_API}/board`,
1079
+ { params: { projectKeyOrId: project, startAt, maxResults } }
1080
+ );
1081
+ });
1082
+ }
1083
+ async listSprintsForBoard(boardId, states) {
1084
+ return this.http.jiraPaginate(async (startAt, maxResults) => {
1085
+ return this.http.jira(
1086
+ `${AGILE_API}/board/${boardId}/sprint`,
1087
+ { params: { startAt, maxResults, ...states?.length ? { state: states.join(",") } : {} } }
1088
+ );
1089
+ });
1090
+ }
1091
+ async listSprintsForProject(project, states) {
1092
+ const boards = await this.listBoards(project);
1093
+ const sprints = [];
1094
+ const seen = /* @__PURE__ */ new Set();
1095
+ for (const board of boards) {
1096
+ if (board.type !== "scrum") continue;
1097
+ for (const sprint of await this.listSprintsForBoard(board.id, states)) {
1098
+ if (seen.has(sprint.id)) continue;
1099
+ seen.add(sprint.id);
1100
+ sprints.push(sprint);
1101
+ }
1102
+ }
1103
+ return sprints;
1104
+ }
1105
+ async setSprint(sprintId, issueKeys) {
1106
+ await this.http.jira(`${AGILE_API}/sprint/${sprintId}/issue`, {
1107
+ method: "POST",
1108
+ body: { issues: issueKeys }
1109
+ });
1110
+ }
1030
1111
  };
1031
1112
  function schemaToPncliType(schema) {
1032
1113
  if (!schema) return void 0;
@@ -1362,6 +1443,38 @@ function registerJiraCommands(program2) {
1362
1443
  fail(err, "jira", "fields", start);
1363
1444
  }
1364
1445
  });
1446
+ jira.command("list-boards").description("List Agile boards for a project").requiredOption("--project <key>", "Project key").action(async (opts) => {
1447
+ const start = Date.now();
1448
+ try {
1449
+ const client = getClient(program2);
1450
+ const data = await client.listBoards(opts.project);
1451
+ success(data, "jira", "list-boards", start);
1452
+ } catch (err) {
1453
+ fail(err, "jira", "list-boards", start);
1454
+ }
1455
+ });
1456
+ jira.command("list-sprints").description("List sprints for a board or project, including start/end dates").option("--board <id>", "Board ID").option("--project <key>", "Project key \u2014 resolves to that project's board(s)").option("--state <states>", "Comma-separated states to filter (active,future,closed)").action(async (opts) => {
1457
+ const start = Date.now();
1458
+ try {
1459
+ if (!opts.board && !opts.project) throw new PncliError("Either --board or --project is required", 1);
1460
+ const client = getClient(program2);
1461
+ const states = opts.state ? opts.state.split(",").map((s) => s.trim()) : void 0;
1462
+ const data = opts.board ? await client.listSprintsForBoard(parseInt(opts.board, 10), states) : await client.listSprintsForProject(opts.project, states);
1463
+ success(data, "jira", "list-sprints", start);
1464
+ } catch (err) {
1465
+ fail(err, "jira", "list-sprints", start);
1466
+ }
1467
+ });
1468
+ jira.command("set-sprint").description("Move a Jira issue into a sprint").requiredOption("--key <issue-key>", "Issue key").requiredOption("--sprint <id>", "Sprint ID (from list-sprints)").action(async (opts) => {
1469
+ const start = Date.now();
1470
+ try {
1471
+ const client = getClient(program2);
1472
+ await client.setSprint(parseInt(opts.sprint, 10), [opts.key]);
1473
+ success({ updated: opts.key, sprint: opts.sprint }, "jira", "set-sprint", start);
1474
+ } catch (err) {
1475
+ fail(err, "jira", "set-sprint", start);
1476
+ }
1477
+ });
1365
1478
  }
1366
1479
  function parseFieldArgs(fieldArgs, fieldMap) {
1367
1480
  const result = {};
@@ -1675,6 +1788,466 @@ function registerBitbucketCommands(program2) {
1675
1788
  });
1676
1789
  }
1677
1790
 
1791
+ // src/services/github/commands.ts
1792
+ import { Option } from "commander";
1793
+
1794
+ // src/services/github/client.ts
1795
+ var GitHubClient = class {
1796
+ constructor(http) {
1797
+ this.http = http;
1798
+ }
1799
+ http;
1800
+ async listPRs(opts) {
1801
+ return this.http.githubPaginate(
1802
+ (page, perPage) => this.http.github(
1803
+ `/repos/${opts.owner}/${opts.repo}/pulls`,
1804
+ {
1805
+ params: {
1806
+ state: opts.state ?? "open",
1807
+ ...opts.head ? { head: opts.head } : {},
1808
+ ...opts.base ? { base: opts.base } : {},
1809
+ per_page: perPage,
1810
+ page
1811
+ }
1812
+ }
1813
+ )
1814
+ );
1815
+ }
1816
+ async getPR(owner, repo, pullNumber) {
1817
+ return this.http.github(`/repos/${owner}/${repo}/pulls/${pullNumber}`);
1818
+ }
1819
+ async createPR(opts) {
1820
+ return this.http.github(
1821
+ `/repos/${opts.owner}/${opts.repo}/pulls`,
1822
+ {
1823
+ method: "POST",
1824
+ body: {
1825
+ title: opts.title,
1826
+ head: opts.head,
1827
+ base: opts.base,
1828
+ ...opts.body !== void 0 ? { body: opts.body } : {},
1829
+ ...opts.draft !== void 0 ? { draft: opts.draft } : {}
1830
+ }
1831
+ }
1832
+ );
1833
+ }
1834
+ async updatePR(opts) {
1835
+ const body = {};
1836
+ if (opts.title !== void 0) body.title = opts.title;
1837
+ if (opts.body !== void 0) body.body = opts.body;
1838
+ if (opts.state !== void 0) body.state = opts.state;
1839
+ if (opts.base !== void 0) body.base = opts.base;
1840
+ return this.http.github(
1841
+ `/repos/${opts.owner}/${opts.repo}/pulls/${opts.pullNumber}`,
1842
+ { method: "PATCH", body }
1843
+ );
1844
+ }
1845
+ async mergePR(opts) {
1846
+ const body = {};
1847
+ if (opts.commitTitle) body.commit_title = opts.commitTitle;
1848
+ if (opts.commitMessage) body.commit_message = opts.commitMessage;
1849
+ if (opts.mergeMethod) body.merge_method = opts.mergeMethod;
1850
+ return this.http.github(
1851
+ `/repos/${opts.owner}/${opts.repo}/pulls/${opts.pullNumber}/merge`,
1852
+ { method: "PUT", body }
1853
+ );
1854
+ }
1855
+ async closePR(owner, repo, pullNumber) {
1856
+ return this.updatePR({ owner, repo, pullNumber, state: "closed" });
1857
+ }
1858
+ async listComments(owner, repo, pullNumber) {
1859
+ return this.http.githubPaginate(
1860
+ (page, perPage) => this.http.github(
1861
+ `/repos/${owner}/${repo}/issues/${pullNumber}/comments`,
1862
+ { params: { per_page: perPage, page } }
1863
+ )
1864
+ );
1865
+ }
1866
+ async addComment(owner, repo, pullNumber, body) {
1867
+ return this.http.github(
1868
+ `/repos/${owner}/${repo}/issues/${pullNumber}/comments`,
1869
+ { method: "POST", body: { body } }
1870
+ );
1871
+ }
1872
+ async listReviewComments(owner, repo, pullNumber) {
1873
+ return this.http.githubPaginate(
1874
+ (page, perPage) => this.http.github(
1875
+ `/repos/${owner}/${repo}/pulls/${pullNumber}/comments`,
1876
+ { params: { per_page: perPage, page } }
1877
+ )
1878
+ );
1879
+ }
1880
+ async addInlineComment(opts) {
1881
+ return this.http.github(
1882
+ `/repos/${opts.owner}/${opts.repo}/pulls/${opts.pullNumber}/comments`,
1883
+ {
1884
+ method: "POST",
1885
+ body: {
1886
+ body: opts.body,
1887
+ commit_id: opts.commitId,
1888
+ path: opts.path,
1889
+ line: opts.line,
1890
+ side: opts.side ?? "RIGHT"
1891
+ }
1892
+ }
1893
+ );
1894
+ }
1895
+ async replyComment(owner, repo, pullNumber, commentId, body) {
1896
+ return this.http.github(
1897
+ `/repos/${owner}/${repo}/pulls/${pullNumber}/comments/${commentId}/replies`,
1898
+ { method: "POST", body: { body } }
1899
+ );
1900
+ }
1901
+ async deleteComment(owner, repo, commentId) {
1902
+ await this.http.github(
1903
+ `/repos/${owner}/${repo}/issues/comments/${commentId}`,
1904
+ { method: "DELETE" }
1905
+ );
1906
+ }
1907
+ async deleteReviewComment(owner, repo, commentId) {
1908
+ await this.http.github(
1909
+ `/repos/${owner}/${repo}/pulls/comments/${commentId}`,
1910
+ { method: "DELETE" }
1911
+ );
1912
+ }
1913
+ async getDiff(owner, repo, pullNumber) {
1914
+ return this.http.githubText(`/repos/${owner}/${repo}/pulls/${pullNumber}`);
1915
+ }
1916
+ async listFiles(owner, repo, pullNumber) {
1917
+ return this.http.githubPaginate(
1918
+ (page, perPage) => this.http.github(
1919
+ `/repos/${owner}/${repo}/pulls/${pullNumber}/files`,
1920
+ { params: { per_page: perPage, page } }
1921
+ )
1922
+ );
1923
+ }
1924
+ async listReviews(owner, repo, pullNumber) {
1925
+ return this.http.githubPaginate(
1926
+ (page, perPage) => this.http.github(
1927
+ `/repos/${owner}/${repo}/pulls/${pullNumber}/reviews`,
1928
+ { params: { per_page: perPage, page } }
1929
+ )
1930
+ );
1931
+ }
1932
+ async submitReview(opts) {
1933
+ return this.http.github(
1934
+ `/repos/${opts.owner}/${opts.repo}/pulls/${opts.pullNumber}/reviews`,
1935
+ {
1936
+ method: "POST",
1937
+ body: {
1938
+ event: opts.event,
1939
+ ...opts.body !== void 0 ? { body: opts.body } : {}
1940
+ }
1941
+ }
1942
+ );
1943
+ }
1944
+ async listRequestedReviewers(owner, repo, pullNumber) {
1945
+ return this.http.github(
1946
+ `/repos/${owner}/${repo}/pulls/${pullNumber}/requested_reviewers`
1947
+ );
1948
+ }
1949
+ async getCommitStatuses(owner, repo, ref) {
1950
+ return this.http.github(`/repos/${owner}/${repo}/commits/${ref}/status`);
1951
+ }
1952
+ async listCheckRuns(owner, repo, ref) {
1953
+ const results = [];
1954
+ let page = 1;
1955
+ const perPage = 100;
1956
+ while (true) {
1957
+ const result = await this.http.github(
1958
+ `/repos/${owner}/${repo}/commits/${ref}/check-runs`,
1959
+ { params: { per_page: perPage, page } }
1960
+ );
1961
+ const runs = result.check_runs ?? [];
1962
+ results.push(...runs);
1963
+ if (runs.length < perPage) break;
1964
+ page++;
1965
+ }
1966
+ return results;
1967
+ }
1968
+ };
1969
+
1970
+ // src/services/github/commands.ts
1971
+ function getClient3(program2, overrides) {
1972
+ const opts = program2.optsWithGlobals();
1973
+ const config = loadConfig({ configPath: opts.config });
1974
+ const http = createHttpClient(config, Boolean(opts.dryRun));
1975
+ const client = new GitHubClient(http);
1976
+ const ctx = getGitContext(config);
1977
+ const owner = overrides?.owner ?? opts.owner ?? ctx?.github?.owner ?? config.defaults.github?.owner ?? "";
1978
+ const repo = overrides?.repo ?? opts.repo ?? ctx?.github?.repo ?? config.defaults.github?.repo ?? "";
1979
+ if (!owner || !repo) {
1980
+ throw new PncliError(
1981
+ "Could not determine GitHub owner/repo. Pass --owner and --repo, or run pncli config init.",
1982
+ 1
1983
+ );
1984
+ }
1985
+ return { client, owner, repo, config };
1986
+ }
1987
+ function registerGitHubCommands(program2) {
1988
+ const gh = program2.command("github").description("GitHub repository operations").option("--owner <owner>", "GitHub owner (user or org)").option("--repo <repo>", "GitHub repository name");
1989
+ gh.command("list-prs").description("List pull requests").addOption(new Option("--state <state>", "PR state").choices(["open", "closed", "all"]).default("open")).option("--head <branch>", "Filter by head branch (user:branch)").option("--base <branch>", "Filter by base branch").action(async (opts) => {
1990
+ const start = Date.now();
1991
+ try {
1992
+ const { client, owner, repo } = getClient3(gh);
1993
+ const data = await client.listPRs({
1994
+ owner,
1995
+ repo,
1996
+ state: opts.state,
1997
+ head: opts.head,
1998
+ base: opts.base
1999
+ });
2000
+ success(data, "github", "list-prs", start);
2001
+ } catch (err) {
2002
+ fail(err, "github", "list-prs", start);
2003
+ }
2004
+ });
2005
+ gh.command("get-pr").description("Get a pull request by number").requiredOption("--number <n>", "Pull request number").action(async (opts) => {
2006
+ const start = Date.now();
2007
+ try {
2008
+ const { client, owner, repo } = getClient3(gh);
2009
+ const data = await client.getPR(owner, repo, parseInt(opts.number, 10));
2010
+ success(data, "github", "get-pr", start);
2011
+ } catch (err) {
2012
+ fail(err, "github", "get-pr", start);
2013
+ }
2014
+ });
2015
+ gh.command("create-pr").description("Create a pull request").requiredOption("--title <title>", "PR title").requiredOption("--head <branch>", "Source branch (or user:branch for forks)").option("--base <branch>", "Target branch (defaults to config or main)").option("--body <body>", "PR description").option("--draft", "Create as draft PR").option("--owner <owner>", "GitHub owner (overrides parent --owner)").option("--repo <repo>", "GitHub repository name (overrides parent --repo)").action(async (opts) => {
2016
+ const start = Date.now();
2017
+ try {
2018
+ const { client, owner, repo, config } = getClient3(gh, { owner: opts.owner, repo: opts.repo });
2019
+ const base = opts.base ?? config.defaults.github?.targetBranch ?? "main";
2020
+ const data = await client.createPR({
2021
+ owner,
2022
+ repo,
2023
+ title: opts.title,
2024
+ head: opts.head,
2025
+ base,
2026
+ body: opts.body,
2027
+ draft: opts.draft
2028
+ });
2029
+ success(data, "github", "create-pr", start);
2030
+ } catch (err) {
2031
+ fail(err, "github", "create-pr", start);
2032
+ }
2033
+ });
2034
+ gh.command("update-pr").description("Update a pull request").requiredOption("--number <n>", "Pull request number").option("--title <title>", "New title").option("--body <body>", "New description").option("--base <branch>", "New base branch").action(async (opts) => {
2035
+ const start = Date.now();
2036
+ try {
2037
+ const { client, owner, repo } = getClient3(gh);
2038
+ const data = await client.updatePR({
2039
+ owner,
2040
+ repo,
2041
+ pullNumber: parseInt(opts.number, 10),
2042
+ title: opts.title,
2043
+ body: opts.body,
2044
+ base: opts.base
2045
+ });
2046
+ success(data, "github", "update-pr", start);
2047
+ } catch (err) {
2048
+ fail(err, "github", "update-pr", start);
2049
+ }
2050
+ });
2051
+ gh.command("merge-pr").description("Merge a pull request").requiredOption("--number <n>", "Pull request number").addOption(new Option("--method <method>", "Merge method").choices(["merge", "squash", "rebase"]).default("merge")).option("--commit-title <title>", "Commit title for merge/squash").option("--commit-message <msg>", "Commit message for merge/squash").action(async (opts) => {
2052
+ const start = Date.now();
2053
+ try {
2054
+ const { client, owner, repo } = getClient3(gh);
2055
+ const data = await client.mergePR({
2056
+ owner,
2057
+ repo,
2058
+ pullNumber: parseInt(opts.number, 10),
2059
+ mergeMethod: opts.method,
2060
+ commitTitle: opts.commitTitle,
2061
+ commitMessage: opts.commitMessage
2062
+ });
2063
+ success(data, "github", "merge-pr", start);
2064
+ } catch (err) {
2065
+ fail(err, "github", "merge-pr", start);
2066
+ }
2067
+ });
2068
+ gh.command("close-pr").description("Close a pull request").requiredOption("--number <n>", "Pull request number").action(async (opts) => {
2069
+ const start = Date.now();
2070
+ try {
2071
+ const { client, owner, repo } = getClient3(gh);
2072
+ const data = await client.closePR(owner, repo, parseInt(opts.number, 10));
2073
+ success(data, "github", "close-pr", start);
2074
+ } catch (err) {
2075
+ fail(err, "github", "close-pr", start);
2076
+ }
2077
+ });
2078
+ gh.command("list-comments").description("List general comments on a pull request").requiredOption("--number <n>", "Pull request number").action(async (opts) => {
2079
+ const start = Date.now();
2080
+ try {
2081
+ const { client, owner, repo } = getClient3(gh);
2082
+ const data = await client.listComments(owner, repo, parseInt(opts.number, 10));
2083
+ success(data, "github", "list-comments", start);
2084
+ } catch (err) {
2085
+ fail(err, "github", "list-comments", start);
2086
+ }
2087
+ });
2088
+ gh.command("add-comment").description("Add a general comment to a pull request").requiredOption("--number <n>", "Pull request number").requiredOption("--body <text>", "Comment text").action(async (opts) => {
2089
+ const start = Date.now();
2090
+ try {
2091
+ const { client, owner, repo } = getClient3(gh);
2092
+ const data = await client.addComment(owner, repo, parseInt(opts.number, 10), opts.body);
2093
+ success(data, "github", "add-comment", start);
2094
+ } catch (err) {
2095
+ fail(err, "github", "add-comment", start);
2096
+ }
2097
+ });
2098
+ gh.command("list-review-comments").description("List inline review comments on a pull request").requiredOption("--number <n>", "Pull request number").action(async (opts) => {
2099
+ const start = Date.now();
2100
+ try {
2101
+ const { client, owner, repo } = getClient3(gh);
2102
+ const data = await client.listReviewComments(owner, repo, parseInt(opts.number, 10));
2103
+ success(data, "github", "list-review-comments", start);
2104
+ } catch (err) {
2105
+ fail(err, "github", "list-review-comments", start);
2106
+ }
2107
+ });
2108
+ gh.command("add-inline-comment").description("Add an inline review comment to a file in a pull request").requiredOption("--number <n>", "Pull request number").requiredOption("--commit <sha>", "Commit SHA the comment is on").requiredOption("--file <path>", "File path").requiredOption("--line <n>", "Line number").requiredOption("--body <text>", "Comment text").addOption(new Option("--side <side>", "Comment side").choices(["LEFT", "RIGHT"]).default("RIGHT")).action(async (opts) => {
2109
+ const start = Date.now();
2110
+ try {
2111
+ const { client, owner, repo } = getClient3(gh);
2112
+ const data = await client.addInlineComment({
2113
+ owner,
2114
+ repo,
2115
+ pullNumber: parseInt(opts.number, 10),
2116
+ body: opts.body,
2117
+ commitId: opts.commit,
2118
+ path: opts.file,
2119
+ line: parseInt(opts.line, 10),
2120
+ side: opts.side
2121
+ });
2122
+ success(data, "github", "add-inline-comment", start);
2123
+ } catch (err) {
2124
+ fail(err, "github", "add-inline-comment", start);
2125
+ }
2126
+ });
2127
+ gh.command("reply-comment").description("Reply to a review comment on a pull request").requiredOption("--number <n>", "Pull request number").requiredOption("--comment-id <id>", "Review comment ID to reply to").requiredOption("--body <text>", "Reply text").action(async (opts) => {
2128
+ const start = Date.now();
2129
+ try {
2130
+ const { client, owner, repo } = getClient3(gh);
2131
+ const data = await client.replyComment(owner, repo, parseInt(opts.number, 10), parseInt(opts.commentId, 10), opts.body);
2132
+ success(data, "github", "reply-comment", start);
2133
+ } catch (err) {
2134
+ fail(err, "github", "reply-comment", start);
2135
+ }
2136
+ });
2137
+ gh.command("delete-comment").description("Delete a general issue comment").requiredOption("--comment-id <id>", "Comment ID").action(async (opts) => {
2138
+ const start = Date.now();
2139
+ try {
2140
+ const { client, owner, repo } = getClient3(gh);
2141
+ await client.deleteComment(owner, repo, parseInt(opts.commentId, 10));
2142
+ success({ deleted: true }, "github", "delete-comment", start);
2143
+ } catch (err) {
2144
+ fail(err, "github", "delete-comment", start);
2145
+ }
2146
+ });
2147
+ gh.command("delete-review-comment").description("Delete an inline review comment").requiredOption("--comment-id <id>", "Review comment ID").action(async (opts) => {
2148
+ const start = Date.now();
2149
+ try {
2150
+ const { client, owner, repo } = getClient3(gh);
2151
+ await client.deleteReviewComment(owner, repo, parseInt(opts.commentId, 10));
2152
+ success({ deleted: true }, "github", "delete-review-comment", start);
2153
+ } catch (err) {
2154
+ fail(err, "github", "delete-review-comment", start);
2155
+ }
2156
+ });
2157
+ gh.command("diff").description("Get unified diff for a pull request").requiredOption("--number <n>", "Pull request number").action(async (opts) => {
2158
+ const start = Date.now();
2159
+ try {
2160
+ const { client, owner, repo } = getClient3(gh);
2161
+ const diff = await client.getDiff(owner, repo, parseInt(opts.number, 10));
2162
+ success({ diff }, "github", "diff", start);
2163
+ } catch (err) {
2164
+ fail(err, "github", "diff", start);
2165
+ }
2166
+ });
2167
+ gh.command("list-files").description("List files changed in a pull request").requiredOption("--number <n>", "Pull request number").action(async (opts) => {
2168
+ const start = Date.now();
2169
+ try {
2170
+ const { client, owner, repo } = getClient3(gh);
2171
+ const data = await client.listFiles(owner, repo, parseInt(opts.number, 10));
2172
+ success(data, "github", "list-files", start);
2173
+ } catch (err) {
2174
+ fail(err, "github", "list-files", start);
2175
+ }
2176
+ });
2177
+ gh.command("list-reviews").description("List reviews on a pull request").requiredOption("--number <n>", "Pull request number").action(async (opts) => {
2178
+ const start = Date.now();
2179
+ try {
2180
+ const { client, owner, repo } = getClient3(gh);
2181
+ const data = await client.listReviews(owner, repo, parseInt(opts.number, 10));
2182
+ success(data, "github", "list-reviews", start);
2183
+ } catch (err) {
2184
+ fail(err, "github", "list-reviews", start);
2185
+ }
2186
+ });
2187
+ gh.command("approve").description("Approve a pull request").requiredOption("--number <n>", "Pull request number").option("--body <text>", "Review body text").action(async (opts) => {
2188
+ const start = Date.now();
2189
+ try {
2190
+ const { client, owner, repo } = getClient3(gh);
2191
+ const data = await client.submitReview({
2192
+ owner,
2193
+ repo,
2194
+ pullNumber: parseInt(opts.number, 10),
2195
+ event: "APPROVE",
2196
+ body: opts.body
2197
+ });
2198
+ success(data, "github", "approve", start);
2199
+ } catch (err) {
2200
+ fail(err, "github", "approve", start);
2201
+ }
2202
+ });
2203
+ gh.command("request-changes").description("Request changes on a pull request").requiredOption("--number <n>", "Pull request number").requiredOption("--body <text>", "Review feedback text").action(async (opts) => {
2204
+ const start = Date.now();
2205
+ try {
2206
+ const { client, owner, repo } = getClient3(gh);
2207
+ const data = await client.submitReview({
2208
+ owner,
2209
+ repo,
2210
+ pullNumber: parseInt(opts.number, 10),
2211
+ event: "REQUEST_CHANGES",
2212
+ body: opts.body
2213
+ });
2214
+ success(data, "github", "request-changes", start);
2215
+ } catch (err) {
2216
+ fail(err, "github", "request-changes", start);
2217
+ }
2218
+ });
2219
+ gh.command("list-reviewers").description("List requested reviewers for a pull request").requiredOption("--number <n>", "Pull request number").action(async (opts) => {
2220
+ const start = Date.now();
2221
+ try {
2222
+ const { client, owner, repo } = getClient3(gh);
2223
+ const data = await client.listRequestedReviewers(owner, repo, parseInt(opts.number, 10));
2224
+ success(data, "github", "list-reviewers", start);
2225
+ } catch (err) {
2226
+ fail(err, "github", "list-reviewers", start);
2227
+ }
2228
+ });
2229
+ gh.command("get-status").description("Get combined commit status for a ref (branch, tag, or SHA)").requiredOption("--ref <ref>", "Git ref (branch, tag, or commit SHA)").action(async (opts) => {
2230
+ const start = Date.now();
2231
+ try {
2232
+ const { client, owner, repo } = getClient3(gh);
2233
+ const data = await client.getCommitStatuses(owner, repo, opts.ref);
2234
+ success(data, "github", "get-status", start);
2235
+ } catch (err) {
2236
+ fail(err, "github", "get-status", start);
2237
+ }
2238
+ });
2239
+ gh.command("list-checks").description("List check runs for a ref (branch, tag, or SHA)").requiredOption("--ref <ref>", "Git ref (branch, tag, or commit SHA)").action(async (opts) => {
2240
+ const start = Date.now();
2241
+ try {
2242
+ const { client, owner, repo } = getClient3(gh);
2243
+ const data = await client.listCheckRuns(owner, repo, opts.ref);
2244
+ success(data, "github", "list-checks", start);
2245
+ } catch (err) {
2246
+ fail(err, "github", "list-checks", start);
2247
+ }
2248
+ });
2249
+ }
2250
+
1678
2251
  // src/services/confluence/client.ts
1679
2252
  var API3 = "/rest/api";
1680
2253
  var ConfluenceClient = class {
@@ -1823,7 +2396,7 @@ var ConfluenceClient = class {
1823
2396
  };
1824
2397
 
1825
2398
  // src/services/confluence/commands.ts
1826
- function getClient3(program2) {
2399
+ function getClient4(program2) {
1827
2400
  const opts = program2.optsWithGlobals();
1828
2401
  const config = loadConfig({ configPath: opts.config });
1829
2402
  if (!config.confluence.baseUrl) throw new PncliError("Confluence not configured. Run: pncli config init");
@@ -1835,7 +2408,7 @@ function registerConfluenceCommands(program2) {
1835
2408
  confluence.command("get-page").description("Get a Confluence page by ID").requiredOption("--id <page-id>", "Page ID").option("--expand <fields>", "Comma-separated fields to expand", "body.storage,version,space,ancestors").action(async (opts) => {
1836
2409
  const start = Date.now();
1837
2410
  try {
1838
- const client = getClient3(program2);
2411
+ const client = getClient4(program2);
1839
2412
  const data = await client.getPage(opts.id, opts.expand);
1840
2413
  success(data, "confluence", "get-page", start);
1841
2414
  } catch (err) {
@@ -1845,7 +2418,7 @@ function registerConfluenceCommands(program2) {
1845
2418
  confluence.command("get-page-by-title").description("Find a Confluence page by space key and title").requiredOption("--space <key>", "Space key").requiredOption("--title <title>", "Page title").action(async (opts) => {
1846
2419
  const start = Date.now();
1847
2420
  try {
1848
- const client = getClient3(program2);
2421
+ const client = getClient4(program2);
1849
2422
  const data = await client.getPageByTitle(opts.space, opts.title);
1850
2423
  if (!data) throw new PncliError(`Page not found: "${opts.title}" in space ${opts.space}`, 1);
1851
2424
  success(data, "confluence", "get-page-by-title", start);
@@ -1856,7 +2429,7 @@ function registerConfluenceCommands(program2) {
1856
2429
  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 total pages to return (default: all)").option("--start <n>", "Offset for first result").action(async (opts) => {
1857
2430
  const start = Date.now();
1858
2431
  try {
1859
- const client = getClient3(program2);
2432
+ const client = getClient4(program2);
1860
2433
  const data = await client.listPages(opts.space, {
1861
2434
  limit: opts.limit ? parseInt(opts.limit, 10) : void 0,
1862
2435
  start: opts.start ? parseInt(opts.start, 10) : void 0
@@ -1869,7 +2442,7 @@ function registerConfluenceCommands(program2) {
1869
2442
  confluence.command("get-page-children").description("Get child pages of a Confluence page").requiredOption("--id <page-id>", "Parent page ID").action(async (opts) => {
1870
2443
  const start = Date.now();
1871
2444
  try {
1872
- const client = getClient3(program2);
2445
+ const client = getClient4(program2);
1873
2446
  const data = await client.getPageChildren(opts.id);
1874
2447
  success(data, "confluence", "get-page-children", start);
1875
2448
  } catch (err) {
@@ -1879,7 +2452,7 @@ function registerConfluenceCommands(program2) {
1879
2452
  confluence.command("get-labels").description("Get labels on a Confluence page").requiredOption("--id <page-id>", "Page ID").action(async (opts) => {
1880
2453
  const start = Date.now();
1881
2454
  try {
1882
- const client = getClient3(program2);
2455
+ const client = getClient4(program2);
1883
2456
  const data = await client.getLabels(opts.id);
1884
2457
  success(data, "confluence", "get-labels", start);
1885
2458
  } catch (err) {
@@ -1889,7 +2462,7 @@ function registerConfluenceCommands(program2) {
1889
2462
  confluence.command("search").description("Search Confluence with CQL").requiredOption("--cql <query>", 'CQL query string (e.g. "space=PROJ AND type=page")').option("--limit <n>", "Maximum number of results", "25").option("--start <n>", "Offset for first result", "0").option("--expand <fields>", "Comma-separated fields to expand").action(async (opts) => {
1890
2463
  const start = Date.now();
1891
2464
  try {
1892
- const client = getClient3(program2);
2465
+ const client = getClient4(program2);
1893
2466
  const data = await client.search(opts.cql, {
1894
2467
  limit: parseInt(opts.limit, 10),
1895
2468
  start: parseInt(opts.start, 10),
@@ -1903,7 +2476,7 @@ function registerConfluenceCommands(program2) {
1903
2476
  confluence.command("create-page").description("Create a new Confluence page").requiredOption("--space <key>", "Space key").requiredOption("--title <title>", "Page title").requiredOption("--body <html>", "Page body (storage format HTML)").option("--parent-id <id>", "Parent page ID (to nest under a page)").option("--representation <format>", "Body format: storage (default) or wiki", "storage").action(async (opts) => {
1904
2477
  const start = Date.now();
1905
2478
  try {
1906
- const client = getClient3(program2);
2479
+ const client = getClient4(program2);
1907
2480
  const data = await client.createPage({
1908
2481
  spaceKey: opts.space,
1909
2482
  title: opts.title,
@@ -1919,7 +2492,7 @@ function registerConfluenceCommands(program2) {
1919
2492
  confluence.command("update-page").description("Update a Confluence page (fetches current version automatically)").requiredOption("--id <page-id>", "Page ID").option("--title <title>", "New page title").option("--body <html>", "New page body (storage format HTML)").option("--status <status>", "Page status: current (default) or draft", "current").option("--representation <format>", "Body format: storage (default) or wiki", "storage").action(async (opts) => {
1920
2493
  const start = Date.now();
1921
2494
  try {
1922
- const client = getClient3(program2);
2495
+ const client = getClient4(program2);
1923
2496
  const current = await client.getPage(opts.id, "version");
1924
2497
  const nextVersion = current.version.number + 1;
1925
2498
  const data = await client.updatePage(opts.id, {
@@ -1937,7 +2510,7 @@ function registerConfluenceCommands(program2) {
1937
2510
  confluence.command("delete-page").description("Delete a Confluence page").requiredOption("--id <page-id>", "Page ID").action(async (opts) => {
1938
2511
  const start = Date.now();
1939
2512
  try {
1940
- const client = getClient3(program2);
2513
+ const client = getClient4(program2);
1941
2514
  await client.deletePage(opts.id);
1942
2515
  success({ deleted: opts.id }, "confluence", "delete-page", start);
1943
2516
  } catch (err) {
@@ -1947,7 +2520,7 @@ function registerConfluenceCommands(program2) {
1947
2520
  confluence.command("list-comments").description("List comments on a Confluence page").requiredOption("--id <page-id>", "Page ID").action(async (opts) => {
1948
2521
  const start = Date.now();
1949
2522
  try {
1950
- const client = getClient3(program2);
2523
+ const client = getClient4(program2);
1951
2524
  const data = await client.listComments(opts.id);
1952
2525
  success(data, "confluence", "list-comments", start);
1953
2526
  } catch (err) {
@@ -1957,7 +2530,7 @@ function registerConfluenceCommands(program2) {
1957
2530
  confluence.command("add-comment").description("Add a comment to a Confluence page").requiredOption("--id <page-id>", "Page ID").requiredOption("--body <text>", "Comment body (storage format HTML)").option("--representation <format>", "Body format: storage (default) or wiki", "storage").action(async (opts) => {
1958
2531
  const start = Date.now();
1959
2532
  try {
1960
- const client = getClient3(program2);
2533
+ const client = getClient4(program2);
1961
2534
  const data = await client.addComment(opts.id, opts.body, opts.representation);
1962
2535
  success(data, "confluence", "add-comment", start);
1963
2536
  } catch (err) {
@@ -1967,7 +2540,7 @@ function registerConfluenceCommands(program2) {
1967
2540
  confluence.command("add-label").description("Add labels to a Confluence page").requiredOption("--id <page-id>", "Page ID").requiredOption("--labels <names>", "Comma-separated label names").action(async (opts) => {
1968
2541
  const start = Date.now();
1969
2542
  try {
1970
- const client = getClient3(program2);
2543
+ const client = getClient4(program2);
1971
2544
  const labels = opts.labels.split(",").map((s) => s.trim()).filter(Boolean);
1972
2545
  const data = await client.addLabel(opts.id, labels);
1973
2546
  success(data, "confluence", "add-label", start);
@@ -1978,7 +2551,7 @@ function registerConfluenceCommands(program2) {
1978
2551
  confluence.command("remove-label").description("Remove a label from a Confluence page").requiredOption("--id <page-id>", "Page ID").requiredOption("--label <name>", "Label name to remove").action(async (opts) => {
1979
2552
  const start = Date.now();
1980
2553
  try {
1981
- const client = getClient3(program2);
2554
+ const client = getClient4(program2);
1982
2555
  await client.removeLabel(opts.id, opts.label);
1983
2556
  success({ removed: opts.label, from: opts.id }, "confluence", "remove-label", start);
1984
2557
  } catch (err) {
@@ -1988,7 +2561,7 @@ function registerConfluenceCommands(program2) {
1988
2561
  confluence.command("list-spaces").description("List Confluence spaces").option("--type <type>", "Space type: global or personal").option("--limit <n>", "Max total spaces to return (default: all)").action(async (opts) => {
1989
2562
  const start = Date.now();
1990
2563
  try {
1991
- const client = getClient3(program2);
2564
+ const client = getClient4(program2);
1992
2565
  const data = await client.listSpaces({
1993
2566
  type: opts.type,
1994
2567
  limit: opts.limit ? parseInt(opts.limit, 10) : void 0
@@ -2001,7 +2574,7 @@ function registerConfluenceCommands(program2) {
2001
2574
  confluence.command("get-space").description("Get a Confluence space by key").requiredOption("--key <space-key>", "Space key").action(async (opts) => {
2002
2575
  const start = Date.now();
2003
2576
  try {
2004
- const client = getClient3(program2);
2577
+ const client = getClient4(program2);
2005
2578
  const data = await client.getSpace(opts.key);
2006
2579
  success(data, "confluence", "get-space", start);
2007
2580
  } catch (err) {
@@ -2011,7 +2584,7 @@ function registerConfluenceCommands(program2) {
2011
2584
  confluence.command("list-attachments").description("List attachments on a Confluence page").requiredOption("--id <page-id>", "Page ID").action(async (opts) => {
2012
2585
  const start = Date.now();
2013
2586
  try {
2014
- const client = getClient3(program2);
2587
+ const client = getClient4(program2);
2015
2588
  const data = await client.listAttachments(opts.id);
2016
2589
  success(data, "confluence", "list-attachments", start);
2017
2590
  } catch (err) {
@@ -2123,7 +2696,7 @@ var SonarClient = class {
2123
2696
 
2124
2697
  // src/services/sonar/commands.ts
2125
2698
  var DEFAULT_METRICS = "coverage,duplicated_lines_density,bugs,vulnerabilities,code_smells,sqale_rating,reliability_rating,security_rating,ncloc";
2126
- function getClient4(program2) {
2699
+ function getClient5(program2) {
2127
2700
  const opts = program2.optsWithGlobals();
2128
2701
  const config = loadConfig({ configPath: opts.config });
2129
2702
  if (!config.sonar.baseUrl) throw new PncliError("SonarQube not configured. Run: pncli config init");
@@ -2140,7 +2713,7 @@ function registerSonarCommands(program2) {
2140
2713
  sonar.command("quality-gate").description("Get quality gate status for a project").option("--project <key>", "SonarQube project key (or set defaults.sonar.project in config)").option("--branch <name>", "Branch name").action(async (opts) => {
2141
2714
  const start = Date.now();
2142
2715
  try {
2143
- const { client, config } = getClient4(program2);
2716
+ const { client, config } = getClient5(program2);
2144
2717
  const projectKey = resolveProject(config, opts.project);
2145
2718
  const data = await client.getQualityGate(projectKey, opts.branch);
2146
2719
  success(data, "sonar", "quality-gate", start);
@@ -2151,7 +2724,7 @@ function registerSonarCommands(program2) {
2151
2724
  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) => {
2152
2725
  const start = Date.now();
2153
2726
  try {
2154
- const { client, config } = getClient4(program2);
2727
+ const { client, config } = getClient5(program2);
2155
2728
  const projectKey = resolveProject(config, opts.project);
2156
2729
  const baseOpts = {
2157
2730
  projectKey,
@@ -2179,7 +2752,7 @@ function registerSonarCommands(program2) {
2179
2752
  sonar.command("measures").description("Get key metrics for a project").option("--project <key>", "SonarQube project key (or set defaults.sonar.project in config)").option("--metrics <list>", "Comma-separated metric keys", DEFAULT_METRICS).option("--branch <name>", "Branch name").action(async (opts) => {
2180
2753
  const start = Date.now();
2181
2754
  try {
2182
- const { client, config } = getClient4(program2);
2755
+ const { client, config } = getClient5(program2);
2183
2756
  const projectKey = resolveProject(config, opts.project);
2184
2757
  const data = await client.getMeasures({
2185
2758
  projectKey,
@@ -2194,7 +2767,7 @@ function registerSonarCommands(program2) {
2194
2767
  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) => {
2195
2768
  const start = Date.now();
2196
2769
  try {
2197
- const { client } = getClient4(program2);
2770
+ const { client } = getClient5(program2);
2198
2771
  if (opts.all) {
2199
2772
  const data = await client.searchAllProjects(opts.query);
2200
2773
  success(data, "sonar", "projects", start);
@@ -2213,7 +2786,7 @@ function registerSonarCommands(program2) {
2213
2786
  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) => {
2214
2787
  const start = Date.now();
2215
2788
  try {
2216
- const { client, config } = getClient4(program2);
2789
+ const { client, config } = getClient5(program2);
2217
2790
  const projectKey = resolveProject(config, opts.project);
2218
2791
  const baseOpts = {
2219
2792
  projectKey,
@@ -2369,7 +2942,7 @@ var SdeClient = class {
2369
2942
  };
2370
2943
 
2371
2944
  // src/services/sde/commands.ts
2372
- function getClient5(program2) {
2945
+ function getClient6(program2) {
2373
2946
  const opts = program2.optsWithGlobals();
2374
2947
  const config = loadConfig({ configPath: opts.config });
2375
2948
  if (!config.sde.baseUrl) throw new PncliError("SDElements not configured. Run: pncli config init");
@@ -2401,7 +2974,7 @@ function registerSdeCommands(program2) {
2401
2974
  sde.command("server-info").description("Get server version and platform info (requires super-user)").action(async () => {
2402
2975
  const start = Date.now();
2403
2976
  try {
2404
- const { client } = getClient5(program2);
2977
+ const { client } = getClient6(program2);
2405
2978
  const data = await client.getServerInfo();
2406
2979
  success(data, "sde", "server-info", start);
2407
2980
  } catch (err) {
@@ -2411,7 +2984,7 @@ function registerSdeCommands(program2) {
2411
2984
  sde.command("whoami").description("Get current authenticated user").action(async () => {
2412
2985
  const start = Date.now();
2413
2986
  try {
2414
- const { client } = getClient5(program2);
2987
+ const { client } = getClient6(program2);
2415
2988
  const data = await client.getMe();
2416
2989
  success(data, "sde", "whoami", start);
2417
2990
  } catch (err) {
@@ -2421,7 +2994,7 @@ function registerSdeCommands(program2) {
2421
2994
  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) => {
2422
2995
  const start = Date.now();
2423
2996
  try {
2424
- const { client } = getClient5(program2);
2997
+ const { client } = getClient6(program2);
2425
2998
  const baseOpts = {
2426
2999
  email: opts.email,
2427
3000
  firstName: opts.firstName,
@@ -2446,7 +3019,7 @@ function registerSdeCommands(program2) {
2446
3019
  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) => {
2447
3020
  const start = Date.now();
2448
3021
  try {
2449
- const { client } = getClient5(program2);
3022
+ const { client } = getClient6(program2);
2450
3023
  const baseOpts = {
2451
3024
  name: opts.name,
2452
3025
  search: opts.search,
@@ -2473,7 +3046,7 @@ function registerSdeCommands(program2) {
2473
3046
  sde.command("project").description("Get a single project by ID").option("--id <id>", "Project ID (or set defaults.sde.project in config)").option("--expand <fields>", "Expand nested fields (comma-separated): application,business_unit,creator").option("--include <fields>", "Include extra fields (comma-separated): task_counts,permissions").action(async (opts) => {
2474
3047
  const start = Date.now();
2475
3048
  try {
2476
- const { client, config } = getClient5(program2);
3049
+ const { client, config } = getClient6(program2);
2477
3050
  const projectId = resolveProject2(config, opts.id);
2478
3051
  const data = await client.getProject(projectId, opts.expand, opts.include);
2479
3052
  success(data, "sde", "project", start);
@@ -2484,7 +3057,7 @@ function registerSdeCommands(program2) {
2484
3057
  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) => {
2485
3058
  const start = Date.now();
2486
3059
  try {
2487
- const { client, config } = getClient5(program2);
3060
+ const { client, config } = getClient6(program2);
2488
3061
  const projectId = resolveProject2(config, opts.project);
2489
3062
  const baseOpts = {
2490
3063
  projectId,
@@ -2518,7 +3091,7 @@ function registerSdeCommands(program2) {
2518
3091
  sde.command("task").description("Get a single countermeasure").option("--project <id>", "Project ID (or set defaults.sde.project in config)").requiredOption("--task <id>", "Task ID (e.g. T21)").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").action(async (opts) => {
2519
3092
  const start = Date.now();
2520
3093
  try {
2521
- const { client, config } = getClient5(program2);
3094
+ const { client, config } = getClient6(program2);
2522
3095
  const projectId = resolveProject2(config, opts.project);
2523
3096
  const data = await client.getTask(projectId, opts.task, opts.expand, opts.include);
2524
3097
  success(data, "sde", "task", start);
@@ -2529,7 +3102,7 @@ function registerSdeCommands(program2) {
2529
3102
  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) => {
2530
3103
  const start = Date.now();
2531
3104
  try {
2532
- const { client, config } = getClient5(program2);
3105
+ const { client, config } = getClient6(program2);
2533
3106
  const projectId = resolveProject2(config, opts.project);
2534
3107
  const baseOpts = {
2535
3108
  projectId,
@@ -2557,7 +3130,7 @@ function registerSdeCommands(program2) {
2557
3130
  }
2558
3131
 
2559
3132
  // src/services/deps/commands.ts
2560
- import { Option } from "commander";
3133
+ import { Option as Option2 } from "commander";
2561
3134
 
2562
3135
  // src/services/deps/parsers/index.ts
2563
3136
  import fs from "fs";
@@ -4241,7 +4814,7 @@ async function runLicenseCheck(config, opts) {
4241
4814
  // src/services/deps/commands.ts
4242
4815
  function registerDepsCommands(program2) {
4243
4816
  const deps = program2.command("deps").description("Dependency scanning, CVE detection, and license auditing");
4244
- deps.command("frisk").description("Scan all dependencies for CVEs and output remediation paths (requires OSV.dev, Sonatype OSS Index, or Sonatype IQ Server)").option("--ecosystem <ecosystem>", "Filter to one ecosystem: npm, nuget, maven, all", "all").option("--direct-only", "Only scan direct dependencies (default: include transitive)", false).option("--include-dev", "Include dev/test dependencies", false).addOption(new Option("--source <source>", "Vulnerability source").choices(["osv", "sonatype", "sonatypeiq", "all"]).default("osv")).option("--application-id <id>", "Sonatype IQ Server application ID (required when --source sonatypeiq)").action(async (opts, cmd) => {
4817
+ deps.command("frisk").description("Scan all dependencies for CVEs and output remediation paths (requires OSV.dev, Sonatype OSS Index, or Sonatype IQ Server)").option("--ecosystem <ecosystem>", "Filter to one ecosystem: npm, nuget, maven, all", "all").option("--direct-only", "Only scan direct dependencies (default: include transitive)", false).option("--include-dev", "Include dev/test dependencies", false).addOption(new Option2("--source <source>", "Vulnerability source").choices(["osv", "sonatype", "sonatypeiq", "all"]).default("osv")).option("--application-id <id>", "Sonatype IQ Server application ID (required when --source sonatypeiq)").action(async (opts, cmd) => {
4245
4818
  const startTime = Date.now();
4246
4819
  try {
4247
4820
  const globalOpts = cmd.optsWithGlobals();
@@ -4441,6 +5014,16 @@ var AdoWorkClient = class {
4441
5014
  async downloadAttachment(absoluteUrl) {
4442
5015
  return this.http.adoBuffer(absoluteUrl);
4443
5016
  }
5017
+ async listAreas(collection, project, depth = 10) {
5018
+ return this.http.ado(
5019
+ `/${encodeURIComponent(collection)}/${encodeURIComponent(project)}/_apis/wit/classificationnodes/areas?$depth=${depth}&api-version=${API6}`
5020
+ );
5021
+ }
5022
+ async listIterations(collection, project, depth = 10) {
5023
+ return this.http.ado(
5024
+ `/${encodeURIComponent(collection)}/${encodeURIComponent(project)}/_apis/wit/classificationnodes/iterations?$depth=${depth}&api-version=${API6}`
5025
+ );
5026
+ }
4444
5027
  async uploadAttachment(collection, workItemId, filePath, comment) {
4445
5028
  const fileContent = readFileSync3(filePath);
4446
5029
  const fileName = basename2(filePath);
@@ -4600,6 +5183,16 @@ function registerConfigCommands(program2) {
4600
5183
  } else {
4601
5184
  results.bitbucket = { ok: null, message: "not configured" };
4602
5185
  }
5186
+ if (cfg.github.baseUrl) {
5187
+ try {
5188
+ await http.github("/user");
5189
+ results.github = { ok: true, message: "connected" };
5190
+ } catch (err) {
5191
+ results.github = { ok: false, message: err instanceof Error ? err.message : String(err) };
5192
+ }
5193
+ } else {
5194
+ results.github = { ok: null, message: "not configured" };
5195
+ }
4603
5196
  if (cfg.confluence.baseUrl) {
4604
5197
  try {
4605
5198
  await http.confluence("/rest/api/space", { params: { limit: 1 } });
@@ -4770,6 +5363,18 @@ function registerConfigCommands(program2) {
4770
5363
  results.bitbucket = categorize2(err);
4771
5364
  }
4772
5365
  }
5366
+ if (!cfg.github.token) {
5367
+ results.github = { status: "blank", message: "not configured" };
5368
+ } else if (!cfg.github.baseUrl) {
5369
+ results.github = { status: "error", message: "baseUrl not configured" };
5370
+ } else {
5371
+ try {
5372
+ await http.github("/user", { timeoutMs: 1e4 });
5373
+ results.github = { status: "valid", message: "ok" };
5374
+ } catch (err) {
5375
+ results.github = categorize2(err);
5376
+ }
5377
+ }
4773
5378
  if (!cfg.confluence.apiTokenExplicit && !cfg.jira.apiToken) {
4774
5379
  results.confluence = { status: "blank", message: "not configured" };
4775
5380
  } else if (!cfg.confluence.apiToken) {
@@ -4922,7 +5527,7 @@ function registerConfigCommands(program2) {
4922
5527
  if (statuses.includes("invalid")) process.exitCode = ExitCode.AUTH_ERROR;
4923
5528
  else if (statuses.includes("error")) process.exitCode = ExitCode.NETWORK_ERROR;
4924
5529
  if (cmdOpts.output === "table") {
4925
- const services = ["jira", "bitbucket", "confluence", "sonar", "sde", "ado", "jenkins", "udeploy", "artifactory", "checkmarx", "servicenow", "contrast", "sonatypeiq", "openshift"];
5530
+ const services = ["jira", "bitbucket", "github", "confluence", "sonar", "sde", "ado", "jenkins", "udeploy", "artifactory", "checkmarx", "servicenow", "contrast", "sonatypeiq", "openshift"];
4926
5531
  const labelWidth = 14;
4927
5532
  const statusWidth = 9;
4928
5533
  for (const svc of services) {
@@ -4936,7 +5541,7 @@ function registerConfigCommands(program2) {
4936
5541
  process.stdout.write("\n");
4937
5542
  } else {
4938
5543
  if (opts.pretty) {
4939
- const services = ["jira", "bitbucket", "confluence", "sonar", "sde", "ado", "jenkins", "udeploy", "artifactory", "checkmarx", "servicenow", "contrast", "sonatypeiq", "openshift"];
5544
+ const services = ["jira", "bitbucket", "github", "confluence", "sonar", "sde", "ado", "jenkins", "udeploy", "artifactory", "checkmarx", "servicenow", "contrast", "sonatypeiq", "openshift"];
4940
5545
  const labelWidth = 14;
4941
5546
  const statusWidth = 9;
4942
5547
  for (const svc of services) {
@@ -4988,6 +5593,40 @@ async function initGlobalConfig(start) {
4988
5593
  const bitbucketPat = await password({
4989
5594
  message: "Bitbucket personal access token:"
4990
5595
  });
5596
+ process.stderr.write("\n\u2500\u2500 GitHub \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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");
5597
+ const useGitHub = await confirm({
5598
+ message: "Configure GitHub for PR operations?",
5599
+ default: false
5600
+ });
5601
+ let githubBaseUrl = "";
5602
+ let githubToken = "";
5603
+ if (useGitHub) {
5604
+ githubBaseUrl = await input({
5605
+ message: "GitHub API base URL\n GitHub.com: api.github.com\n GitHub Enterprise: <host>/api/v3\n URL: ",
5606
+ default: ""
5607
+ });
5608
+ githubToken = await password({
5609
+ message: "GitHub personal access token (classic or fine-grained):"
5610
+ });
5611
+ if (githubBaseUrl && githubToken) {
5612
+ process.stderr.write("\n Verifying connection...\n");
5613
+ try {
5614
+ const tempConfig = {
5615
+ ...loadConfig(),
5616
+ github: {
5617
+ baseUrl: normalizeBaseUrl(githubBaseUrl),
5618
+ token: githubToken
5619
+ }
5620
+ };
5621
+ const tempHttp = createHttpClient(tempConfig);
5622
+ await tempHttp.github("/user");
5623
+ process.stderr.write(" Connected.\n");
5624
+ } catch (err) {
5625
+ warn(`Could not connect to GitHub: ${err instanceof Error ? err.message : String(err)}`);
5626
+ warn("Config will be saved anyway. Check your URL and token and re-run pncli config init or pncli config test.");
5627
+ }
5628
+ }
5629
+ }
4991
5630
  process.stderr.write("\n\u2500\u2500 Confluence \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
4992
5631
  const confluenceBaseUrl = await input({
4993
5632
  message: "Confluence base URL (e.g. confluence.your-company.com):",
@@ -5495,6 +6134,12 @@ async function initGlobalConfig(start) {
5495
6134
  baseUrl: normalizeBaseUrl(bitbucketBaseUrl) || void 0,
5496
6135
  pat: bitbucketPat || void 0
5497
6136
  },
6137
+ ...useGitHub && githubBaseUrl ? {
6138
+ github: {
6139
+ baseUrl: normalizeBaseUrl(githubBaseUrl),
6140
+ token: githubToken || void 0
6141
+ }
6142
+ } : {},
5498
6143
  ...confluenceBaseUrl || confluenceApiToken ? {
5499
6144
  confluence: {
5500
6145
  baseUrl: normalizeBaseUrl(confluenceBaseUrl) || void 0,
@@ -6233,6 +6878,26 @@ function registerAdoWorkCommands(ado) {
6233
6878
  fail(err, "ado", "work-download-attachment", start);
6234
6879
  }
6235
6880
  });
6881
+ work.command("list-areas").description("List classification areas for the team project (tree)").option("--depth <n>", "Depth of the area tree to retrieve", "10").action(async (opts) => {
6882
+ const start = Date.now();
6883
+ try {
6884
+ const { collection, project, workClient } = getAdoContext(ado);
6885
+ const data = await workClient.listAreas(collection, project, parseInt(opts.depth, 10));
6886
+ success(data, "ado", "work-list-areas", start);
6887
+ } catch (err) {
6888
+ fail(err, "ado", "work-list-areas", start);
6889
+ }
6890
+ });
6891
+ work.command("list-iterations").description("List classification iterations for the team project (tree), including start/finish dates").option("--depth <n>", "Depth of the iteration tree to retrieve", "10").action(async (opts) => {
6892
+ const start = Date.now();
6893
+ try {
6894
+ const { collection, project, workClient } = getAdoContext(ado);
6895
+ const data = await workClient.listIterations(collection, project, parseInt(opts.depth, 10));
6896
+ success(data, "ado", "work-list-iterations", start);
6897
+ } catch (err) {
6898
+ fail(err, "ado", "work-list-iterations", start);
6899
+ }
6900
+ });
6236
6901
  work.command("types").description("List work item types in the project").option("--discover", "Fetch from server (always true for this command)").option("--save", "Save discovered types to ~/.pncli/config.json").action(async (opts) => {
6237
6902
  const start = Date.now();
6238
6903
  try {
@@ -6554,11 +7219,11 @@ function registerAdoRepoCommands(ado) {
6554
7219
  const start = Date.now();
6555
7220
  try {
6556
7221
  const globalOpts = ado.optsWithGlobals();
6557
- const { loadConfig: lc } = await import("./config-TL5X47RY.js");
7222
+ const { loadConfig: lc } = await import("./config-S56LADVF.js");
6558
7223
  const config = lc({ configPath: globalOpts.config });
6559
7224
  const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
6560
7225
  const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
6561
- const { createHttpClient: createHttpClient2 } = await import("./http-OYI4NEKO.js");
7226
+ const { createHttpClient: createHttpClient2 } = await import("./http-5WRBWEM6.js");
6562
7227
  const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
6563
7228
  const coreClient = new AdoCoreClient2(http);
6564
7229
  const me = await coreClient.getConnectionData(collection);
@@ -6816,7 +7481,7 @@ var JenkinsClient = class {
6816
7481
  };
6817
7482
 
6818
7483
  // src/services/jenkins/commands.ts
6819
- function getClient6(program2) {
7484
+ function getClient7(program2) {
6820
7485
  const opts = program2.optsWithGlobals();
6821
7486
  const config = loadConfig({ configPath: opts.config });
6822
7487
  if (!config.jenkins.baseUrl) throw new PncliError("Jenkins not configured. Run: pncli config init");
@@ -6850,7 +7515,7 @@ function registerJenkinsCommands(program2) {
6850
7515
  pipeline.command("list").description("List all jobs on the Jenkins instance (or within a folder)").option("--folder <name>", "Folder name to enumerate (supports nested folders: parentFolder/childFolder)").action(async (opts) => {
6851
7516
  const start = Date.now();
6852
7517
  try {
6853
- const client = getClient6(program2);
7518
+ const client = getClient7(program2);
6854
7519
  const data = await client.listJobs(opts.folder);
6855
7520
  success(data, "jenkins", "pipeline-list", start);
6856
7521
  } catch (err) {
@@ -6860,7 +7525,7 @@ function registerJenkinsCommands(program2) {
6860
7525
  pipeline.command("get").description("Get details for a specific job").requiredOption("--name <job>", "Job name (use folder/job for folder-scoped jobs)").action(async (opts) => {
6861
7526
  const start = Date.now();
6862
7527
  try {
6863
- const client = getClient6(program2);
7528
+ const client = getClient7(program2);
6864
7529
  const data = await client.getJob(opts.name);
6865
7530
  success(data, "jenkins", "pipeline-get", start);
6866
7531
  } catch (err) {
@@ -6873,7 +7538,7 @@ function registerJenkinsCommands(program2) {
6873
7538
  }, []).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) => {
6874
7539
  const start = Date.now();
6875
7540
  try {
6876
- const client = getClient6(program2);
7541
+ const client = getClient7(program2);
6877
7542
  for (const p of opts.parameter) {
6878
7543
  if (!p.includes("=")) throw new PncliError(`Invalid --parameter "${p}". Expected format: key=value`, 1);
6879
7544
  }
@@ -6906,7 +7571,7 @@ function registerJenkinsCommands(program2) {
6906
7571
  pipeline.command("list-runs").description("List recent builds for a job").requiredOption("--name <job>", "Job name (use folder/job for folder-scoped jobs)").option("--top <n>", "Maximum number of builds to return (default 25)", "25").action(async (opts) => {
6907
7572
  const start = Date.now();
6908
7573
  try {
6909
- const client = getClient6(program2);
7574
+ const client = getClient7(program2);
6910
7575
  const top = parseInt(opts.top, 10);
6911
7576
  if (isNaN(top) || top <= 0) throw new PncliError(`Invalid --top "${opts.top}". Expected a positive integer.`, 1);
6912
7577
  const data = await client.listBuilds(opts.name, top);
@@ -6918,7 +7583,7 @@ function registerJenkinsCommands(program2) {
6918
7583
  pipeline.command("get-run").description("Get details for a specific build").requiredOption("--name <job>", "Job name (use folder/job for folder-scoped jobs)").requiredOption("--number <n>", "Build number").action(async (opts) => {
6919
7584
  const start = Date.now();
6920
7585
  try {
6921
- const client = getClient6(program2);
7586
+ const client = getClient7(program2);
6922
7587
  const buildNumber = parseInt(opts.number, 10);
6923
7588
  if (isNaN(buildNumber)) throw new PncliError(`Invalid --number "${opts.number}". Expected an integer.`, 1);
6924
7589
  const data = await client.getBuild(opts.name, buildNumber);
@@ -6930,7 +7595,7 @@ function registerJenkinsCommands(program2) {
6930
7595
  pipeline.command("logs").description("Fetch console log for a build (consider --output-file for large logs)").requiredOption("--name <job>", "Job name (use folder/job for folder-scoped jobs)").requiredOption("--number <n>", "Build number").action(async (opts) => {
6931
7596
  const start = Date.now();
6932
7597
  try {
6933
- const client = getClient6(program2);
7598
+ const client = getClient7(program2);
6934
7599
  const buildNumber = parseInt(opts.number, 10);
6935
7600
  if (isNaN(buildNumber)) throw new PncliError(`Invalid --number "${opts.number}". Expected an integer.`, 1);
6936
7601
  const text = await client.getConsoleLog(opts.name, buildNumber);
@@ -7022,7 +7687,7 @@ var ArtifactoryClient = class {
7022
7687
  };
7023
7688
 
7024
7689
  // src/services/artifactory/commands.ts
7025
- function getClient7(program2) {
7690
+ function getClient8(program2) {
7026
7691
  const opts = program2.optsWithGlobals();
7027
7692
  const config = loadConfig({ configPath: opts.config });
7028
7693
  if (!config.artifactory.baseUrl) throw new PncliError("Artifactory not configured. Run: pncli config init");
@@ -7035,7 +7700,7 @@ function registerArtifactoryCommands(program2) {
7035
7700
  art.command("ping").description("Check Artifactory connectivity and authentication").action(async () => {
7036
7701
  const start = Date.now();
7037
7702
  try {
7038
- const client = getClient7(program2);
7703
+ const client = getClient8(program2);
7039
7704
  const data = await client.ping();
7040
7705
  success(data, "artifactory", "ping", start);
7041
7706
  } catch (err) {
@@ -7045,7 +7710,7 @@ function registerArtifactoryCommands(program2) {
7045
7710
  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) => {
7046
7711
  const start = Date.now();
7047
7712
  try {
7048
- const client = getClient7(program2);
7713
+ const client = getClient8(program2);
7049
7714
  const data = await client.listRepos({ type: opts.type, packageType: opts.packageType });
7050
7715
  success(data, "artifactory", "repos", start);
7051
7716
  } catch (err) {
@@ -7055,7 +7720,7 @@ function registerArtifactoryCommands(program2) {
7055
7720
  art.command("artifact-info <repo> <path>").description("Get metadata for an artifact or folder (checksums, size, timestamps)").action(async (repo, path8) => {
7056
7721
  const start = Date.now();
7057
7722
  try {
7058
- const client = getClient7(program2);
7723
+ const client = getClient8(program2);
7059
7724
  const data = await client.getStorageInfo(repo, path8);
7060
7725
  success(data, "artifactory", "artifact-info", start);
7061
7726
  } catch (err) {
@@ -7065,7 +7730,7 @@ function registerArtifactoryCommands(program2) {
7065
7730
  art.command("properties-get <repo> <path>").description("Read custom properties on an artifact").action(async (repo, path8) => {
7066
7731
  const start = Date.now();
7067
7732
  try {
7068
- const client = getClient7(program2);
7733
+ const client = getClient8(program2);
7069
7734
  const data = await client.getProperties(repo, path8);
7070
7735
  success(data, "artifactory", "properties-get", start);
7071
7736
  } catch (err) {
@@ -7082,7 +7747,7 @@ function registerArtifactoryCommands(program2) {
7082
7747
  if (eq < 1) throw new PncliError(`Invalid property format: "${kv}" \u2014 expected key=value`);
7083
7748
  properties[kv.slice(0, eq)] = kv.slice(eq + 1);
7084
7749
  }
7085
- const client = getClient7(program2);
7750
+ const client = getClient8(program2);
7086
7751
  await client.setProperties(repo, path8, properties, { recursive: opts.recursive });
7087
7752
  success({ repo, path: path8, properties }, "artifactory", "properties-set", start);
7088
7753
  } catch (err) {
@@ -7095,7 +7760,7 @@ function registerArtifactoryCommands(program2) {
7095
7760
  if (!opts.repo && !opts.name && !opts.path) {
7096
7761
  throw new PncliError("Provide at least one filter: --repo, --name, or --path");
7097
7762
  }
7098
- const client = getClient7(program2);
7763
+ const client = getClient8(program2);
7099
7764
  const data = await client.searchAql({
7100
7765
  repo: opts.repo,
7101
7766
  name: opts.name,
@@ -7113,7 +7778,7 @@ function registerArtifactoryCommands(program2) {
7113
7778
  art.command("builds").description('List builds registered in Artifactory Build Integration (names may differ from ADO pipeline or uDeploy component names; use "search --repo" when empty)').option("--timeout <s>", "Request timeout in seconds (default 60; increase for large Artifactory instances)", "60").action(async (opts) => {
7114
7779
  const start = Date.now();
7115
7780
  try {
7116
- const client = getClient7(program2);
7781
+ const client = getClient8(program2);
7117
7782
  const timeoutSecs = parseInt(opts.timeout, 10);
7118
7783
  if (isNaN(timeoutSecs) || timeoutSecs <= 0) throw new PncliError("--timeout must be a positive integer (seconds)", 1);
7119
7784
  const timeoutMs = timeoutSecs * 1e3;
@@ -7126,7 +7791,7 @@ function registerArtifactoryCommands(program2) {
7126
7791
  art.command("build-runs <name>").description("List all run numbers for a build").action(async (name) => {
7127
7792
  const start = Date.now();
7128
7793
  try {
7129
- const client = getClient7(program2);
7794
+ const client = getClient8(program2);
7130
7795
  const data = await client.listBuildRuns(name);
7131
7796
  success(data, "artifactory", "build-runs", start);
7132
7797
  } catch (err) {
@@ -7136,7 +7801,7 @@ function registerArtifactoryCommands(program2) {
7136
7801
  art.command("build-info <name> <number>").description("Get build info (artifacts, dependencies, VCS revision) for a specific run").action(async (name, number) => {
7137
7802
  const start = Date.now();
7138
7803
  try {
7139
- const client = getClient7(program2);
7804
+ const client = getClient8(program2);
7140
7805
  const data = await client.getBuildInfo(name, number);
7141
7806
  success(data, "artifactory", "build-info", start);
7142
7807
  } catch (err) {
@@ -7208,7 +7873,7 @@ var UdeployClient = class {
7208
7873
  // src/services/udeploy/commands.ts
7209
7874
  var POLL_INTERVAL_MS2 = 5e3;
7210
7875
  var DEFAULT_TIMEOUT_MS = 10 * 60 * 1e3;
7211
- function getClient8(program2) {
7876
+ function getClient9(program2) {
7212
7877
  const opts = program2.optsWithGlobals();
7213
7878
  const config = loadConfig({ configPath: opts.config });
7214
7879
  if (!config.udeploy.baseUrl) {
@@ -7232,7 +7897,7 @@ function registerUdeployCommands(program2) {
7232
7897
  udeploy.command("apps").description("List all applications").action(async () => {
7233
7898
  const start = Date.now();
7234
7899
  try {
7235
- const { client } = getClient8(program2);
7900
+ const { client } = getClient9(program2);
7236
7901
  const apps = await client.listApplications();
7237
7902
  success(apps, "udeploy", "apps", start);
7238
7903
  } catch (err) {
@@ -7242,7 +7907,7 @@ function registerUdeployCommands(program2) {
7242
7907
  udeploy.command("environments").description("List environments for an application").action(async () => {
7243
7908
  const start = Date.now();
7244
7909
  try {
7245
- const { client, config } = getClient8(program2);
7910
+ const { client, config } = getClient9(program2);
7246
7911
  const parentOpts = udeploy.opts();
7247
7912
  const application = resolveApplication(config, parentOpts.application);
7248
7913
  const envs = await client.listEnvironments(application);
@@ -7254,7 +7919,7 @@ function registerUdeployCommands(program2) {
7254
7919
  udeploy.command("components").description("List all components").action(async () => {
7255
7920
  const start = Date.now();
7256
7921
  try {
7257
- const { client } = getClient8(program2);
7922
+ const { client } = getClient9(program2);
7258
7923
  const components = await client.listComponents();
7259
7924
  success(components, "udeploy", "components", start);
7260
7925
  } catch (err) {
@@ -7264,7 +7929,7 @@ function registerUdeployCommands(program2) {
7264
7929
  udeploy.command("versions").description("List versions for a component").requiredOption("--component <name>", "Component name or ID").action(async (cmdOpts) => {
7265
7930
  const start = Date.now();
7266
7931
  try {
7267
- const { client } = getClient8(program2);
7932
+ const { client } = getClient9(program2);
7268
7933
  const versions = await client.listVersions(cmdOpts.component);
7269
7934
  success(versions, "udeploy", "versions", start);
7270
7935
  } catch (err) {
@@ -7274,7 +7939,7 @@ function registerUdeployCommands(program2) {
7274
7939
  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) => {
7275
7940
  const start = Date.now();
7276
7941
  try {
7277
- const { client } = getClient8(program2);
7942
+ const { client } = getClient9(program2);
7278
7943
  const created = await client.createVersion(cmdOpts.component, cmdOpts.name);
7279
7944
  if (cmdOpts.finish) {
7280
7945
  await client.finishImporting(cmdOpts.component, cmdOpts.name);
@@ -7287,7 +7952,7 @@ function registerUdeployCommands(program2) {
7287
7952
  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) => {
7288
7953
  const start = Date.now();
7289
7954
  try {
7290
- const { client, config } = getClient8(program2);
7955
+ const { client, config } = getClient9(program2);
7291
7956
  const parentOpts = udeploy.opts();
7292
7957
  const application = resolveApplication(config, parentOpts.application);
7293
7958
  const environment = resolveEnvironment(config, parentOpts.environment);
@@ -7340,7 +8005,7 @@ function registerUdeployCommands(program2) {
7340
8005
  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) => {
7341
8006
  const start = Date.now();
7342
8007
  try {
7343
- const { client } = getClient8(program2);
8008
+ const { client } = getClient9(program2);
7344
8009
  const status = await client.getRequestStatus(cmdOpts.requestId);
7345
8010
  success(status, "udeploy", "request-status", start);
7346
8011
  } catch (err) {
@@ -7350,7 +8015,7 @@ function registerUdeployCommands(program2) {
7350
8015
  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) => {
7351
8016
  const start = Date.now();
7352
8017
  try {
7353
- const { client } = getClient8(program2);
8018
+ const { client } = getClient9(program2);
7354
8019
  const info = await client.getRequestInfo(cmdOpts.requestId);
7355
8020
  success(info, "udeploy", "request-info", start);
7356
8021
  } catch (err) {
@@ -7394,7 +8059,7 @@ var CheckmarxClient = class {
7394
8059
  };
7395
8060
 
7396
8061
  // src/services/checkmarx/commands.ts
7397
- function getClient9(program2) {
8062
+ function getClient10(program2) {
7398
8063
  const opts = program2.optsWithGlobals();
7399
8064
  const config = loadConfig({ configPath: opts.config });
7400
8065
  if (!config.checkmarx.baseUrl) throw new PncliError("Checkmarx not configured. Run: pncli config init");
@@ -7408,7 +8073,7 @@ function registerCheckmarxCommands(program2) {
7408
8073
  project.command("list").description("List all projects").action(async () => {
7409
8074
  const start = Date.now();
7410
8075
  try {
7411
- const data = await getClient9(program2).listProjects();
8076
+ const data = await getClient10(program2).listProjects();
7412
8077
  success(data, "checkmarx", "project-list", start);
7413
8078
  } catch (err) {
7414
8079
  fail(err, "checkmarx", "project-list", start);
@@ -7419,7 +8084,7 @@ function registerCheckmarxCommands(program2) {
7419
8084
  try {
7420
8085
  const id = parseInt(opts.id, 10);
7421
8086
  if (isNaN(id)) throw new PncliError(`Invalid --id "${opts.id}". Expected an integer.`, 1);
7422
- const data = await getClient9(program2).getProject(id);
8087
+ const data = await getClient10(program2).getProject(id);
7423
8088
  success(data, "checkmarx", "project-get", start);
7424
8089
  } catch (err) {
7425
8090
  fail(err, "checkmarx", "project-get", start);
@@ -7432,7 +8097,7 @@ function registerCheckmarxCommands(program2) {
7432
8097
  const last = opts.last ? parseInt(opts.last, 10) : void 0;
7433
8098
  if (opts.project && isNaN(projectId)) throw new PncliError(`Invalid --project "${opts.project}". Expected an integer.`, 1);
7434
8099
  if (opts.last && isNaN(last)) throw new PncliError(`Invalid --last "${opts.last}". Expected an integer.`, 1);
7435
- const data = await getClient9(program2).listScans({ projectId, last });
8100
+ const data = await getClient10(program2).listScans({ projectId, last });
7436
8101
  success(data, "checkmarx", "scan-list", start);
7437
8102
  } catch (err) {
7438
8103
  fail(err, "checkmarx", "scan-list", start);
@@ -7443,7 +8108,7 @@ function registerCheckmarxCommands(program2) {
7443
8108
  try {
7444
8109
  const id = parseInt(opts.id, 10);
7445
8110
  if (isNaN(id)) throw new PncliError(`Invalid --id "${opts.id}". Expected an integer.`, 1);
7446
- const data = await getClient9(program2).getScan(id);
8111
+ const data = await getClient10(program2).getScan(id);
7447
8112
  success(data, "checkmarx", "scan-get", start);
7448
8113
  } catch (err) {
7449
8114
  fail(err, "checkmarx", "scan-get", start);
@@ -7454,7 +8119,7 @@ function registerCheckmarxCommands(program2) {
7454
8119
  try {
7455
8120
  const id = parseInt(opts.id, 10);
7456
8121
  if (isNaN(id)) throw new PncliError(`Invalid --id "${opts.id}". Expected an integer.`, 1);
7457
- const data = await getClient9(program2).getScanResultsStatistics(id);
8122
+ const data = await getClient10(program2).getScanResultsStatistics(id);
7458
8123
  success(data, "checkmarx", "scan-stats", start);
7459
8124
  } catch (err) {
7460
8125
  fail(err, "checkmarx", "scan-stats", start);
@@ -7704,6 +8369,8 @@ import os from "os";
7704
8369
  import { fileURLToPath } from "url";
7705
8370
  import { execFileSync as execFileSync3 } from "child_process";
7706
8371
  import select from "@inquirer/select";
8372
+ var BACK = "__back__";
8373
+ var ALL_MARKETPLACES = "__all_marketplaces__";
7707
8374
  function injectTokenIntoUrl(url, token) {
7708
8375
  let parsed;
7709
8376
  try {
@@ -7727,10 +8394,31 @@ function repoNameFromUrl(url) {
7727
8394
  function defaultMarketplacePath(url) {
7728
8395
  return path7.join(os.homedir(), ".agents", "marketplaces", repoNameFromUrl(url));
7729
8396
  }
8397
+ function marketplaceLabel(m) {
8398
+ return m.name ?? repoNameFromUrl(m.repoUrl ?? "");
8399
+ }
8400
+ function scrubToken(msg) {
8401
+ return msg.replace(/x-(?:token-auth|access-token):[^@]+@/g, "x-token-auth:***@");
8402
+ }
8403
+ function assertInteractive(hint) {
8404
+ if (!process.stdin.isTTY) {
8405
+ throw new Error(`This selection requires an interactive terminal. ${hint}`);
8406
+ }
8407
+ }
7730
8408
  var AGENT_PATHS = {
7731
8409
  "github-copilot": { project: ".agents/skills", user: path7.join(os.homedir(), ".copilot/skills") },
7732
8410
  "claude-code": { project: ".claude/skills", user: path7.join(os.homedir(), ".claude/skills") }
7733
8411
  };
8412
+ function resolveAgentName(opts) {
8413
+ return opts.claude ? "claude-code" : opts.agent ?? "github-copilot";
8414
+ }
8415
+ function resolveAgentPaths(agentName) {
8416
+ const agentConfig = AGENT_PATHS[agentName];
8417
+ if (!agentConfig) {
8418
+ throw new Error(`Unknown agent: "${agentName}". Use: ${Object.keys(AGENT_PATHS).join(" | ")}`);
8419
+ }
8420
+ return agentConfig;
8421
+ }
7734
8422
  function getBundledSkillsDir() {
7735
8423
  try {
7736
8424
  const thisFile = fileURLToPath(import.meta.url);
@@ -7739,6 +8427,233 @@ function getBundledSkillsDir() {
7739
8427
  return "";
7740
8428
  }
7741
8429
  }
8430
+ function getInstalledMetaPath(targetDir) {
8431
+ return path7.join(targetDir, ".pncli-installed.json");
8432
+ }
8433
+ function readInstalledMeta(targetDir) {
8434
+ const metaPath = getInstalledMetaPath(targetDir);
8435
+ const raw = loadJsonFile(metaPath);
8436
+ if (raw && raw.version === 1 && raw.skills) return raw;
8437
+ return { version: 1, skills: {} };
8438
+ }
8439
+ function recordInstalledSkills(targetDir, skillNames, record) {
8440
+ if (skillNames.length === 0) return;
8441
+ const meta = readInstalledMeta(targetDir);
8442
+ const now = (/* @__PURE__ */ new Date()).toISOString();
8443
+ for (const skillName of skillNames) {
8444
+ meta.skills[skillName] = { ...record, installedAt: now };
8445
+ }
8446
+ fs4.writeFileSync(getInstalledMetaPath(targetDir), JSON.stringify(meta, null, 2), "utf8");
8447
+ }
8448
+ function getAllMarketplaces(globalConfig) {
8449
+ const result = [];
8450
+ if (Array.isArray(globalConfig.marketplaces)) {
8451
+ result.push(...globalConfig.marketplaces);
8452
+ }
8453
+ if (globalConfig.marketplace?.repoUrl) {
8454
+ const legacyUrl = globalConfig.marketplace.repoUrl;
8455
+ const alreadyPresent = result.some((m) => m.repoUrl === legacyUrl);
8456
+ if (!alreadyPresent) {
8457
+ const existingNames = new Set(result.map((m) => m.name).filter((n) => !!n));
8458
+ let legacyName = globalConfig.marketplace.name ?? repoNameFromUrl(legacyUrl);
8459
+ if (existingNames.has(legacyName)) {
8460
+ let suffix = 2;
8461
+ while (existingNames.has(`${legacyName}-${suffix}`)) suffix++;
8462
+ legacyName = `${legacyName}-${suffix}`;
8463
+ }
8464
+ result.push({
8465
+ name: legacyName,
8466
+ repoUrl: globalConfig.marketplace.repoUrl,
8467
+ localPath: globalConfig.marketplace.localPath,
8468
+ token: globalConfig.marketplace.token
8469
+ });
8470
+ }
8471
+ }
8472
+ return result;
8473
+ }
8474
+ function saveMarketplaces(configPath, existing, marketplaces) {
8475
+ const updated = { ...existing, marketplaces };
8476
+ delete updated.marketplace;
8477
+ writeGlobalConfig(updated, configPath);
8478
+ }
8479
+ function loadMarketplacesConfig(configPath) {
8480
+ const existing = loadJsonFile(configPath) ?? {};
8481
+ const all = getAllMarketplaces(existing);
8482
+ if (existing.marketplace?.repoUrl) {
8483
+ saveMarketplaces(configPath, existing, all);
8484
+ warn("Migrated legacy single-marketplace config to the multi-marketplace format.");
8485
+ delete existing.marketplace;
8486
+ }
8487
+ return { existing, all };
8488
+ }
8489
+ function loadMarketplaces(configPath) {
8490
+ return loadMarketplacesConfig(configPath).all;
8491
+ }
8492
+ function upsertMarketplace(all, entry) {
8493
+ const idxByUrl = all.findIndex((m) => m.repoUrl === entry.repoUrl);
8494
+ const idxByName = all.findIndex((m) => m.name === entry.name);
8495
+ if (idxByUrl !== -1) {
8496
+ if (idxByName !== -1 && idxByName !== idxByUrl) {
8497
+ throw new Error(`Marketplace name "${entry.name}" is already used by a different marketplace (${all[idxByName].repoUrl}). Choose a different --name.`);
8498
+ }
8499
+ all[idxByUrl] = { ...entry, token: entry.token ?? all[idxByUrl].token };
8500
+ } else if (idxByName !== -1) {
8501
+ throw new Error(`Marketplace name "${entry.name}" is already registered for a different repo (${all[idxByName].repoUrl}). Choose a different --name, or remove the existing marketplace first.`);
8502
+ } else {
8503
+ all.push(entry);
8504
+ }
8505
+ }
8506
+ function cloneOrReuseMarketplace(url, resolvedPath, opts) {
8507
+ const hasGit = fs4.existsSync(path7.join(resolvedPath, ".git"));
8508
+ if (fs4.existsSync(resolvedPath) && !hasGit && fs4.readdirSync(resolvedPath).length > 0) {
8509
+ throw new Error(`Directory already exists and is not a git repo: ${resolvedPath}`);
8510
+ }
8511
+ if (hasGit) {
8512
+ warn(`Directory already contains a git repo at ${resolvedPath} \u2014 skipping clone, updating config and re-installing plugins.`);
8513
+ return;
8514
+ }
8515
+ const branchLabel = opts.branch ?? "remote default";
8516
+ warn(`Cloning ${url} (branch: ${branchLabel}) \u2192 ${resolvedPath}...`);
8517
+ const cloneUrl = opts.token ? injectTokenIntoUrl(url, opts.token) : url;
8518
+ const cloneArgs = ["clone"];
8519
+ if (opts.branch) cloneArgs.push("--branch", opts.branch);
8520
+ cloneArgs.push(cloneUrl, resolvedPath);
8521
+ try {
8522
+ execFileSync3("git", cloneArgs, { stdio: ["inherit", "inherit", "pipe"] });
8523
+ } catch (e) {
8524
+ let cloneActuallySucceeded = false;
8525
+ try {
8526
+ execFileSync3("git", ["-C", resolvedPath, "rev-parse", "HEAD"], { stdio: "pipe" });
8527
+ cloneActuallySucceeded = true;
8528
+ } catch {
8529
+ }
8530
+ if (!cloneActuallySucceeded) {
8531
+ const msg = e instanceof Error ? e.message : String(e);
8532
+ throw new Error(scrubToken(msg));
8533
+ }
8534
+ }
8535
+ }
8536
+ function pullMarketplace(marketplacePath, repoUrl, token, marketplaceName) {
8537
+ warn(`Pulling latest content for "${marketplaceName}"...`);
8538
+ const gitArgs = ["-C", marketplacePath];
8539
+ if (repoUrl && token) {
8540
+ gitArgs.push("-c", `remote.origin.url=${injectTokenIntoUrl(repoUrl, token)}`);
8541
+ }
8542
+ gitArgs.push("pull");
8543
+ let pullOutput;
8544
+ try {
8545
+ pullOutput = execFileSync3("git", gitArgs, { encoding: "utf8", stdio: ["inherit", "pipe", "pipe"], env: { ...process.env, LANG: "C", LC_ALL: "C" } });
8546
+ } catch (e) {
8547
+ const msg = e instanceof Error ? e.message : String(e);
8548
+ throw new Error(scrubToken(msg));
8549
+ }
8550
+ const updated = !pullOutput.includes("Already up to date");
8551
+ if (pullOutput.trim() && updated) warn(pullOutput.trim());
8552
+ warn(updated ? `"${marketplaceName}" updated.` : `"${marketplaceName}" already up to date \u2014 no changes to sync.`);
8553
+ return { updated };
8554
+ }
8555
+ function installPluginsForMarketplace(marketplacePath, marketplaceName, repoUrl, pluginNames, targetDir) {
8556
+ const results = {};
8557
+ let totalInstalled = 0;
8558
+ for (const pluginName of pluginNames) {
8559
+ const skillsSrc = resolveSkillsSrc(marketplacePath, pluginName);
8560
+ if (!fs4.existsSync(skillsSrc)) {
8561
+ results[pluginName] = { installed: [], failed: [] };
8562
+ warn(`No skills directory found for plugin "${pluginName}" in "${marketplaceName}" \u2014 skipping.`);
8563
+ continue;
8564
+ }
8565
+ const { installed, failed } = copyPluginSkills(skillsSrc, targetDir, {
8566
+ marketplace: marketplaceName,
8567
+ plugin: pluginName,
8568
+ installedFrom: repoUrl ?? ""
8569
+ });
8570
+ results[pluginName] = { installed, failed };
8571
+ totalInstalled += installed.length;
8572
+ for (const skill of installed) {
8573
+ warn(` ${skill}: ${path7.join(skillsSrc, skill)} \u2192 ${path7.join(targetDir, skill)}`);
8574
+ }
8575
+ if (failed.length > 0) {
8576
+ warn(`Skipped ${failed.length} skill(s) with invalid names in "${pluginName}": ${failed.join(", ")}`);
8577
+ }
8578
+ }
8579
+ return { results, totalInstalled };
8580
+ }
8581
+ function installAllPlugins(resolvedPath, marketplaceName, url, targetDir) {
8582
+ const pluginChoices = resolvePluginChoices(resolvedPath);
8583
+ if (pluginChoices.length === 0) {
8584
+ warn("No plugins found in marketplace. Check the marketplace repository structure.");
8585
+ return { pluginResults: {}, totalInstalled: 0 };
8586
+ }
8587
+ warn(`Installing ${pluginChoices.length} plugin(s) to ${targetDir}...`);
8588
+ const { results, totalInstalled } = installPluginsForMarketplace(resolvedPath, marketplaceName, url, pluginChoices.map((p) => p.name), targetDir);
8589
+ return { pluginResults: results, totalInstalled };
8590
+ }
8591
+ function syncMarketplacePlugins(m, targetDir, force, pluginFilter) {
8592
+ const marketplaceName = marketplaceLabel(m);
8593
+ try {
8594
+ const marketplacePath = m.localPath;
8595
+ if (!marketplacePath || !fs4.existsSync(marketplacePath)) {
8596
+ warn(`Marketplace "${marketplaceName}" local path not found \u2014 skipping.`);
8597
+ return { marketplace: marketplaceName, skipped: true, message: "Local path not found." };
8598
+ }
8599
+ const { updated } = pullMarketplace(marketplacePath, m.repoUrl, m.token, marketplaceName);
8600
+ if (!updated && !force) {
8601
+ return { marketplace: marketplaceName, marketplaceUpdated: false, skipped: true, message: "No changes detected \u2014 skipping install. Use --force to reinstall anyway." };
8602
+ }
8603
+ const pluginChoices = resolvePluginChoices(marketplacePath);
8604
+ if (pluginChoices.length === 0) {
8605
+ warn(`No plugins found in marketplace "${marketplaceName}" \u2014 skipping.`);
8606
+ return { marketplace: marketplaceName, skipped: true, message: "No plugins found." };
8607
+ }
8608
+ let pluginNames;
8609
+ if (pluginFilter === "all") {
8610
+ pluginNames = pluginChoices.map((p) => p.name);
8611
+ } else if (pluginChoices.some((p) => p.name === pluginFilter)) {
8612
+ pluginNames = [pluginFilter];
8613
+ } else {
8614
+ warn(`Plugin "${pluginFilter}" not found in "${marketplaceName}" \u2014 skipping.`);
8615
+ return { marketplace: marketplaceName, skipped: true, message: `Plugin "${pluginFilter}" not found.` };
8616
+ }
8617
+ const { results, totalInstalled } = installPluginsForMarketplace(marketplacePath, marketplaceName, m.repoUrl, pluginNames, targetDir);
8618
+ return { marketplace: marketplaceName, plugins: results, total: totalInstalled, marketplaceUpdated: updated };
8619
+ } catch (err) {
8620
+ const message = err instanceof Error ? err.message : String(err);
8621
+ warn(`Marketplace "${marketplaceName}" failed \u2014 skipping. ${message}`);
8622
+ return { marketplace: marketplaceName, skipped: true, error: message };
8623
+ }
8624
+ }
8625
+ async function marketplaceAddAction(url, localPath, opts, commandName) {
8626
+ const start = Date.now();
8627
+ try {
8628
+ const resolvedPath = path7.resolve(localPath ?? defaultMarketplacePath(url));
8629
+ const marketplaceName = opts.name ?? repoNameFromUrl(url);
8630
+ cloneOrReuseMarketplace(url, resolvedPath, opts);
8631
+ const configPath = getGlobalConfigPath();
8632
+ const { existing, all } = loadMarketplacesConfig(configPath);
8633
+ const entry = {
8634
+ name: marketplaceName,
8635
+ repoUrl: url,
8636
+ localPath: resolvedPath,
8637
+ ...opts.token ? { token: opts.token } : {}
8638
+ };
8639
+ upsertMarketplace(all, entry);
8640
+ saveMarketplaces(configPath, existing, all);
8641
+ const targetDir = resolveAgentPaths(resolveAgentName(opts)).user;
8642
+ const { pluginResults, totalInstalled } = installAllPlugins(resolvedPath, marketplaceName, url, targetDir);
8643
+ success({
8644
+ name: marketplaceName,
8645
+ repoUrl: url,
8646
+ localPath: resolvedPath,
8647
+ branch: opts.branch ?? null,
8648
+ tokenConfigured: !!opts.token,
8649
+ plugins: pluginResults,
8650
+ total: totalInstalled,
8651
+ target: targetDir
8652
+ }, "skills", commandName, start);
8653
+ } catch (err) {
8654
+ fail(err, "skills", commandName, start);
8655
+ }
8656
+ }
7742
8657
  function registerSkillsCommands(program2) {
7743
8658
  const skills = program2.command("skills").description("Manage pncli Claude Code skills");
7744
8659
  skills.command("install").description("Install pncli skills into the current repo").option("--agent <agent>", "Target agent host: github-copilot | claude-code", "github-copilot").option("--scope <scope>", "Installation scope: project | user", "project").option("--target <dir>", "Override install directory (ignores --agent and --scope)").action((opts) => {
@@ -7748,10 +8663,7 @@ function registerSkillsCommands(program2) {
7748
8663
  if (opts.target) {
7749
8664
  targetDir = path7.resolve(opts.target);
7750
8665
  } else {
7751
- const agentConfig = AGENT_PATHS[opts.agent];
7752
- if (!agentConfig) {
7753
- throw new Error(`Unknown agent: "${opts.agent}". Use: ${Object.keys(AGENT_PATHS).join(" | ")}`);
7754
- }
8666
+ const agentConfig = resolveAgentPaths(opts.agent);
7755
8667
  const scopePath = opts.scope === "user" ? agentConfig.user : agentConfig.project;
7756
8668
  targetDir = path7.resolve(scopePath);
7757
8669
  }
@@ -7801,6 +8713,7 @@ function registerSkillsCommands(program2) {
7801
8713
  if (failed.length > 0) {
7802
8714
  warn(`Failed to install: ${failed.join(", ")}`);
7803
8715
  }
8716
+ recordInstalledSkills(targetDir, installed, { source: "bundled" });
7804
8717
  success({
7805
8718
  installed,
7806
8719
  failed,
@@ -7821,10 +8734,7 @@ function registerSkillsCommands(program2) {
7821
8734
  if (opts.target) {
7822
8735
  targetDir = path7.resolve(opts.target);
7823
8736
  } else {
7824
- const agentConfig = AGENT_PATHS[opts.agent];
7825
- if (!agentConfig) {
7826
- throw new Error(`Unknown agent: "${opts.agent}". Use: ${Object.keys(AGENT_PATHS).join(" | ")}`);
7827
- }
8737
+ const agentConfig = resolveAgentPaths(opts.agent);
7828
8738
  const scopePath = opts.scope === "user" ? agentConfig.user : agentConfig.project;
7829
8739
  targetDir = path7.resolve(scopePath);
7830
8740
  }
@@ -7832,7 +8742,9 @@ function registerSkillsCommands(program2) {
7832
8742
  success({ skills: [], message: `No skills directory found at ${targetDir}. Run: pncli skills install` }, "skills", "list", start);
7833
8743
  return;
7834
8744
  }
8745
+ const meta = readInstalledMeta(targetDir);
7835
8746
  const skillDirs = fs4.readdirSync(targetDir).filter((name) => {
8747
+ if (name.startsWith(".")) return false;
7836
8748
  const skillPath = path7.join(targetDir, name, "SKILL.md");
7837
8749
  return fs4.existsSync(skillPath);
7838
8750
  });
@@ -7867,7 +8779,8 @@ function registerSkillsCommands(program2) {
7867
8779
  category: metadata.category || data.category || "other",
7868
8780
  services: metadata.services || data.services || "",
7869
8781
  providers: metadata.providers || data.providers || "none",
7870
- userInvocable: data["user-invocable"] === "true"
8782
+ userInvocable: data["user-invocable"] === "true",
8783
+ installed: meta.skills[name] ?? null
7871
8784
  };
7872
8785
  });
7873
8786
  success({ skills: skillsList, total: skillsList.length }, "skills", "list", start);
@@ -7875,155 +8788,217 @@ function registerSkillsCommands(program2) {
7875
8788
  fail(err, "skills", "list", start);
7876
8789
  }
7877
8790
  });
7878
- const marketplace = skills.command("marketplace").description("Manage a git-hosted skills marketplace");
7879
- marketplace.command("setup").description("Clone a skills marketplace repo and register it in global config").argument("<url>", "Git clone URL of the marketplace repository").argument("[localPath]", "Local directory to clone into (default: ~/.agents/marketplaces/<repo-name>)").option("--branch <branch>", "Branch to clone (default: remote HEAD)").option("--token <token>", "HTTP access token for authenticated clone and pull (GitHub PAT or Bitbucket token)").action(async (url, localPath, opts) => {
8791
+ skills.command("uninstall").description("Uninstall a skill (defaults to user scope, matching the marketplace install target; pass --scope project for skills installed via `skills install`)").argument("<name>", "Skill name to uninstall (the directory name under your skills folder)").option("--agent <agent>", "Target agent host: github-copilot | claude-code", "github-copilot").option("--scope <scope>", "Installation scope to uninstall from: project | user", "user").option("--claude", "Shorthand for --agent claude-code").option("--target <dir>", "Override skills directory").action((name, opts) => {
7880
8792
  const start = Date.now();
7881
8793
  try {
7882
- const resolvedPath = path7.resolve(localPath ?? defaultMarketplacePath(url));
7883
- const hasGit = fs4.existsSync(path7.join(resolvedPath, ".git"));
7884
- if (fs4.existsSync(resolvedPath) && !hasGit && fs4.readdirSync(resolvedPath).length > 0) {
7885
- throw new Error(`Directory already exists and is not a git repo: ${resolvedPath}`);
7886
- }
7887
- if (hasGit) {
7888
- warn(`Directory already contains a git repo at ${resolvedPath} \u2014 skipping clone, updating config only.`);
8794
+ let targetDir;
8795
+ if (opts.target) {
8796
+ targetDir = path7.resolve(opts.target);
7889
8797
  } else {
7890
- const branchLabel = opts.branch ?? "remote default";
7891
- warn(`Cloning ${url} (branch: ${branchLabel}) \u2192 ${resolvedPath}...`);
7892
- const cloneUrl = opts.token ? injectTokenIntoUrl(url, opts.token) : url;
7893
- const cloneArgs = ["clone"];
7894
- if (opts.branch) cloneArgs.push("--branch", opts.branch);
7895
- cloneArgs.push(cloneUrl, resolvedPath);
7896
- try {
7897
- execFileSync3("git", cloneArgs, { stdio: ["inherit", "inherit", "pipe"] });
7898
- } catch (e) {
7899
- const msg = e instanceof Error ? e.message : String(e);
7900
- throw new Error(msg.replace(/x-(?:token-auth|access-token):[^@]+@/g, "x-token-auth:***@"));
7901
- }
8798
+ const agentConfig = resolveAgentPaths(resolveAgentName(opts));
8799
+ const scopePath = opts.scope === "project" ? agentConfig.project : agentConfig.user;
8800
+ targetDir = path7.resolve(scopePath);
7902
8801
  }
8802
+ const resolvedTarget = path7.resolve(targetDir);
8803
+ const skillDir = path7.resolve(targetDir, name);
8804
+ if (!skillDir.startsWith(resolvedTarget + path7.sep)) {
8805
+ throw new Error(`Invalid skill name: "${name}"`);
8806
+ }
8807
+ if (!fs4.existsSync(skillDir)) {
8808
+ throw new Error(`Skill "${name}" not found at ${skillDir}`);
8809
+ }
8810
+ const meta = readInstalledMeta(targetDir);
8811
+ const record = meta.skills[name];
8812
+ fs4.rmSync(skillDir, { recursive: true, force: true });
8813
+ delete meta.skills[name];
8814
+ fs4.writeFileSync(getInstalledMetaPath(targetDir), JSON.stringify(meta, null, 2), "utf8");
8815
+ success({
8816
+ uninstalled: name,
8817
+ target: targetDir,
8818
+ wasTracked: !!record,
8819
+ ...record ? { installedFrom: record } : {}
8820
+ }, "skills", "uninstall", start);
8821
+ } catch (err) {
8822
+ fail(err, "skills", "uninstall", start);
8823
+ }
8824
+ });
8825
+ const marketplace = skills.command("marketplace").description("Manage git-hosted skills marketplaces");
8826
+ function withMarketplaceAddOptions(cmd) {
8827
+ return cmd.argument("<url>", "Git clone URL of the marketplace repository").argument("[localPath]", "Local directory to clone into (default: ~/.agents/marketplaces/<repo-name>)").option("--name <name>", "Human-readable name for this marketplace (default: derived from URL)").option("--branch <branch>", "Branch to clone (default: remote HEAD)").option("--token <token>", "HTTP access token for authenticated clone and pull (GitHub PAT or Bitbucket token)").option("--agent <agent>", "Target agent host for plugin install: github-copilot | claude-code (default: github-copilot)").option("--claude", "Shorthand for --agent claude-code");
8828
+ }
8829
+ withMarketplaceAddOptions(
8830
+ marketplace.command("add").description("Register a new marketplace, clone it, and install all its plugins")
8831
+ ).action((url, localPath, opts) => marketplaceAddAction(url, localPath, opts, "marketplace-add"));
8832
+ withMarketplaceAddOptions(
8833
+ marketplace.command("setup").description("Alias for `marketplace add` \u2014 clone a marketplace and install all its plugins")
8834
+ ).action((url, localPath, opts) => marketplaceAddAction(url, localPath, opts, "marketplace-setup"));
8835
+ marketplace.command("list").description("List all registered marketplaces").action(() => {
8836
+ const start = Date.now();
8837
+ try {
7903
8838
  const configPath = getGlobalConfigPath();
7904
- const existing = loadJsonFile(configPath) ?? {};
7905
- existing.marketplace = { repoUrl: url, localPath: resolvedPath, ...opts.token ? { token: opts.token } : {} };
7906
- writeGlobalConfig(existing, configPath);
7907
- warn("Marketplace ready. Run: pncli skills marketplace sync to install skills.");
7908
- success({ repoUrl: url, localPath: resolvedPath, branch: opts.branch ?? null, tokenConfigured: !!opts.token }, "skills", "marketplace-setup", start);
8839
+ const all = loadMarketplaces(configPath);
8840
+ success({
8841
+ marketplaces: all.map((m) => ({
8842
+ name: marketplaceLabel(m),
8843
+ repoUrl: m.repoUrl,
8844
+ localPath: m.localPath,
8845
+ tokenConfigured: !!m.token
8846
+ })),
8847
+ total: all.length
8848
+ }, "skills", "marketplace-list", start);
7909
8849
  } catch (err) {
7910
- fail(err, "skills", "marketplace-setup", start);
8850
+ fail(err, "skills", "marketplace-list", start);
7911
8851
  }
7912
8852
  });
7913
- marketplace.command("sync").description("Pull latest marketplace content and install a plugin's skills").argument("[plugin]", 'Plugin name to install, or "all" to install every plugin (skips interactive selection)').option("--agent <agent>", "Target agent host: github-copilot | claude-code (default: github-copilot)").option("--claude", "Shorthand for --agent claude-code").option("--force", "Force reinstall even if the marketplace repo has no new changes").action(async (plugin, opts) => {
8853
+ marketplace.command("plugins").description("List the plugins available in a registered marketplace").argument("<name>", "Marketplace name (or repo URL) to inspect").action((name) => {
7914
8854
  const start = Date.now();
7915
8855
  try {
7916
8856
  const configPath = getGlobalConfigPath();
7917
- const globalConfig = loadJsonFile(configPath) ?? {};
7918
- const marketplacePath = globalConfig.marketplace?.localPath;
7919
- if (!marketplacePath || !fs4.existsSync(marketplacePath)) {
7920
- throw new Error("Marketplace not configured. Run: pncli skills marketplace setup <url> <localPath>");
7921
- }
7922
- warn("Pulling latest marketplace content...");
7923
- const repoUrl = globalConfig.marketplace?.repoUrl;
7924
- const token = globalConfig.marketplace?.token;
7925
- const gitArgs = ["-C", marketplacePath];
7926
- if (repoUrl && token) {
7927
- gitArgs.push("-c", `remote.origin.url=${injectTokenIntoUrl(repoUrl, token)}`);
7928
- }
7929
- gitArgs.push("pull");
7930
- let pullOutput;
7931
- try {
7932
- pullOutput = execFileSync3("git", gitArgs, { encoding: "utf8", stdio: ["inherit", "pipe", "pipe"], env: { ...process.env, LANG: "C", LC_ALL: "C" } });
7933
- } catch (e) {
7934
- const msg = e instanceof Error ? e.message : String(e);
7935
- throw new Error(msg.replace(/x-(?:token-auth|access-token):[^@]+@/g, "x-token-auth:***@"));
7936
- }
7937
- const marketplaceUpdated = !pullOutput.includes("Already up to date");
7938
- if (pullOutput.trim() && marketplaceUpdated) {
7939
- warn(pullOutput.trim());
7940
- }
7941
- warn(marketplaceUpdated ? "Marketplace updated." : "Marketplace already up to date \u2014 no changes to sync.");
7942
- const pluginChoices = resolvePluginChoices(marketplacePath);
7943
- if (pluginChoices.length === 0) {
7944
- throw new Error("No plugins found in marketplace. Check the marketplace repository structure.");
7945
- }
7946
- const agentName = opts.claude ? "claude-code" : opts.agent ?? "github-copilot";
7947
- const agentConfig = AGENT_PATHS[agentName];
7948
- if (!agentConfig) {
7949
- throw new Error(`Unknown agent: "${agentName}". Use: ${Object.keys(AGENT_PATHS).join(" | ")}`);
7950
- }
7951
- const targetDir = agentConfig.user;
7952
- let selectedPlugin;
7953
- if (plugin) {
7954
- if (plugin !== "all" && !pluginChoices.some((p) => p.name === plugin)) {
7955
- throw new Error(`Plugin "${plugin}" not found. Available: ${pluginChoices.map((p) => p.name).join(", ")}`);
8857
+ const all = loadMarketplaces(configPath);
8858
+ const found = all.find((m) => m.name === name || m.repoUrl === name);
8859
+ if (!found) {
8860
+ throw new Error(`Marketplace "${name}" not found. Run: pncli skills marketplace list`);
8861
+ }
8862
+ if (!found.localPath || !fs4.existsSync(found.localPath)) {
8863
+ throw new Error(`Marketplace "${marketplaceLabel(found)}" local path not found at ${found.localPath ?? "(not set)"}. Run: pncli skills marketplace add <url>`);
8864
+ }
8865
+ const plugins = resolvePluginChoices(found.localPath);
8866
+ success({
8867
+ marketplace: marketplaceLabel(found),
8868
+ plugins,
8869
+ total: plugins.length
8870
+ }, "skills", "marketplace-plugins", start);
8871
+ } catch (err) {
8872
+ fail(err, "skills", "marketplace-plugins", start);
8873
+ }
8874
+ });
8875
+ marketplace.command("remove").description("Remove a registered marketplace from the config (does not delete the local clone)").argument("<name>", "Name of the marketplace to remove").action((name) => {
8876
+ const start = Date.now();
8877
+ try {
8878
+ const configPath = getGlobalConfigPath();
8879
+ const { existing, all } = loadMarketplacesConfig(configPath);
8880
+ const idx = all.findIndex((m) => m.name === name || m.repoUrl === name);
8881
+ if (idx === -1) {
8882
+ throw new Error(`Marketplace "${name}" not found. Run: pncli skills marketplace list`);
8883
+ }
8884
+ const removed = all.splice(idx, 1)[0];
8885
+ saveMarketplaces(configPath, existing, all);
8886
+ success({
8887
+ removed: {
8888
+ name: marketplaceLabel(removed),
8889
+ repoUrl: removed.repoUrl,
8890
+ localPath: removed.localPath
8891
+ },
8892
+ remaining: all.length
8893
+ }, "skills", "marketplace-remove", start);
8894
+ } catch (err) {
8895
+ fail(err, "skills", "marketplace-remove", start);
8896
+ }
8897
+ });
8898
+ marketplace.command("sync").description("Pull latest marketplace content and install plugin skills").argument("[plugin]", 'Plugin name to install, or "all" to install every plugin (skips interactive selection)').option("--marketplace <name>", 'Marketplace name to sync, or "all" to sync every registered marketplace (skips interactive selection)').option("--agent <agent>", "Target agent host: github-copilot | claude-code (default: github-copilot)").option("--claude", "Shorthand for --agent claude-code").option("--force", 'Reinstall even if a marketplace has no new changes (applies to single-plugin and "all" installs alike)').action(async (plugin, opts) => {
8899
+ const start = Date.now();
8900
+ try {
8901
+ const configPath = getGlobalConfigPath();
8902
+ const allMarketplaces = loadMarketplaces(configPath);
8903
+ if (allMarketplaces.length === 0) {
8904
+ throw new Error("No marketplaces configured. Run: pncli skills marketplace add <url>");
8905
+ }
8906
+ const targetDir = resolveAgentPaths(resolveAgentName(opts)).user;
8907
+ const force = opts.force ?? false;
8908
+ if (opts.marketplace === "all") {
8909
+ const results = allMarketplaces.map((m) => syncMarketplacePlugins(m, targetDir, force, plugin ?? "all"));
8910
+ success({ allMarketplaces: true, marketplaces: results, target: targetDir }, "skills", "marketplace-sync", start);
8911
+ return;
8912
+ }
8913
+ let selectedMarketplace;
8914
+ let selectedPlugin = plugin;
8915
+ const canGoBack = !opts.marketplace && allMarketplaces.length > 1;
8916
+ if (opts.marketplace) {
8917
+ const found = allMarketplaces.find((m) => m.name === opts.marketplace || m.repoUrl === opts.marketplace);
8918
+ if (!found) {
8919
+ throw new Error(`Marketplace "${opts.marketplace}" not found. Run: pncli skills marketplace list`);
7956
8920
  }
7957
- selectedPlugin = plugin;
7958
- } else {
7959
- selectedPlugin = await select({
7960
- message: "Select a plugin to install:",
7961
- choices: [
8921
+ selectedMarketplace = found;
8922
+ } else if (allMarketplaces.length === 1) {
8923
+ selectedMarketplace = allMarketplaces[0];
8924
+ }
8925
+ for (; ; ) {
8926
+ if (!selectedMarketplace) {
8927
+ assertInteractive(`Multiple marketplaces are registered (${allMarketplaces.map(marketplaceLabel).join(", ")}). Pass --marketplace <name> (or --marketplace all) to run this non-interactively.`);
8928
+ const chosen = await select({
8929
+ message: "Select a marketplace to sync:",
8930
+ choices: [
8931
+ { value: ALL_MARKETPLACES, name: "All marketplaces \u2014 sync every plugin from every marketplace" },
8932
+ // Index-based values: labels alone aren't guaranteed unique (two marketplaces can share a derived name).
8933
+ ...allMarketplaces.map((m, i) => ({ value: String(i), name: `${marketplaceLabel(m)} \u2014 ${m.repoUrl ?? ""}` }))
8934
+ ]
8935
+ });
8936
+ if (chosen === ALL_MARKETPLACES) {
8937
+ const results2 = allMarketplaces.map((m) => syncMarketplacePlugins(m, targetDir, force, selectedPlugin ?? "all"));
8938
+ success({ allMarketplaces: true, marketplaces: results2, target: targetDir }, "skills", "marketplace-sync", start);
8939
+ return;
8940
+ }
8941
+ selectedMarketplace = allMarketplaces[Number(chosen)];
8942
+ if (!selectedMarketplace) {
8943
+ throw new Error(`Marketplace "${chosen}" not found.`);
8944
+ }
8945
+ }
8946
+ const marketplaceName = marketplaceLabel(selectedMarketplace);
8947
+ const marketplacePath = selectedMarketplace.localPath;
8948
+ if (!marketplacePath || !fs4.existsSync(marketplacePath)) {
8949
+ throw new Error(`Marketplace "${marketplaceName}" local path not found at ${marketplacePath ?? "(not set)"}. Run: pncli skills marketplace add <url>`);
8950
+ }
8951
+ const { updated } = pullMarketplace(marketplacePath, selectedMarketplace.repoUrl, selectedMarketplace.token, marketplaceName);
8952
+ const pluginChoices = resolvePluginChoices(marketplacePath);
8953
+ if (pluginChoices.length === 0) {
8954
+ throw new Error(`No plugins found in marketplace "${marketplaceName}". Check the marketplace repository structure.`);
8955
+ }
8956
+ if (!selectedPlugin) {
8957
+ assertInteractive(`Pass a plugin name (or "all") to run this non-interactively.`);
8958
+ const choices = [
7962
8959
  { value: "all", name: "All \u2014 install every plugin" },
7963
- ...pluginChoices.map((p) => ({
7964
- value: p.name,
7965
- name: p.description ? `${p.name} \u2014 ${p.description}` : p.name
7966
- }))
7967
- ]
7968
- });
7969
- }
7970
- if (selectedPlugin === "all") {
7971
- if (!marketplaceUpdated && !opts.force) {
8960
+ ...pluginChoices.map((p) => ({ value: p.name, name: p.description ? `${p.name} \u2014 ${p.description}` : p.name }))
8961
+ ];
8962
+ if (canGoBack) {
8963
+ choices.push({ value: BACK, name: "\u2190 Back to marketplace selection" });
8964
+ }
8965
+ const chosen = await select({ message: `Select a plugin from "${marketplaceName}" to install:`, choices });
8966
+ if (chosen === BACK) {
8967
+ selectedMarketplace = void 0;
8968
+ continue;
8969
+ }
8970
+ selectedPlugin = chosen;
8971
+ } else if (selectedPlugin !== "all" && !pluginChoices.some((p) => p.name === selectedPlugin)) {
8972
+ throw new Error(`Plugin "${selectedPlugin}" not found in "${marketplaceName}". Available: ${pluginChoices.map((p) => p.name).join(", ")}`);
8973
+ }
8974
+ if (!updated && !force) {
7972
8975
  success({
7973
- marketplace: marketplacePath,
8976
+ marketplace: marketplaceName,
7974
8977
  marketplaceUpdated: false,
7975
8978
  updated: false,
7976
8979
  skipped: true,
7977
- message: "No marketplace changes detected \u2014 skipping install."
8980
+ message: `No changes detected in "${marketplaceName}" \u2014 skipping install. Use --force to reinstall anyway.`
7978
8981
  }, "skills", "marketplace-sync", start);
7979
8982
  return;
7980
8983
  }
7981
- const results = {};
7982
- let totalInstalled = 0;
7983
- for (const pluginChoice of pluginChoices) {
7984
- const skillsSrc2 = resolveSkillsSrc(marketplacePath, pluginChoice.name);
7985
- if (!fs4.existsSync(skillsSrc2)) {
7986
- results[pluginChoice.name] = { installed: [], failed: [] };
7987
- warn(`No skills directory found for plugin "${pluginChoice.name}" \u2014 skipping.`);
7988
- continue;
7989
- }
7990
- const { installed: installed2, failed: failed2 } = copyPluginSkills(skillsSrc2, targetDir);
7991
- results[pluginChoice.name] = { installed: installed2, failed: failed2 };
7992
- totalInstalled += installed2.length;
7993
- for (const skill of installed2) {
7994
- warn(` ${skill}: ${path7.join(skillsSrc2, skill)} \u2192 ${path7.join(targetDir, skill)}`);
7995
- }
7996
- if (failed2.length > 0) {
7997
- warn(`Skipped ${failed2.length} skill(s) with invalid names in "${pluginChoice.name}": ${failed2.join(", ")}`);
7998
- }
8984
+ const pluginNames = selectedPlugin === "all" ? pluginChoices.map((p) => p.name) : [selectedPlugin];
8985
+ const { results, totalInstalled } = installPluginsForMarketplace(marketplacePath, marketplaceName, selectedMarketplace.repoUrl, pluginNames, targetDir);
8986
+ if (selectedPlugin === "all") {
8987
+ success({ marketplace: marketplaceName, plugins: results, total: totalInstalled, target: targetDir, marketplaceUpdated: updated }, "skills", "marketplace-sync", start);
8988
+ } else {
8989
+ const single = results[selectedPlugin] ?? { installed: [], failed: [] };
8990
+ success({
8991
+ marketplace: marketplaceName,
8992
+ plugin: selectedPlugin,
8993
+ installed: single.installed,
8994
+ failed: single.failed,
8995
+ total: single.installed.length,
8996
+ target: targetDir,
8997
+ marketplaceUpdated: updated
8998
+ }, "skills", "marketplace-sync", start);
7999
8999
  }
8000
- success({
8001
- plugins: results,
8002
- total: totalInstalled,
8003
- target: targetDir,
8004
- marketplaceUpdated
8005
- }, "skills", "marketplace-sync", start);
8006
9000
  return;
8007
9001
  }
8008
- const skillsSrc = resolveSkillsSrc(marketplacePath, selectedPlugin);
8009
- if (!fs4.existsSync(skillsSrc)) {
8010
- throw new Error(`No skills directory found at ${skillsSrc}`);
8011
- }
8012
- const { installed, failed } = copyPluginSkills(skillsSrc, targetDir);
8013
- for (const skill of installed) {
8014
- warn(` ${skill}: ${path7.join(skillsSrc, skill)} \u2192 ${path7.join(targetDir, skill)}`);
8015
- }
8016
- if (failed.length > 0) {
8017
- warn(`Skipped ${failed.length} skill(s) with invalid names: ${failed.join(", ")}`);
8018
- }
8019
- success({
8020
- plugin: selectedPlugin,
8021
- installed,
8022
- failed,
8023
- total: installed.length,
8024
- target: targetDir,
8025
- marketplaceUpdated
8026
- }, "skills", "marketplace-sync", start);
8027
9002
  } catch (err) {
8028
9003
  fail(err, "skills", "marketplace-sync", start);
8029
9004
  }
@@ -8054,7 +9029,7 @@ function resolveSkillsSrc(marketplacePath, selectedPlugin) {
8054
9029
  }
8055
9030
  return skillsSrc;
8056
9031
  }
8057
- function copyPluginSkills(skillsSrc, targetDir) {
9032
+ function copyPluginSkills(skillsSrc, targetDir, meta) {
8058
9033
  fs4.mkdirSync(targetDir, { recursive: true });
8059
9034
  const resolvedTarget = path7.resolve(targetDir);
8060
9035
  const skillNames = fs4.readdirSync(skillsSrc).filter(
@@ -8072,6 +9047,14 @@ function copyPluginSkills(skillsSrc, targetDir) {
8072
9047
  fs4.cpSync(path7.join(skillsSrc, skillName), dest, { recursive: true });
8073
9048
  installed.push(skillName);
8074
9049
  }
9050
+ if (meta) {
9051
+ recordInstalledSkills(targetDir, installed, {
9052
+ source: "marketplace",
9053
+ marketplace: meta.marketplace,
9054
+ plugin: meta.plugin,
9055
+ installedFrom: meta.installedFrom
9056
+ });
9057
+ }
8075
9058
  return { installed, failed };
8076
9059
  }
8077
9060
 
@@ -8338,7 +9321,7 @@ if (!process.env.PNCLI_VERIFY_TLS) {
8338
9321
  var require2 = createRequire(import.meta.url);
8339
9322
  var pkg = require2("../package.json");
8340
9323
  var TAGLINE = "One command does what three meetings couldn't.";
8341
- var program = new Command2();
9324
+ var program = new Command3();
8342
9325
  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("--debug", "Print diagnostic traces (method, URL, status) for all API calls \u2014 does not log credentials", 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");
8343
9326
  program.hook("preAction", (thisCommand) => {
8344
9327
  const opts = thisCommand.optsWithGlobals();
@@ -8357,6 +9340,7 @@ program.hook("preAction", (thisCommand) => {
8357
9340
  registerGitCommands(program);
8358
9341
  registerJiraCommands(program);
8359
9342
  registerBitbucketCommands(program);
9343
+ registerGitHubCommands(program);
8360
9344
  registerConfluenceCommands(program);
8361
9345
  registerSonarCommands(program);
8362
9346
  registerSdeCommands(program);
@@ -8379,6 +9363,7 @@ Services:
8379
9363
  deps Dependency scanning, CVE detection, license auditing
8380
9364
  jira Jira Data Cloud
8381
9365
  bitbucket Bitbucket Server
9366
+ github GitHub (PRs, reviews, comments, checks)
8382
9367
  confluence Confluence
8383
9368
  sonar SonarQube Server (quality gates, issues, metrics, hotspots)
8384
9369
  sde SDElements (threat modeling, countermeasures, compliance)