@kolatts/pncli 1.10.0 → 1.11.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.
Files changed (34) hide show
  1. package/copilot-instructions.md +34 -1
  2. package/dist/{chunk-O3LWAWSN.js → chunk-AWSXVWRN.js} +2 -1
  3. package/dist/{chunk-O3LWAWSN.js.map → chunk-AWSXVWRN.js.map} +1 -1
  4. package/dist/{chunk-3XHTRFRX.js → chunk-VPUWMP2M.js} +3 -2
  5. package/dist/chunk-VPUWMP2M.js.map +1 -0
  6. package/dist/cli.js +410 -36
  7. package/dist/cli.js.map +1 -1
  8. package/dist/{config-NCI2BTQG.js → config-OFLPO5DN.js} +4 -2
  9. package/dist/{http-L2P23H2D.js → http-LNDRNPP7.js} +2 -2
  10. package/package.json +1 -1
  11. package/skills/pncli/SKILL.md +92 -0
  12. package/skills/pncli/ado.md +33 -0
  13. package/skills/pncli/artifactory.md +35 -0
  14. package/skills/pncli/bitbucket.md +30 -0
  15. package/skills/pncli/checkmarx.md +31 -0
  16. package/skills/pncli/confluence.md +24 -0
  17. package/skills/pncli/contrast.md +39 -0
  18. package/skills/pncli/jenkins.md +29 -0
  19. package/skills/pncli/jira.md +35 -0
  20. package/skills/pncli/marketplace.md +41 -0
  21. package/skills/pncli/sde.md +29 -0
  22. package/skills/pncli/servicenow.md +33 -0
  23. package/skills/pncli/sonarqube.md +30 -0
  24. package/skills/pncli/sonatypeiq.md +39 -0
  25. package/skills/pncli/udeploy.md +48 -0
  26. package/dist/chunk-3XHTRFRX.js.map +0 -1
  27. package/skills/address-pr-feedback/SKILL.md +0 -87
  28. package/skills/code-review/SKILL.md +0 -82
  29. package/skills/local-setup/SKILL.md +0 -225
  30. package/skills/plan/SKILL.md +0 -120
  31. package/skills/security-review/SKILL.md +0 -108
  32. package/skills/ship/SKILL.md +0 -170
  33. /package/dist/{config-NCI2BTQG.js.map → config-OFLPO5DN.js.map} +0 -0
  34. /package/dist/{http-L2P23H2D.js.map → http-LNDRNPP7.js.map} +0 -0
package/dist/cli.js CHANGED
@@ -2,12 +2,13 @@
2
2
  import {
3
3
  getGlobalConfigPath,
4
4
  loadConfig,
5
+ loadJsonFile,
5
6
  maskConfig,
6
7
  setConfigValue,
7
8
  setRepoConfigValue,
8
9
  writeGlobalConfig,
9
10
  writeRepoConfig
10
- } from "./chunk-O3LWAWSN.js";
11
+ } from "./chunk-AWSXVWRN.js";
11
12
  import {
12
13
  ExitCode,
13
14
  PncliError,
@@ -18,7 +19,7 @@ import {
18
19
  setGlobalUser,
19
20
  success,
20
21
  warn
21
- } from "./chunk-3XHTRFRX.js";
22
+ } from "./chunk-VPUWMP2M.js";
22
23
  import {
23
24
  AdoCoreClient
24
25
  } from "./chunk-VDL32PSG.js";
@@ -161,6 +162,201 @@ function getBranches(root) {
161
162
  }
162
163
  return { current, local, remote };
163
164
  }
165
+ var COMMIT_PREFIX = "__PNCLI_COMMIT__";
166
+ function getWeekMonday(dateStr) {
167
+ const d = new Date(dateStr);
168
+ const day = d.getUTCDay();
169
+ const diff = day === 0 ? -6 : 1 - day;
170
+ const monday = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() + diff));
171
+ return monday.toISOString().slice(0, 10);
172
+ }
173
+ function getWeekSunday(mondayStr) {
174
+ const d = new Date(mondayStr);
175
+ const sunday = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() + 6));
176
+ return sunday.toISOString().slice(0, 10);
177
+ }
178
+ function getISOWeekLabel(mondayStr) {
179
+ const d = new Date(mondayStr);
180
+ const thursday = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() + 3));
181
+ const year = thursday.getUTCFullYear();
182
+ const jan4 = new Date(Date.UTC(year, 0, 4));
183
+ const jan4Monday = new Date(Date.UTC(year, 0, 4 - (jan4.getUTCDay() === 0 ? 6 : jan4.getUTCDay() - 1)));
184
+ const weekNum = Math.round((d.getTime() - jan4Monday.getTime()) / (7 * 864e5)) + 1;
185
+ return `${year}-W${String(weekNum).padStart(2, "0")}`;
186
+ }
187
+ function buildWeeklyBreakdown(commits) {
188
+ const weekMap = /* @__PURE__ */ new Map();
189
+ for (const commit of commits) {
190
+ const monday = getWeekMonday(commit.date);
191
+ if (!weekMap.has(monday)) weekMap.set(monday, /* @__PURE__ */ new Map());
192
+ const authorMap = weekMap.get(monday);
193
+ if (!authorMap.has(commit.author)) {
194
+ authorMap.set(commit.author, {
195
+ author: commit.author,
196
+ commitCount: 0,
197
+ insertions: 0,
198
+ deletions: 0,
199
+ netLines: 0,
200
+ filesChanged: 0
201
+ });
202
+ }
203
+ const stats = authorMap.get(commit.author);
204
+ stats.commitCount++;
205
+ stats.insertions += commit.insertions;
206
+ stats.deletions += commit.deletions;
207
+ stats.netLines += commit.netLines;
208
+ stats.filesChanged += commit.filesChanged;
209
+ }
210
+ return [...weekMap.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([monday, authorMap]) => {
211
+ const authors = [...authorMap.values()].sort((a, b) => a.author.localeCompare(b.author));
212
+ return {
213
+ week: getISOWeekLabel(monday),
214
+ weekStart: monday,
215
+ weekEnd: getWeekSunday(monday),
216
+ authors,
217
+ summary: {
218
+ commitCount: authors.reduce((s, a) => s + a.commitCount, 0),
219
+ totalInsertions: authors.reduce((s, a) => s + a.insertions, 0),
220
+ totalDeletions: authors.reduce((s, a) => s + a.deletions, 0),
221
+ totalNetLines: authors.reduce((s, a) => s + a.netLines, 0),
222
+ totalFilesChanged: authors.reduce((s, a) => s + a.filesChanged, 0)
223
+ }
224
+ };
225
+ });
226
+ }
227
+ function buildByAuthor(commits) {
228
+ const map = /* @__PURE__ */ new Map();
229
+ for (const commit of commits) {
230
+ if (!map.has(commit.author)) {
231
+ map.set(commit.author, {
232
+ author: commit.author,
233
+ commitCount: 0,
234
+ insertions: 0,
235
+ deletions: 0,
236
+ netLines: 0,
237
+ filesChanged: 0
238
+ });
239
+ }
240
+ const stats = map.get(commit.author);
241
+ stats.commitCount++;
242
+ stats.insertions += commit.insertions;
243
+ stats.deletions += commit.deletions;
244
+ stats.netLines += commit.netLines;
245
+ stats.filesChanged += commit.filesChanged;
246
+ }
247
+ return [...map.values()].sort((a, b) => a.author.localeCompare(b.author));
248
+ }
249
+ function parseBranchReportOutput(raw, branchName, opts) {
250
+ const commits = [];
251
+ let current = null;
252
+ for (const line of raw.split("\n")) {
253
+ if (line.startsWith(COMMIT_PREFIX)) {
254
+ if (current) commits.push(current);
255
+ const parts = line.slice(COMMIT_PREFIX.length).split(" ");
256
+ current = {
257
+ hash: parts[0] ?? "",
258
+ author: parts[1] ?? "",
259
+ date: parts[2] ?? "",
260
+ message: parts[3] ?? "",
261
+ filesChanged: 0,
262
+ insertions: 0,
263
+ deletions: 0,
264
+ netLines: 0
265
+ };
266
+ } else if (current && /^(\d+|-)\t(\d+|-)/.test(line)) {
267
+ const [ins, del] = line.split(" ");
268
+ const insertions = ins === "-" ? 0 : parseInt(ins ?? "0", 10);
269
+ const deletions = del === "-" ? 0 : parseInt(del ?? "0", 10);
270
+ if (!isNaN(insertions) && !isNaN(deletions)) {
271
+ current.insertions += insertions;
272
+ current.deletions += deletions;
273
+ current.filesChanged++;
274
+ current.netLines = current.insertions - current.deletions;
275
+ }
276
+ }
277
+ }
278
+ if (current) commits.push(current);
279
+ const totalInsertions = commits.reduce((sum, c) => sum + c.insertions, 0);
280
+ const totalDeletions = commits.reduce((sum, c) => sum + c.deletions, 0);
281
+ return {
282
+ branch: branchName,
283
+ since: opts.since ?? null,
284
+ until: opts.until ?? null,
285
+ commits,
286
+ weeks: buildWeeklyBreakdown(commits),
287
+ byAuthor: buildByAuthor(commits),
288
+ summary: {
289
+ commitCount: commits.length,
290
+ totalInsertions,
291
+ totalDeletions,
292
+ totalNetLines: totalInsertions - totalDeletions,
293
+ totalFilesChanged: commits.reduce((sum, c) => sum + c.filesChanged, 0)
294
+ }
295
+ };
296
+ }
297
+ function buildReportArgs(opts) {
298
+ const fmt = `${COMMIT_PREFIX}%H %an %aI %s`;
299
+ const args = ["log", `--format=${fmt}`, "--numstat"];
300
+ if (opts.since) args.push(`--since=${opts.since}`);
301
+ if (opts.until) args.push(`--until=${opts.until}`);
302
+ if (opts.base && opts.branch) {
303
+ args.push(`${opts.base}..${opts.branch}`);
304
+ } else if (opts.base) {
305
+ args.push(`${opts.base}..HEAD`);
306
+ } else if (opts.branch) {
307
+ args.push(opts.branch);
308
+ }
309
+ return args;
310
+ }
311
+ function getBranchReport(root, opts) {
312
+ const args = buildReportArgs(opts);
313
+ let raw = "";
314
+ try {
315
+ raw = execFileSync("git", args, { encoding: "utf8", cwd: root }).trim();
316
+ } catch {
317
+ }
318
+ let branchName = opts.branch ?? "";
319
+ if (!branchName) {
320
+ try {
321
+ branchName = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
322
+ encoding: "utf8",
323
+ cwd: root
324
+ }).trim();
325
+ } catch {
326
+ branchName = "HEAD";
327
+ }
328
+ }
329
+ return parseBranchReportOutput(raw, branchName, opts);
330
+ }
331
+ function escapeCsvField(value) {
332
+ if (value.includes(",") || value.includes('"') || value.includes("\n") || value.includes("\r")) {
333
+ return `"${value.replace(/"/g, '""')}"`;
334
+ }
335
+ return value;
336
+ }
337
+ function formatBranchReportCsv(report) {
338
+ const header = "week,week_start,author,commits,insertions,deletions,net_lines,files_changed";
339
+ const rows = [];
340
+ for (const week of report.weeks) {
341
+ for (const a of week.authors) {
342
+ rows.push(
343
+ [
344
+ week.week,
345
+ week.weekStart,
346
+ escapeCsvField(a.author),
347
+ a.commitCount,
348
+ a.insertions,
349
+ a.deletions,
350
+ a.netLines,
351
+ a.filesChanged
352
+ ].join(",")
353
+ );
354
+ }
355
+ }
356
+ const s = report.summary;
357
+ const totalRow = ["TOTAL", "", "", s.commitCount, s.totalInsertions, s.totalDeletions, s.totalNetLines, s.totalFilesChanged].join(",");
358
+ return [header, ...rows, totalRow].join("\n") + "\n";
359
+ }
164
360
 
165
361
  // src/lib/git-context.ts
166
362
  import { execSync as execSync2 } from "child_process";
@@ -529,6 +725,25 @@ function registerGitCommands(program2) {
529
725
  fail(err, "git", "branch", start);
530
726
  }
531
727
  });
728
+ git.command("report").description("Report lines of code and commit counts for a branch, optionally filtered by date").option("--branch <name>", "Branch to report on (defaults to current branch)").option("--base <ref>", 'Base ref to compare against, e.g. "main" (base..branch, or base..HEAD if --branch is omitted)').option("--since <date>", 'Include commits on or after this date (e.g. "2024-01-01")').option("--until <date>", 'Include commits on or before this date (e.g. "2024-12-31")').option("--csv", "Output as CSV instead of JSON").action((opts) => {
729
+ const start = Date.now();
730
+ try {
731
+ const root = requireRepoRoot();
732
+ const report = getBranchReport(root, {
733
+ branch: opts.branch,
734
+ base: opts.base,
735
+ since: opts.since,
736
+ until: opts.until
737
+ });
738
+ if (opts.csv) {
739
+ process.stdout.write(formatBranchReportCsv(report));
740
+ } else {
741
+ success(report, "git", "report", start);
742
+ }
743
+ } catch (err) {
744
+ fail(err, "git", "report", start);
745
+ }
746
+ });
532
747
  git.command("current-pr").description("Find the open PR for the current branch").action(async () => {
533
748
  const start = Date.now();
534
749
  try {
@@ -4366,6 +4581,11 @@ function registerConfigCommands(program2) {
4366
4581
  }
4367
4582
  });
4368
4583
  }
4584
+ function normalizeBaseUrl(raw) {
4585
+ const trimmed = raw.trim();
4586
+ if (!trimmed) return trimmed;
4587
+ return /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
4588
+ }
4369
4589
  async function initGlobalConfig(start) {
4370
4590
  process.stderr.write("pncli config init \u2014 Global configuration\n\n");
4371
4591
  process.stderr.write("\u2500\u2500 Identity \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
@@ -4379,7 +4599,7 @@ async function initGlobalConfig(start) {
4379
4599
  });
4380
4600
  process.stderr.write("\n\u2500\u2500 Jira \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
4381
4601
  const jiraBaseUrl = await input({
4382
- message: "Jira base URL (e.g. https://jira.your-company.com):",
4602
+ message: "Jira base URL (e.g. jira.your-company.com):",
4383
4603
  default: ""
4384
4604
  });
4385
4605
  const jiraApiToken = await password({
@@ -4387,7 +4607,7 @@ async function initGlobalConfig(start) {
4387
4607
  });
4388
4608
  process.stderr.write("\n\u2500\u2500 Bitbucket \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
4389
4609
  const bitbucketBaseUrl = await input({
4390
- message: "Bitbucket Server base URL (e.g. https://bitbucket.your-company.com):",
4610
+ message: "Bitbucket Server base URL (e.g. bitbucket.your-company.com):",
4391
4611
  default: ""
4392
4612
  });
4393
4613
  const bitbucketPat = await password({
@@ -4395,7 +4615,7 @@ async function initGlobalConfig(start) {
4395
4615
  });
4396
4616
  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");
4397
4617
  const confluenceBaseUrl = await input({
4398
- message: "Confluence base URL (e.g. https://confluence.your-company.com):",
4618
+ message: "Confluence base URL (e.g. confluence.your-company.com):",
4399
4619
  default: ""
4400
4620
  });
4401
4621
  const confluenceApiToken = await password({
@@ -4413,7 +4633,7 @@ async function initGlobalConfig(start) {
4413
4633
  let mavenRepo = "";
4414
4634
  if (useArtifactory) {
4415
4635
  artifactoryBaseUrl = await input({
4416
- message: "Artifactory base URL (e.g. https://artifactory.company.com):",
4636
+ message: "Artifactory base URL (e.g. artifactory.company.com):",
4417
4637
  default: ""
4418
4638
  });
4419
4639
  artifactoryToken = await password({
@@ -4442,7 +4662,7 @@ async function initGlobalConfig(start) {
4442
4662
  let sonarToken = "";
4443
4663
  if (useSonar) {
4444
4664
  sonarBaseUrl = await input({
4445
- message: "SonarQube Server base URL (e.g. https://sonar.your-company.com):",
4665
+ message: "SonarQube Server base URL (e.g. sonar.your-company.com):",
4446
4666
  default: ""
4447
4667
  });
4448
4668
  sonarToken = await password({
@@ -4495,7 +4715,7 @@ async function initGlobalConfig(start) {
4495
4715
  let adoDiscoveredTypes = [];
4496
4716
  if (useAdo) {
4497
4717
  adoBaseUrl = await input({
4498
- message: "Azure DevOps Server base URL (e.g. https://tfs.company.com or https://devops.company.com/tfs):",
4718
+ message: "Azure DevOps Server base URL (e.g. tfs.company.com or devops.company.com/tfs):",
4499
4719
  default: ""
4500
4720
  });
4501
4721
  adoPat = await password({
@@ -4515,7 +4735,7 @@ async function initGlobalConfig(start) {
4515
4735
  const tempConfig = {
4516
4736
  ...loadConfig(),
4517
4737
  ado: {
4518
- baseUrl: adoBaseUrl,
4738
+ baseUrl: normalizeBaseUrl(adoBaseUrl),
4519
4739
  pat: adoPat || void 0,
4520
4740
  fieldAliases: {},
4521
4741
  discoveredFields: [],
@@ -4567,7 +4787,7 @@ async function initGlobalConfig(start) {
4567
4787
  let jenkinsApiToken = "";
4568
4788
  if (useJenkins) {
4569
4789
  jenkinsBaseUrl = await input({
4570
- message: "Jenkins base URL (e.g. https://jenkins.your-company.com):",
4790
+ message: "Jenkins base URL (e.g. jenkins.your-company.com):",
4571
4791
  default: ""
4572
4792
  });
4573
4793
  jenkinsUsername = await input({
@@ -4591,7 +4811,7 @@ async function initGlobalConfig(start) {
4591
4811
  let udeployDefaultEnv = "";
4592
4812
  if (useUdeploy) {
4593
4813
  udeployBaseUrl = await input({
4594
- message: "UDeploy base URL (e.g. https://ucd.company.com:8443):",
4814
+ message: "UDeploy base URL (e.g. ucd.company.com:8443):",
4595
4815
  default: ""
4596
4816
  });
4597
4817
  udeployUsername = await input({
@@ -4623,7 +4843,7 @@ async function initGlobalConfig(start) {
4623
4843
  const tempConfig = {
4624
4844
  ...loadConfig(),
4625
4845
  udeploy: {
4626
- baseUrl: udeployBaseUrl,
4846
+ baseUrl: normalizeBaseUrl(udeployBaseUrl),
4627
4847
  pat: udeployPat || void 0,
4628
4848
  username: udeployUsername || void 0,
4629
4849
  password: udeployPassword || void 0
@@ -4648,7 +4868,7 @@ async function initGlobalConfig(start) {
4648
4868
  let checkmarxPassword = "";
4649
4869
  if (useCheckmarx) {
4650
4870
  checkmarxBaseUrl = await input({
4651
- message: "Checkmarx base URL (e.g. https://cx.company.com):",
4871
+ message: "Checkmarx base URL (e.g. cx.company.com):",
4652
4872
  default: ""
4653
4873
  });
4654
4874
  checkmarxUsername = await input({
@@ -4665,7 +4885,7 @@ async function initGlobalConfig(start) {
4665
4885
  const tempConfig = {
4666
4886
  ...loadConfig(),
4667
4887
  checkmarx: {
4668
- baseUrl: checkmarxBaseUrl,
4888
+ baseUrl: normalizeBaseUrl(checkmarxBaseUrl),
4669
4889
  username: checkmarxUsername,
4670
4890
  password: checkmarxPassword
4671
4891
  }
@@ -4690,7 +4910,7 @@ async function initGlobalConfig(start) {
4690
4910
  let servicenowApiToken = "";
4691
4911
  if (useServiceNow) {
4692
4912
  servicenowBaseUrl = await input({
4693
- message: "ServiceNow instance URL (e.g. https://mycompany.service-now.com):",
4913
+ message: "ServiceNow instance URL (e.g. mycompany.service-now.com):",
4694
4914
  default: ""
4695
4915
  });
4696
4916
  servicenowUsername = await input({
@@ -4719,7 +4939,7 @@ async function initGlobalConfig(start) {
4719
4939
  const tempConfig = {
4720
4940
  ...loadConfig(),
4721
4941
  servicenow: {
4722
- baseUrl: servicenowBaseUrl,
4942
+ baseUrl: normalizeBaseUrl(servicenowBaseUrl),
4723
4943
  username: servicenowUsername,
4724
4944
  password: servicenowPassword || void 0,
4725
4945
  apiToken: servicenowApiToken || void 0
@@ -4745,7 +4965,7 @@ async function initGlobalConfig(start) {
4745
4965
  if (useSonatypeIq) {
4746
4966
  process.stderr.write(" User Token credentials are found in your IQ Server profile under User Token.\n");
4747
4967
  sonatypeIqBaseUrl = await input({
4748
- message: "Sonatype IQ Server base URL (e.g. https://iq.your-company.com):",
4968
+ message: "Sonatype IQ Server base URL (e.g. iq.your-company.com):",
4749
4969
  default: ""
4750
4970
  });
4751
4971
  sonatypeIqUserCode = await input({
@@ -4761,7 +4981,7 @@ async function initGlobalConfig(start) {
4761
4981
  const tempConfig = {
4762
4982
  ...loadConfig(),
4763
4983
  sonatypeiq: {
4764
- baseUrl: sonatypeIqBaseUrl,
4984
+ baseUrl: normalizeBaseUrl(sonatypeIqBaseUrl),
4765
4985
  userCode: sonatypeIqUserCode,
4766
4986
  passcode: sonatypeIqPasscode
4767
4987
  }
@@ -4788,7 +5008,8 @@ async function initGlobalConfig(start) {
4788
5008
  if (useContrast) {
4789
5009
  process.stderr.write(" Find your credentials in Contrast under User Settings \u2192 Your Keys.\n");
4790
5010
  contrastBaseUrl = await input({
4791
- message: "Contrast base URL (leave blank for cloud: https://app.contrastsecurity.com):",
5011
+ message: "Contrast base URL (e.g. https://app.contrastsecurity.com for cloud SaaS):",
5012
+ validate: (v) => v.trim() ? true : "Base URL is required",
4792
5013
  default: ""
4793
5014
  });
4794
5015
  contrastOrgUuid = await input({
@@ -4812,7 +5033,7 @@ async function initGlobalConfig(start) {
4812
5033
  const tempConfig = {
4813
5034
  ...loadConfig(),
4814
5035
  contrast: {
4815
- baseUrl: contrastBaseUrl || void 0,
5036
+ baseUrl: normalizeBaseUrl(contrastBaseUrl) || void 0,
4816
5037
  orgUuid: contrastOrgUuid,
4817
5038
  username: contrastUsername,
4818
5039
  apiKey: contrastApiKey,
@@ -4857,22 +5078,22 @@ async function initGlobalConfig(start) {
4857
5078
  userId: userId || void 0
4858
5079
  },
4859
5080
  jira: {
4860
- baseUrl: jiraBaseUrl || void 0,
5081
+ baseUrl: normalizeBaseUrl(jiraBaseUrl) || void 0,
4861
5082
  apiToken: jiraApiToken || void 0
4862
5083
  },
4863
5084
  bitbucket: {
4864
- baseUrl: bitbucketBaseUrl || void 0,
5085
+ baseUrl: normalizeBaseUrl(bitbucketBaseUrl) || void 0,
4865
5086
  pat: bitbucketPat || void 0
4866
5087
  },
4867
5088
  ...confluenceBaseUrl || confluenceApiToken ? {
4868
5089
  confluence: {
4869
- baseUrl: confluenceBaseUrl || void 0,
5090
+ baseUrl: normalizeBaseUrl(confluenceBaseUrl) || void 0,
4870
5091
  apiToken: confluenceApiToken || void 0
4871
5092
  }
4872
5093
  } : {},
4873
5094
  ...useArtifactory ? {
4874
5095
  artifactory: {
4875
- baseUrl: artifactoryBaseUrl || void 0,
5096
+ baseUrl: normalizeBaseUrl(artifactoryBaseUrl) || void 0,
4876
5097
  token: artifactoryToken || void 0,
4877
5098
  npmRepo: npmRepo || void 0,
4878
5099
  nugetRepo: nugetRepo || void 0,
@@ -4881,7 +5102,7 @@ async function initGlobalConfig(start) {
4881
5102
  } : {},
4882
5103
  ...useSonar ? {
4883
5104
  sonar: {
4884
- baseUrl: sonarBaseUrl || void 0,
5105
+ baseUrl: normalizeBaseUrl(sonarBaseUrl) || void 0,
4885
5106
  token: sonarToken || void 0
4886
5107
  }
4887
5108
  } : {},
@@ -4892,7 +5113,7 @@ async function initGlobalConfig(start) {
4892
5113
  } : {},
4893
5114
  ...useAdo && adoBaseUrl ? {
4894
5115
  ado: {
4895
- baseUrl: adoBaseUrl,
5116
+ baseUrl: normalizeBaseUrl(adoBaseUrl),
4896
5117
  ...adoPat ? { pat: adoPat } : {},
4897
5118
  ...Object.keys(adoFieldAliases).length ? { fieldAliases: adoFieldAliases } : {},
4898
5119
  ...adoDiscoveredFields.length ? { discoveredFields: adoDiscoveredFields } : {},
@@ -4901,14 +5122,14 @@ async function initGlobalConfig(start) {
4901
5122
  } : {},
4902
5123
  ...useJenkins && jenkinsBaseUrl ? {
4903
5124
  jenkins: {
4904
- baseUrl: jenkinsBaseUrl,
5125
+ baseUrl: normalizeBaseUrl(jenkinsBaseUrl),
4905
5126
  ...jenkinsUsername ? { username: jenkinsUsername } : {},
4906
5127
  ...jenkinsApiToken ? { apiToken: jenkinsApiToken } : {}
4907
5128
  }
4908
5129
  } : {},
4909
5130
  ...useUdeploy && udeployBaseUrl ? {
4910
5131
  udeploy: {
4911
- baseUrl: udeployBaseUrl,
5132
+ baseUrl: normalizeBaseUrl(udeployBaseUrl),
4912
5133
  ...udeployPat ? { pat: udeployPat } : {},
4913
5134
  ...udeployUsername ? { username: udeployUsername } : {},
4914
5135
  ...udeployPassword ? { password: udeployPassword } : {}
@@ -4916,14 +5137,14 @@ async function initGlobalConfig(start) {
4916
5137
  } : {},
4917
5138
  ...useCheckmarx && checkmarxBaseUrl ? {
4918
5139
  checkmarx: {
4919
- baseUrl: checkmarxBaseUrl,
5140
+ baseUrl: normalizeBaseUrl(checkmarxBaseUrl),
4920
5141
  username: checkmarxUsername || void 0,
4921
5142
  password: checkmarxPassword || void 0
4922
5143
  }
4923
5144
  } : {},
4924
5145
  ...useServiceNow && servicenowBaseUrl ? {
4925
5146
  servicenow: {
4926
- baseUrl: servicenowBaseUrl,
5147
+ baseUrl: normalizeBaseUrl(servicenowBaseUrl),
4927
5148
  username: servicenowUsername || void 0,
4928
5149
  ...servicenowApiToken ? { apiToken: servicenowApiToken } : {},
4929
5150
  ...servicenowPassword ? { password: servicenowPassword } : {}
@@ -4931,7 +5152,7 @@ async function initGlobalConfig(start) {
4931
5152
  } : {},
4932
5153
  ...useContrast && contrastOrgUuid ? {
4933
5154
  contrast: {
4934
- ...contrastBaseUrl ? { baseUrl: contrastBaseUrl } : {},
5155
+ ...contrastBaseUrl ? { baseUrl: normalizeBaseUrl(contrastBaseUrl) } : {},
4935
5156
  orgUuid: contrastOrgUuid,
4936
5157
  username: contrastUsername || void 0,
4937
5158
  apiKey: contrastApiKey || void 0,
@@ -4940,7 +5161,7 @@ async function initGlobalConfig(start) {
4940
5161
  } : {},
4941
5162
  ...useSonatypeIq && sonatypeIqBaseUrl ? {
4942
5163
  sonatypeiq: {
4943
- baseUrl: sonatypeIqBaseUrl,
5164
+ baseUrl: normalizeBaseUrl(sonatypeIqBaseUrl),
4944
5165
  userCode: sonatypeIqUserCode || void 0,
4945
5166
  passcode: sonatypeIqPasscode || void 0
4946
5167
  }
@@ -5849,11 +6070,11 @@ function registerAdoRepoCommands(ado) {
5849
6070
  const start = Date.now();
5850
6071
  try {
5851
6072
  const globalOpts = ado.optsWithGlobals();
5852
- const { loadConfig: lc } = await import("./config-NCI2BTQG.js");
6073
+ const { loadConfig: lc } = await import("./config-OFLPO5DN.js");
5853
6074
  const config = lc({ configPath: globalOpts.config });
5854
6075
  const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
5855
6076
  const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
5856
- const { createHttpClient: createHttpClient2 } = await import("./http-L2P23H2D.js");
6077
+ const { createHttpClient: createHttpClient2 } = await import("./http-LNDRNPP7.js");
5857
6078
  const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
5858
6079
  const coreClient = new AdoCoreClient2(http);
5859
6080
  const me = await coreClient.getConnectionData(collection);
@@ -6961,6 +7182,19 @@ import fs3 from "fs";
6961
7182
  import path5 from "path";
6962
7183
  import os from "os";
6963
7184
  import { fileURLToPath } from "url";
7185
+ import { execFileSync as execFileSync3 } from "child_process";
7186
+ import { select } from "@inquirer/prompts";
7187
+ function injectTokenIntoUrl(url, token) {
7188
+ let parsed;
7189
+ try {
7190
+ parsed = new URL(url);
7191
+ } catch {
7192
+ throw new Error(`--token requires an HTTPS clone URL; got: ${url}`);
7193
+ }
7194
+ parsed.username = "x-token-auth";
7195
+ parsed.password = token;
7196
+ return parsed.toString();
7197
+ }
6964
7198
  var AGENT_PATHS = {
6965
7199
  "github-copilot": { project: ".agents/skills", user: path5.join(os.homedir(), ".copilot/skills") },
6966
7200
  "claude-code": { project: ".claude/skills", user: path5.join(os.homedir(), ".claude/skills") }
@@ -7020,9 +7254,12 @@ function registerSkillsCommands(program2) {
7020
7254
  continue;
7021
7255
  }
7022
7256
  try {
7023
- const content = fs3.readFileSync(path5.join(bundledDir, skillName, "SKILL.md"), "utf8");
7257
+ const mdFiles = fs3.readdirSync(path5.join(bundledDir, skillName)).filter((f) => f.endsWith(".md"));
7024
7258
  fs3.mkdirSync(skillDir, { recursive: true });
7025
- fs3.writeFileSync(path5.join(skillDir, "SKILL.md"), content, "utf8");
7259
+ for (const mdFile of mdFiles) {
7260
+ const content = fs3.readFileSync(path5.join(bundledDir, skillName, mdFile), "utf8");
7261
+ fs3.writeFileSync(path5.join(skillDir, mdFile), content, "utf8");
7262
+ }
7026
7263
  installed.push(skillName);
7027
7264
  } catch {
7028
7265
  failed.push(skillName);
@@ -7106,6 +7343,143 @@ function registerSkillsCommands(program2) {
7106
7343
  fail(err, "skills", "list", start);
7107
7344
  }
7108
7345
  });
7346
+ const marketplace = skills.command("marketplace").description("Manage a git-hosted skills marketplace");
7347
+ 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 the marketplace into").option("--branch <branch>", "Branch to clone", "master").option("--token <token>", "Bitbucket HTTP access token for authenticated clone and pull").action(async (url, localPath, opts) => {
7348
+ const start = Date.now();
7349
+ try {
7350
+ const resolvedPath = path5.resolve(localPath);
7351
+ const hasGit = fs3.existsSync(path5.join(resolvedPath, ".git"));
7352
+ if (fs3.existsSync(resolvedPath) && !hasGit && fs3.readdirSync(resolvedPath).length > 0) {
7353
+ throw new Error(`Directory already exists and is not a git repo: ${resolvedPath}`);
7354
+ }
7355
+ if (hasGit) {
7356
+ warn(`Directory already contains a git repo at ${resolvedPath} \u2014 skipping clone, updating config only.`);
7357
+ } else {
7358
+ warn(`Cloning ${url} (branch: ${opts.branch}) \u2192 ${resolvedPath}...`);
7359
+ const cloneUrl = opts.token ? injectTokenIntoUrl(url, opts.token) : url;
7360
+ try {
7361
+ execFileSync3("git", ["clone", "--branch", opts.branch, cloneUrl, resolvedPath], { stdio: ["inherit", "inherit", "pipe"] });
7362
+ } catch (e) {
7363
+ const msg = e instanceof Error ? e.message : String(e);
7364
+ throw new Error(msg.replace(/x-token-auth:[^@]+@/g, "x-token-auth:***@"));
7365
+ }
7366
+ }
7367
+ const configPath = getGlobalConfigPath();
7368
+ const existing = loadJsonFile(configPath) ?? {};
7369
+ existing.marketplace = { repoUrl: url, localPath: resolvedPath, ...opts.token ? { token: opts.token } : {} };
7370
+ writeGlobalConfig(existing, configPath);
7371
+ success({ repoUrl: url, localPath: resolvedPath, branch: opts.branch, tokenConfigured: !!opts.token }, "skills", "marketplace-setup", start);
7372
+ } catch (err) {
7373
+ fail(err, "skills", "marketplace-setup", start);
7374
+ }
7375
+ });
7376
+ marketplace.command("sync").description("Pull latest marketplace content and install a plugin's skills").argument("[plugin]", "Plugin name to install (skips interactive selection)").option("--claude", "Install to ~/.claude/skills instead of ~/.agents/skills").action(async (plugin, opts) => {
7377
+ const start = Date.now();
7378
+ try {
7379
+ const configPath = getGlobalConfigPath();
7380
+ const globalConfig = loadJsonFile(configPath) ?? {};
7381
+ const marketplacePath = globalConfig.marketplace?.localPath;
7382
+ if (!marketplacePath || !fs3.existsSync(marketplacePath)) {
7383
+ throw new Error("Marketplace not configured. Run: pncli skills marketplace setup <url> <localPath>");
7384
+ }
7385
+ warn("Pulling latest marketplace content...");
7386
+ const repoUrl = globalConfig.marketplace?.repoUrl;
7387
+ const token = globalConfig.marketplace?.token;
7388
+ const gitArgs = ["-C", marketplacePath];
7389
+ if (repoUrl && token) {
7390
+ gitArgs.push("-c", `remote.origin.url=${injectTokenIntoUrl(repoUrl, token)}`);
7391
+ }
7392
+ gitArgs.push("pull");
7393
+ try {
7394
+ execFileSync3("git", gitArgs, { stdio: ["inherit", "inherit", "pipe"] });
7395
+ } catch (e) {
7396
+ const msg = e instanceof Error ? e.message : String(e);
7397
+ throw new Error(msg.replace(/x-token-auth:[^@]+@/g, "x-token-auth:***@"));
7398
+ }
7399
+ const pluginChoices = resolvePluginChoices(marketplacePath);
7400
+ if (pluginChoices.length === 0) {
7401
+ throw new Error("No plugins found in marketplace. Check the marketplace repository structure.");
7402
+ }
7403
+ let selectedPlugin;
7404
+ if (plugin) {
7405
+ if (!pluginChoices.some((p) => p.name === plugin)) {
7406
+ throw new Error(`Plugin "${plugin}" not found. Available: ${pluginChoices.map((p) => p.name).join(", ")}`);
7407
+ }
7408
+ selectedPlugin = plugin;
7409
+ } else {
7410
+ selectedPlugin = await select({
7411
+ message: "Select a plugin to install:",
7412
+ choices: pluginChoices.map((p) => ({
7413
+ value: p.name,
7414
+ name: p.description ? `${p.name} \u2014 ${p.description}` : p.name
7415
+ }))
7416
+ });
7417
+ }
7418
+ const skillsSrc = resolveSkillsSrc(marketplacePath, selectedPlugin);
7419
+ if (!fs3.existsSync(skillsSrc)) {
7420
+ throw new Error(`No skills directory found at ${skillsSrc}`);
7421
+ }
7422
+ const targetDir = opts.claude ? path5.join(os.homedir(), ".claude", "skills") : path5.join(os.homedir(), ".agents", "skills");
7423
+ const { installed, failed } = copyPluginSkills(skillsSrc, targetDir);
7424
+ if (failed.length > 0) {
7425
+ warn(`Skipped ${failed.length} skill(s) with invalid names: ${failed.join(", ")}`);
7426
+ }
7427
+ success({
7428
+ plugin: selectedPlugin,
7429
+ installed,
7430
+ failed,
7431
+ total: installed.length,
7432
+ target: targetDir
7433
+ }, "skills", "marketplace-sync", start);
7434
+ } catch (err) {
7435
+ fail(err, "skills", "marketplace-sync", start);
7436
+ }
7437
+ });
7438
+ }
7439
+ function resolvePluginChoices(marketplacePath) {
7440
+ const marketplaceJsonPath = path5.join(marketplacePath, ".claude-plugin", "marketplace.json");
7441
+ if (fs3.existsSync(marketplaceJsonPath)) {
7442
+ try {
7443
+ const meta = JSON.parse(fs3.readFileSync(marketplaceJsonPath, "utf8"));
7444
+ if (Array.isArray(meta.plugins) && meta.plugins.length > 0) {
7445
+ return meta.plugins.map((p) => ({ name: p.name, description: p.description ?? "" }));
7446
+ }
7447
+ } catch {
7448
+ }
7449
+ }
7450
+ const pluginsDir = path5.join(marketplacePath, "plugins");
7451
+ if (fs3.existsSync(pluginsDir)) {
7452
+ return fs3.readdirSync(pluginsDir).filter((name) => fs3.statSync(path5.join(pluginsDir, name)).isDirectory()).map((name) => ({ name, description: "" }));
7453
+ }
7454
+ return [];
7455
+ }
7456
+ function resolveSkillsSrc(marketplacePath, selectedPlugin) {
7457
+ const pluginsBase = path5.resolve(marketplacePath, "plugins");
7458
+ const skillsSrc = path5.resolve(pluginsBase, selectedPlugin, "skills");
7459
+ if (!skillsSrc.startsWith(pluginsBase + path5.sep)) {
7460
+ throw new Error(`Invalid plugin name: "${selectedPlugin}"`);
7461
+ }
7462
+ return skillsSrc;
7463
+ }
7464
+ function copyPluginSkills(skillsSrc, targetDir) {
7465
+ fs3.mkdirSync(targetDir, { recursive: true });
7466
+ const resolvedTarget = path5.resolve(targetDir);
7467
+ const skillNames = fs3.readdirSync(skillsSrc).filter(
7468
+ (name) => fs3.statSync(path5.join(skillsSrc, name)).isDirectory()
7469
+ );
7470
+ const installed = [];
7471
+ const failed = [];
7472
+ for (const skillName of skillNames) {
7473
+ const dest = path5.resolve(targetDir, skillName);
7474
+ if (!dest.startsWith(resolvedTarget + path5.sep)) {
7475
+ failed.push(skillName);
7476
+ continue;
7477
+ }
7478
+ fs3.rmSync(dest, { recursive: true, force: true });
7479
+ fs3.cpSync(path5.join(skillsSrc, skillName), dest, { recursive: true });
7480
+ installed.push(skillName);
7481
+ }
7482
+ return { installed, failed };
7109
7483
  }
7110
7484
 
7111
7485
  // src/cli.ts