@kolatts/pncli 1.25.1 → 2.0.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
@@ -4,15 +4,18 @@ import {
4
4
  loadConfig,
5
5
  loadJsonFile,
6
6
  maskConfig,
7
+ normalizeBaseUrl,
7
8
  setConfigValue,
8
9
  setRepoConfigValue,
9
10
  writeGlobalConfig,
10
11
  writeRepoConfig
11
- } from "./chunk-JOAWUILY.js";
12
+ } from "./chunk-PXKUQPPF.js";
13
+ import {
14
+ createHttpClient
15
+ } from "./chunk-ZDJYOI3W.js";
12
16
  import {
13
17
  ExitCode,
14
18
  PncliError,
15
- createHttpClient,
16
19
  fail,
17
20
  log,
18
21
  setGlobalOptions,
@@ -20,7 +23,7 @@ import {
20
23
  success,
21
24
  warn,
22
25
  writeRawOutput
23
- } from "./chunk-ABHLES5M.js";
26
+ } from "./chunk-HZF6WQPU.js";
24
27
  import {
25
28
  AdoCoreClient
26
29
  } from "./chunk-VDL32PSG.js";
@@ -2368,6 +2371,232 @@ var GitHubClient = class {
2368
2371
  const data = await this.http.githubGraphQL(mutation, { threadId });
2369
2372
  return data.resolveReviewThread.thread;
2370
2373
  }
2374
+ // ── Auto-merge ─────────────────────────────────────────────────────
2375
+ /** Returns the GraphQL node ID of a PR, needed for mutations. */
2376
+ async getPRNodeId(owner, repo, pullNumber) {
2377
+ const pr = await this.getPR(owner, repo, pullNumber);
2378
+ return pr.node_id;
2379
+ }
2380
+ async enableAutoMerge(opts) {
2381
+ const nodeId = await this.getPRNodeId(opts.owner, opts.repo, opts.pullNumber);
2382
+ const mutation = `
2383
+ mutation($pullRequestId: ID!, $mergeMethod: PullRequestMergeMethod, $expectedHeadOid: GitObjectID) {
2384
+ enablePullRequestAutoMerge(input: {
2385
+ pullRequestId: $pullRequestId
2386
+ mergeMethod: $mergeMethod
2387
+ expectedHeadOid: $expectedHeadOid
2388
+ }) {
2389
+ pullRequest {
2390
+ number
2391
+ autoMergeRequest {
2392
+ mergeMethod
2393
+ enabledAt
2394
+ enabledBy { login }
2395
+ }
2396
+ }
2397
+ }
2398
+ }
2399
+ `;
2400
+ const variables = { pullRequestId: nodeId };
2401
+ if (opts.mergeMethod) variables.mergeMethod = opts.mergeMethod;
2402
+ if (opts.expectedHeadOid) variables.expectedHeadOid = opts.expectedHeadOid;
2403
+ const data = await this.http.githubGraphQL(mutation, variables);
2404
+ return data.enablePullRequestAutoMerge.pullRequest;
2405
+ }
2406
+ async disableAutoMerge(owner, repo, pullNumber) {
2407
+ const nodeId = await this.getPRNodeId(owner, repo, pullNumber);
2408
+ const mutation = `
2409
+ mutation($pullRequestId: ID!) {
2410
+ disablePullRequestAutoMerge(input: { pullRequestId: $pullRequestId }) {
2411
+ pullRequest {
2412
+ number
2413
+ autoMergeRequest {
2414
+ mergeMethod
2415
+ enabledAt
2416
+ enabledBy { login }
2417
+ }
2418
+ }
2419
+ }
2420
+ }
2421
+ `;
2422
+ const data = await this.http.githubGraphQL(mutation, { pullRequestId: nodeId });
2423
+ return data.disablePullRequestAutoMerge.pullRequest;
2424
+ }
2425
+ async enqueuePR(opts) {
2426
+ const nodeId = await this.getPRNodeId(opts.owner, opts.repo, opts.pullNumber);
2427
+ const mutation = `
2428
+ mutation($pullRequestId: ID!, $expectedHeadOid: GitObjectID, $mergeMethod: PullRequestMergeMethod) {
2429
+ addPullRequestToMergeQueue(input: {
2430
+ pullRequestId: $pullRequestId
2431
+ expectedHeadOid: $expectedHeadOid
2432
+ mergeMethod: $mergeMethod
2433
+ }) {
2434
+ mergeQueue {
2435
+ url
2436
+ }
2437
+ }
2438
+ }
2439
+ `;
2440
+ const variables = { pullRequestId: nodeId };
2441
+ if (opts.expectedHeadOid) variables.expectedHeadOid = opts.expectedHeadOid;
2442
+ if (opts.mergeMethod) variables.mergeMethod = opts.mergeMethod;
2443
+ const data = await this.http.githubGraphQL(mutation, variables);
2444
+ return { mergeQueueUrl: data.addPullRequestToMergeQueue.mergeQueue.url };
2445
+ }
2446
+ // ── PR status aggregation ──────────────────────────────────────────
2447
+ async getPRStatus(owner, repo, pullNumber) {
2448
+ const query = `
2449
+ query($owner: String!, $repo: String!, $number: Int!) {
2450
+ repository(owner: $owner, name: $repo) {
2451
+ pullRequest(number: $number) {
2452
+ number
2453
+ title
2454
+ state
2455
+ isDraft
2456
+ merged
2457
+ mergeable
2458
+ mergeStateStatus
2459
+ reviewDecision
2460
+ autoMergeRequest {
2461
+ mergeMethod
2462
+ enabledAt
2463
+ enabledBy { login }
2464
+ }
2465
+ commits(last: 1) {
2466
+ nodes {
2467
+ commit {
2468
+ statusCheckRollup {
2469
+ contexts(first: 100) {
2470
+ nodes {
2471
+ ... on CheckRun {
2472
+ __typename
2473
+ name
2474
+ status
2475
+ conclusion
2476
+ }
2477
+ ... on StatusContext {
2478
+ __typename
2479
+ context
2480
+ state
2481
+ }
2482
+ }
2483
+ }
2484
+ }
2485
+ }
2486
+ }
2487
+ }
2488
+ }
2489
+ }
2490
+ }
2491
+ `;
2492
+ const data = await this.http.githubGraphQL(query, { owner, repo, number: pullNumber });
2493
+ const pr = data.repository.pullRequest;
2494
+ const checkNodes = pr.commits.nodes[0]?.commit.statusCheckRollup?.contexts.nodes ?? [];
2495
+ const details = checkNodes.map((n) => {
2496
+ if (n.__typename === "CheckRun") {
2497
+ return { name: n.name, status: n.status, conclusion: n.conclusion };
2498
+ }
2499
+ return { name: n.context, status: n.state, conclusion: null };
2500
+ });
2501
+ const passed = details.filter((d) => d.conclusion === "success" || d.status === "success").length;
2502
+ const failed = details.filter(
2503
+ (d) => d.conclusion === "failure" || d.conclusion === "timed_out" || d.conclusion === "action_required" || d.status === "failure" || d.status === "error"
2504
+ ).length;
2505
+ const pending = details.filter(
2506
+ (d) => d.status === "queued" || d.status === "in_progress" || d.status === "pending"
2507
+ ).length;
2508
+ const other = details.length - passed - failed - pending;
2509
+ return {
2510
+ number: pr.number,
2511
+ title: pr.title,
2512
+ state: pr.state,
2513
+ isDraft: pr.isDraft,
2514
+ merged: pr.merged,
2515
+ mergeable: pr.mergeable,
2516
+ mergeStateStatus: pr.mergeStateStatus,
2517
+ reviewDecision: pr.reviewDecision,
2518
+ autoMergeRequest: pr.autoMergeRequest ? {
2519
+ mergeMethod: pr.autoMergeRequest.mergeMethod,
2520
+ enabledAt: pr.autoMergeRequest.enabledAt,
2521
+ enabledBy: pr.autoMergeRequest.enabledBy.login
2522
+ } : null,
2523
+ checks: { total: details.length, passed, failed, pending, other, details }
2524
+ };
2525
+ }
2526
+ // ── Draft / ready-for-review ───────────────────────────────────────
2527
+ async convertToDraft(owner, repo, pullNumber) {
2528
+ const nodeId = await this.getPRNodeId(owner, repo, pullNumber);
2529
+ const mutation = `
2530
+ mutation($pullRequestId: ID!) {
2531
+ convertPullRequestToDraft(input: { pullRequestId: $pullRequestId }) {
2532
+ pullRequest { number isDraft }
2533
+ }
2534
+ }
2535
+ `;
2536
+ const data = await this.http.githubGraphQL(mutation, { pullRequestId: nodeId });
2537
+ return data.convertPullRequestToDraft.pullRequest;
2538
+ }
2539
+ async markReadyForReview(owner, repo, pullNumber) {
2540
+ const nodeId = await this.getPRNodeId(owner, repo, pullNumber);
2541
+ const mutation = `
2542
+ mutation($pullRequestId: ID!) {
2543
+ markPullRequestReadyForReview(input: { pullRequestId: $pullRequestId }) {
2544
+ pullRequest { number isDraft }
2545
+ }
2546
+ }
2547
+ `;
2548
+ const data = await this.http.githubGraphQL(mutation, { pullRequestId: nodeId });
2549
+ return data.markPullRequestReadyForReview.pullRequest;
2550
+ }
2551
+ // ── Reopen ─────────────────────────────────────────────────────────
2552
+ async reopenPR(owner, repo, pullNumber) {
2553
+ return this.updatePR({ owner, repo, pullNumber, state: "open" });
2554
+ }
2555
+ // ── Labels ─────────────────────────────────────────────────────────
2556
+ async addLabels(owner, repo, issueNumber, labels) {
2557
+ return this.http.github(
2558
+ `/repos/${owner}/${repo}/issues/${issueNumber}/labels`,
2559
+ { method: "POST", body: { labels } }
2560
+ );
2561
+ }
2562
+ async removeLabel(owner, repo, issueNumber, label) {
2563
+ await this.http.github(
2564
+ `/repos/${owner}/${repo}/issues/${issueNumber}/labels/${label}`,
2565
+ { method: "DELETE" }
2566
+ );
2567
+ }
2568
+ // ── Reviewers ──────────────────────────────────────────────────────
2569
+ async addReviewers(opts) {
2570
+ const body = {};
2571
+ if (opts.reviewers?.length) body.reviewers = opts.reviewers;
2572
+ if (opts.teamReviewers?.length) body.team_reviewers = opts.teamReviewers;
2573
+ return this.http.github(
2574
+ `/repos/${opts.owner}/${opts.repo}/pulls/${opts.pullNumber}/requested_reviewers`,
2575
+ { method: "POST", body }
2576
+ );
2577
+ }
2578
+ async removeReviewers(opts) {
2579
+ const body = {};
2580
+ if (opts.reviewers?.length) body.reviewers = opts.reviewers;
2581
+ if (opts.teamReviewers?.length) body.team_reviewers = opts.teamReviewers;
2582
+ await this.http.github(
2583
+ `/repos/${opts.owner}/${opts.repo}/pulls/${opts.pullNumber}/requested_reviewers`,
2584
+ { method: "DELETE", body }
2585
+ );
2586
+ }
2587
+ // ── Assignees ─────────────────────────────────────────────────────
2588
+ async addAssignees(owner, repo, issueNumber, assignees) {
2589
+ return this.http.github(
2590
+ `/repos/${owner}/${repo}/issues/${issueNumber}/assignees`,
2591
+ { method: "POST", body: { assignees } }
2592
+ );
2593
+ }
2594
+ async removeAssignees(owner, repo, issueNumber, assignees) {
2595
+ return this.http.github(
2596
+ `/repos/${owner}/${repo}/issues/${issueNumber}/assignees`,
2597
+ { method: "DELETE", body: { assignees } }
2598
+ );
2599
+ }
2371
2600
  };
2372
2601
 
2373
2602
  // src/services/github/commands.ts
@@ -2506,7 +2735,7 @@ function registerGitHubCommands(program2) {
2506
2735
  fail(err, "github", "update-pr", start);
2507
2736
  }
2508
2737
  });
2509
- 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) => {
2738
+ gh.command("merge-pr").description("Merge a pull request (immediate merge \u2014 use enable-auto-merge for auto-merge)").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) => {
2510
2739
  const start = Date.now();
2511
2740
  try {
2512
2741
  const { client, owner, repo } = getClient3(gh);
@@ -2523,6 +2752,48 @@ function registerGitHubCommands(program2) {
2523
2752
  fail(err, "github", "merge-pr", start);
2524
2753
  }
2525
2754
  });
2755
+ gh.command("enable-auto-merge").description("Enable auto-merge on a pull request (merges automatically when all requirements are met)").requiredOption("--number <n>", "Pull request number").addOption(new Option("--method <method>", "Merge method").choices(["merge", "squash", "rebase"])).option("--match-head-commit <sha>", "Only enable if the PR head matches this SHA (prevents race conditions)").action(async (opts) => {
2756
+ const start = Date.now();
2757
+ try {
2758
+ const { client, owner, repo } = getClient3(gh);
2759
+ const data = await client.enableAutoMerge({
2760
+ owner,
2761
+ repo,
2762
+ pullNumber: parseInt(opts.number, 10),
2763
+ mergeMethod: opts.method?.toUpperCase(),
2764
+ expectedHeadOid: opts.matchHeadCommit
2765
+ });
2766
+ success(data, "github", "enable-auto-merge", start);
2767
+ } catch (err) {
2768
+ fail(err, "github", "enable-auto-merge", start);
2769
+ }
2770
+ });
2771
+ gh.command("disable-auto-merge").description("Disable auto-merge on a pull request").requiredOption("--number <n>", "Pull request number").action(async (opts) => {
2772
+ const start = Date.now();
2773
+ try {
2774
+ const { client, owner, repo } = getClient3(gh);
2775
+ const data = await client.disableAutoMerge(owner, repo, parseInt(opts.number, 10));
2776
+ success(data, "github", "disable-auto-merge", start);
2777
+ } catch (err) {
2778
+ fail(err, "github", "disable-auto-merge", start);
2779
+ }
2780
+ });
2781
+ gh.command("enqueue-pr").description("Add a pull request to the repository merge queue").requiredOption("--number <n>", "Pull request number").addOption(new Option("--method <method>", "Merge method").choices(["merge", "squash", "rebase"])).option("--match-head-commit <sha>", "Only enqueue if the PR head matches this SHA (prevents race conditions)").action(async (opts) => {
2782
+ const start = Date.now();
2783
+ try {
2784
+ const { client, owner, repo } = getClient3(gh);
2785
+ const data = await client.enqueuePR({
2786
+ owner,
2787
+ repo,
2788
+ pullNumber: parseInt(opts.number, 10),
2789
+ mergeMethod: opts.method?.toUpperCase(),
2790
+ expectedHeadOid: opts.matchHeadCommit
2791
+ });
2792
+ success(data, "github", "enqueue-pr", start);
2793
+ } catch (err) {
2794
+ fail(err, "github", "enqueue-pr", start);
2795
+ }
2796
+ });
2526
2797
  gh.command("close-pr").description("Close a pull request").requiredOption("--number <n>", "Pull request number").action(async (opts) => {
2527
2798
  const start = Date.now();
2528
2799
  try {
@@ -2724,6 +2995,148 @@ function registerGitHubCommands(program2) {
2724
2995
  fail(err, "github", "list-checks", start);
2725
2996
  }
2726
2997
  });
2998
+ gh.command("pr-status").description("Aggregated PR status: review decision, checks, mergeability, draft state, and auto-merge").requiredOption("--number <n>", "Pull request number").action(async (opts) => {
2999
+ const start = Date.now();
3000
+ try {
3001
+ const { client, owner, repo } = getClient3(gh);
3002
+ const data = await client.getPRStatus(owner, repo, parseInt(opts.number, 10));
3003
+ success(data, "github", "pr-status", start);
3004
+ } catch (err) {
3005
+ fail(err, "github", "pr-status", start);
3006
+ }
3007
+ });
3008
+ gh.command("convert-to-draft").description("Convert an open pull request to a draft").requiredOption("--number <n>", "Pull request number").action(async (opts) => {
3009
+ const start = Date.now();
3010
+ try {
3011
+ const { client, owner, repo } = getClient3(gh);
3012
+ const data = await client.convertToDraft(owner, repo, parseInt(opts.number, 10));
3013
+ success(data, "github", "convert-to-draft", start);
3014
+ } catch (err) {
3015
+ fail(err, "github", "convert-to-draft", start);
3016
+ }
3017
+ });
3018
+ gh.command("ready-for-review").description("Mark a draft pull request as ready for review").requiredOption("--number <n>", "Pull request number").action(async (opts) => {
3019
+ const start = Date.now();
3020
+ try {
3021
+ const { client, owner, repo } = getClient3(gh);
3022
+ const data = await client.markReadyForReview(owner, repo, parseInt(opts.number, 10));
3023
+ success(data, "github", "ready-for-review", start);
3024
+ } catch (err) {
3025
+ fail(err, "github", "ready-for-review", start);
3026
+ }
3027
+ });
3028
+ gh.command("reopen-pr").description("Reopen a closed pull request").requiredOption("--number <n>", "Pull request number").action(async (opts) => {
3029
+ const start = Date.now();
3030
+ try {
3031
+ const { client, owner, repo } = getClient3(gh);
3032
+ const data = await client.reopenPR(owner, repo, parseInt(opts.number, 10));
3033
+ success(data, "github", "reopen-pr", start);
3034
+ } catch (err) {
3035
+ fail(err, "github", "reopen-pr", start);
3036
+ }
3037
+ });
3038
+ gh.command("add-labels").description("Add labels to a pull request or issue").requiredOption("--number <n>", "PR or issue number").option("--label <label>", "Label to add (repeatable)", (v, acc) => {
3039
+ acc.push(v);
3040
+ return acc;
3041
+ }, []).action(async (opts) => {
3042
+ const start = Date.now();
3043
+ try {
3044
+ const { client, owner, repo } = getClient3(gh);
3045
+ if (!opts.label.length) throw new PncliError("At least one --label is required", 1);
3046
+ const data = await client.addLabels(owner, repo, parseInt(opts.number, 10), opts.label);
3047
+ success(data, "github", "add-labels", start);
3048
+ } catch (err) {
3049
+ fail(err, "github", "add-labels", start);
3050
+ }
3051
+ });
3052
+ gh.command("remove-label").description("Remove a label from a pull request or issue").requiredOption("--number <n>", "PR or issue number").requiredOption("--label <label>", "Label name to remove").action(async (opts) => {
3053
+ const start = Date.now();
3054
+ try {
3055
+ const { client, owner, repo } = getClient3(gh);
3056
+ await client.removeLabel(owner, repo, parseInt(opts.number, 10), opts.label);
3057
+ success({ removed: true, label: opts.label }, "github", "remove-label", start);
3058
+ } catch (err) {
3059
+ fail(err, "github", "remove-label", start);
3060
+ }
3061
+ });
3062
+ gh.command("add-reviewers").description("Request reviewers on a pull request").requiredOption("--number <n>", "Pull request number").option("--reviewer <login>", "User reviewer login (repeatable)", (v, acc) => {
3063
+ acc.push(v);
3064
+ return acc;
3065
+ }, []).option("--team-reviewer <slug>", "Team slug to request (repeatable)", (v, acc) => {
3066
+ acc.push(v);
3067
+ return acc;
3068
+ }, []).action(async (opts) => {
3069
+ const start = Date.now();
3070
+ try {
3071
+ const { client, owner, repo } = getClient3(gh);
3072
+ if (!opts.reviewer.length && !opts.teamReviewer.length) {
3073
+ throw new PncliError("At least one --reviewer or --team-reviewer is required", 1);
3074
+ }
3075
+ const data = await client.addReviewers({
3076
+ owner,
3077
+ repo,
3078
+ pullNumber: parseInt(opts.number, 10),
3079
+ reviewers: opts.reviewer.length ? opts.reviewer : void 0,
3080
+ teamReviewers: opts.teamReviewer.length ? opts.teamReviewer : void 0
3081
+ });
3082
+ success(data, "github", "add-reviewers", start);
3083
+ } catch (err) {
3084
+ fail(err, "github", "add-reviewers", start);
3085
+ }
3086
+ });
3087
+ gh.command("remove-reviewers").description("Remove requested reviewers from a pull request").requiredOption("--number <n>", "Pull request number").option("--reviewer <login>", "User reviewer login to remove (repeatable)", (v, acc) => {
3088
+ acc.push(v);
3089
+ return acc;
3090
+ }, []).option("--team-reviewer <slug>", "Team slug to remove (repeatable)", (v, acc) => {
3091
+ acc.push(v);
3092
+ return acc;
3093
+ }, []).action(async (opts) => {
3094
+ const start = Date.now();
3095
+ try {
3096
+ const { client, owner, repo } = getClient3(gh);
3097
+ if (!opts.reviewer.length && !opts.teamReviewer.length) {
3098
+ throw new PncliError("At least one --reviewer or --team-reviewer is required", 1);
3099
+ }
3100
+ await client.removeReviewers({
3101
+ owner,
3102
+ repo,
3103
+ pullNumber: parseInt(opts.number, 10),
3104
+ reviewers: opts.reviewer.length ? opts.reviewer : void 0,
3105
+ teamReviewers: opts.teamReviewer.length ? opts.teamReviewer : void 0
3106
+ });
3107
+ success({ removed: true }, "github", "remove-reviewers", start);
3108
+ } catch (err) {
3109
+ fail(err, "github", "remove-reviewers", start);
3110
+ }
3111
+ });
3112
+ gh.command("add-assignees").description("Add assignees to a pull request or issue").requiredOption("--number <n>", "PR or issue number").option("--assignee <login>", "Assignee login (repeatable)", (v, acc) => {
3113
+ acc.push(v);
3114
+ return acc;
3115
+ }, []).action(async (opts) => {
3116
+ const start = Date.now();
3117
+ try {
3118
+ const { client, owner, repo } = getClient3(gh);
3119
+ if (!opts.assignee.length) throw new PncliError("At least one --assignee is required", 1);
3120
+ const data = await client.addAssignees(owner, repo, parseInt(opts.number, 10), opts.assignee);
3121
+ success(data, "github", "add-assignees", start);
3122
+ } catch (err) {
3123
+ fail(err, "github", "add-assignees", start);
3124
+ }
3125
+ });
3126
+ gh.command("remove-assignees").description("Remove assignees from a pull request or issue").requiredOption("--number <n>", "PR or issue number").option("--assignee <login>", "Assignee login to remove (repeatable)", (v, acc) => {
3127
+ acc.push(v);
3128
+ return acc;
3129
+ }, []).action(async (opts) => {
3130
+ const start = Date.now();
3131
+ try {
3132
+ const { client, owner, repo } = getClient3(gh);
3133
+ if (!opts.assignee.length) throw new PncliError("At least one --assignee is required", 1);
3134
+ const data = await client.removeAssignees(owner, repo, parseInt(opts.number, 10), opts.assignee);
3135
+ success(data, "github", "remove-assignees", start);
3136
+ } catch (err) {
3137
+ fail(err, "github", "remove-assignees", start);
3138
+ }
3139
+ });
2727
3140
  }
2728
3141
 
2729
3142
  // src/services/confluence/commands.ts
@@ -5901,16 +6314,6 @@ function registerConfigCommands(program2) {
5901
6314
  } else {
5902
6315
  results.artifactory = { ok: null, message: "not configured" };
5903
6316
  }
5904
- if (cfg.udeploy.baseUrl && (cfg.udeploy.pat || cfg.udeploy.username && cfg.udeploy.password)) {
5905
- try {
5906
- await http.udeploy("/cli/application");
5907
- results.udeploy = { ok: true, message: "connected" };
5908
- } catch (err) {
5909
- results.udeploy = { ok: false, message: err instanceof Error ? err.message : String(err) };
5910
- }
5911
- } else {
5912
- results.udeploy = { ok: null, message: "not configured" };
5913
- }
5914
6317
  if (cfg.checkmarx.baseUrl && cfg.checkmarx.tenantName && (cfg.checkmarx.apiKey || cfg.checkmarx.clientId && cfg.checkmarx.clientSecret)) {
5915
6318
  try {
5916
6319
  await http.checkmarx("projects", { params: { limit: 1 } });
@@ -6003,6 +6406,26 @@ function registerConfigCommands(program2) {
6003
6406
  } else {
6004
6407
  results.logscale = { ok: null, message: "not configured" };
6005
6408
  }
6409
+ if (cfg.splitio.baseUrl && cfg.splitio.adminApiKey) {
6410
+ try {
6411
+ await http.splitio("/internal/api/v2/workspaces");
6412
+ results.splitio = { ok: true, message: "connected" };
6413
+ } catch (err) {
6414
+ results.splitio = { ok: false, message: err instanceof Error ? err.message : String(err) };
6415
+ }
6416
+ } else {
6417
+ results.splitio = { ok: null, message: "not configured" };
6418
+ }
6419
+ if (cfg.figma.baseUrl && cfg.figma.token) {
6420
+ try {
6421
+ await http.figma("/v1/me");
6422
+ results.figma = { ok: true, message: "connected" };
6423
+ } catch (err) {
6424
+ results.figma = { ok: false, message: err instanceof Error ? err.message : String(err) };
6425
+ }
6426
+ } else {
6427
+ results.figma = { ok: null, message: "not configured" };
6428
+ }
6006
6429
  success(results, "config", "test", start);
6007
6430
  } catch (err) {
6008
6431
  fail(err, "config", "test", start);
@@ -6127,18 +6550,6 @@ function registerConfigCommands(program2) {
6127
6550
  results.jenkins = categorize2(err);
6128
6551
  }
6129
6552
  }
6130
- if (!cfg.udeploy.pat && !(cfg.udeploy.username && cfg.udeploy.password)) {
6131
- results.udeploy = { status: "blank", message: "not configured" };
6132
- } else if (!cfg.udeploy.baseUrl) {
6133
- results.udeploy = { status: "error", message: "baseUrl not configured" };
6134
- } else {
6135
- try {
6136
- await http.udeploy("/cli/application", { timeoutMs: 1e4 });
6137
- results.udeploy = { status: "valid", message: "ok" };
6138
- } catch (err) {
6139
- results.udeploy = categorize2(err);
6140
- }
6141
- }
6142
6553
  const artResult = await checkArtifactoryConnectivity(cfg.artifactory);
6143
6554
  if (!artResult.configured) {
6144
6555
  results.artifactory = { status: "blank", message: artResult.error ?? "not configured" };
@@ -6259,11 +6670,35 @@ function registerConfigCommands(program2) {
6259
6670
  results.logscale = categorize2(err);
6260
6671
  }
6261
6672
  }
6673
+ if (!cfg.splitio.adminApiKey) {
6674
+ results.splitio = { status: "blank", message: "not configured" };
6675
+ } else if (!cfg.splitio.baseUrl) {
6676
+ results.splitio = { status: "error", message: "baseUrl not configured" };
6677
+ } else {
6678
+ try {
6679
+ await http.splitio("/internal/api/v2/workspaces", { timeoutMs: 1e4 });
6680
+ results.splitio = { status: "valid", message: "ok" };
6681
+ } catch (err) {
6682
+ results.splitio = categorize2(err);
6683
+ }
6684
+ }
6685
+ if (!cfg.figma.token) {
6686
+ results.figma = { status: "blank", message: "not configured" };
6687
+ } else if (!cfg.figma.baseUrl) {
6688
+ results.figma = { status: "error", message: "baseUrl not configured" };
6689
+ } else {
6690
+ try {
6691
+ await http.figma("/v1/me", { timeoutMs: 1e4 });
6692
+ results.figma = { status: "valid", message: "ok" };
6693
+ } catch (err) {
6694
+ results.figma = categorize2(err);
6695
+ }
6696
+ }
6262
6697
  const statuses = Object.values(results).map((r) => r.status);
6263
6698
  if (statuses.includes("invalid")) process.exitCode = ExitCode.AUTH_ERROR;
6264
6699
  else if (statuses.includes("error")) process.exitCode = ExitCode.NETWORK_ERROR;
6265
6700
  if (cmdOpts.output === "table") {
6266
- const services = ["jira", "bitbucket", "github", "confluence", "sonar", "sde", "ado", "jenkins", "udeploy", "artifactory", "checkmarx", "servicenow", "contrast", "sonatypeiq", "openshift", "dynatrace", "dynatrace_platform", "logscale"];
6701
+ const services = ["jira", "bitbucket", "github", "confluence", "sonar", "sde", "ado", "jenkins", "artifactory", "checkmarx", "servicenow", "contrast", "sonatypeiq", "openshift", "dynatrace", "dynatrace_platform", "logscale", "splitio", "figma"];
6267
6702
  const labelWidth = 14;
6268
6703
  const statusWidth = 9;
6269
6704
  for (const svc of services) {
@@ -6277,7 +6712,7 @@ function registerConfigCommands(program2) {
6277
6712
  process.stdout.write("\n");
6278
6713
  } else {
6279
6714
  if (opts.pretty) {
6280
- const services = ["jira", "bitbucket", "github", "confluence", "sonar", "sde", "ado", "jenkins", "udeploy", "artifactory", "checkmarx", "servicenow", "contrast", "sonatypeiq", "openshift", "dynatrace", "dynatrace_platform", "logscale"];
6715
+ const services = ["jira", "bitbucket", "github", "confluence", "sonar", "sde", "ado", "jenkins", "artifactory", "checkmarx", "servicenow", "contrast", "sonatypeiq", "openshift", "dynatrace", "dynatrace_platform", "logscale", "splitio", "figma"];
6281
6716
  const labelWidth = 14;
6282
6717
  const statusWidth = 9;
6283
6718
  for (const svc of services) {
@@ -6297,11 +6732,6 @@ function registerConfigCommands(program2) {
6297
6732
  }
6298
6733
  });
6299
6734
  }
6300
- function normalizeBaseUrl(raw) {
6301
- const trimmed = raw.trim();
6302
- if (!trimmed) return trimmed;
6303
- return /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
6304
- }
6305
6735
  async function initGlobalConfig(start) {
6306
6736
  process.stderr.write("pncli config init \u2014 Global configuration\n\n");
6307
6737
  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");
@@ -6529,7 +6959,7 @@ async function initGlobalConfig(start) {
6529
6959
  }
6530
6960
  process.stderr.write("\n\u2500\u2500 Jenkins \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
6531
6961
  const useJenkins = await confirm({
6532
- message: "Configure Jenkins Data Center for pipeline operations?",
6962
+ message: "Configure Jenkins for pipeline operations?",
6533
6963
  default: false
6534
6964
  });
6535
6965
  let jenkinsBaseUrl = "";
@@ -6548,66 +6978,6 @@ async function initGlobalConfig(start) {
6548
6978
  message: "Jenkins API token:"
6549
6979
  });
6550
6980
  }
6551
- process.stderr.write("\n\u2500\u2500 IBM UrbanCode Deploy \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
6552
- const useUdeploy = await confirm({
6553
- message: "Configure IBM UrbanCode Deploy for component imports and deployment processes?",
6554
- default: false
6555
- });
6556
- let udeployBaseUrl = "";
6557
- let udeployPat = "";
6558
- let udeployUsername = "";
6559
- let udeployPassword = "";
6560
- let udeployDefaultApp = "";
6561
- let udeployDefaultEnv = "";
6562
- if (useUdeploy) {
6563
- udeployBaseUrl = await input({
6564
- message: "UDeploy base URL (e.g. ucd.imagile.dev:8443):",
6565
- default: ""
6566
- });
6567
- udeployUsername = await input({
6568
- message: "UDeploy username (leave blank to use a PAT instead):",
6569
- default: ""
6570
- });
6571
- if (udeployUsername) {
6572
- udeployPassword = await password({
6573
- message: "UDeploy password:",
6574
- validate: (v) => v.length > 0 || "Password cannot be blank"
6575
- });
6576
- } else {
6577
- udeployPat = await password({
6578
- message: "UDeploy personal access token:"
6579
- });
6580
- }
6581
- udeployDefaultApp = await input({
6582
- message: "Default application name (optional):",
6583
- default: ""
6584
- });
6585
- udeployDefaultEnv = await input({
6586
- message: "Default environment name (optional):",
6587
- default: ""
6588
- });
6589
- const hasCredentials = udeployBaseUrl && (udeployPat || udeployUsername && udeployPassword);
6590
- if (hasCredentials) {
6591
- process.stderr.write("\n Verifying connection...\n");
6592
- try {
6593
- const tempConfig = {
6594
- ...loadConfig(),
6595
- udeploy: {
6596
- baseUrl: normalizeBaseUrl(udeployBaseUrl),
6597
- pat: udeployPat || void 0,
6598
- username: udeployUsername || void 0,
6599
- password: udeployPassword || void 0
6600
- }
6601
- };
6602
- const tempHttp = createHttpClient(tempConfig);
6603
- await tempHttp.udeploy("/cli/application");
6604
- process.stderr.write(" Connected.\n");
6605
- } catch (err) {
6606
- warn(`Could not connect to UDeploy: ${err instanceof Error ? err.message : String(err)}`);
6607
- warn("Config will be saved anyway. Check your URL and credentials and re-run pncli config init or pncli config test.");
6608
- }
6609
- }
6610
- }
6611
6981
  process.stderr.write("\n\u2500\u2500 Checkmarx \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
6612
6982
  const useCheckmarx = await confirm({
6613
6983
  message: "Configure Checkmarx One for vulnerability scanning?",
@@ -6934,16 +7304,80 @@ async function initGlobalConfig(start) {
6934
7304
  }
6935
7305
  }
6936
7306
  }
6937
- process.stderr.write("\n\u2500\u2500 Defaults \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
6938
- const jiraProject = await input({
6939
- message: "Default Jira project key (optional):",
6940
- default: ""
7307
+ process.stderr.write("\n\u2500\u2500 Split.IO \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");
7308
+ const useSplitio = await confirm({
7309
+ message: "Configure Split.IO for feature flag administration?",
7310
+ default: false
6941
7311
  });
6942
- const sonarProject = useSonar ? await input({
6943
- message: "Default SonarQube project key (optional):",
6944
- default: ""
6945
- }) : "";
6946
- const sdeProject = useSde ? await input({
7312
+ let splitioBaseUrl = "";
7313
+ let splitioAdminApiKey = "";
7314
+ if (useSplitio) {
7315
+ splitioBaseUrl = await input({
7316
+ message: "Split.IO Admin API base URL (e.g. https://api.split.io):",
7317
+ default: ""
7318
+ });
7319
+ splitioAdminApiKey = await password({
7320
+ message: "Split.IO Admin API key:"
7321
+ });
7322
+ if (splitioBaseUrl && splitioAdminApiKey) {
7323
+ process.stderr.write("\n Verifying connection...\n");
7324
+ try {
7325
+ const tempConfig = {
7326
+ ...loadConfig(),
7327
+ splitio: {
7328
+ baseUrl: normalizeBaseUrl(splitioBaseUrl),
7329
+ adminApiKey: splitioAdminApiKey
7330
+ }
7331
+ };
7332
+ const tempHttp = createHttpClient(tempConfig);
7333
+ await tempHttp.splitio("/internal/api/v2/workspaces");
7334
+ process.stderr.write(" Connected.\n");
7335
+ } catch (err) {
7336
+ warn(`Could not connect to Split.IO: ${err instanceof Error ? err.message : String(err)}`);
7337
+ warn("Config will be saved anyway. Check your URL and API key and re-run pncli config init or pncli config test.");
7338
+ }
7339
+ }
7340
+ }
7341
+ process.stderr.write("\n\u2500\u2500 Figma \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");
7342
+ const useFigma = await confirm({
7343
+ message: "Configure Figma for reading design files and comments?",
7344
+ default: false
7345
+ });
7346
+ let figmaToken = "";
7347
+ if (useFigma) {
7348
+ process.stderr.write(" Generate a personal access token in Figma under Account Settings \u2192 Personal access tokens.\n");
7349
+ figmaToken = await password({
7350
+ message: "Figma personal access token:"
7351
+ });
7352
+ if (figmaToken) {
7353
+ process.stderr.write("\n Verifying connection...\n");
7354
+ try {
7355
+ const tempConfig = {
7356
+ ...loadConfig(),
7357
+ figma: {
7358
+ baseUrl: "https://api.figma.com",
7359
+ token: figmaToken
7360
+ }
7361
+ };
7362
+ const tempHttp = createHttpClient(tempConfig);
7363
+ await tempHttp.figma("/v1/me");
7364
+ process.stderr.write(" Connected.\n");
7365
+ } catch (err) {
7366
+ warn(`Could not connect to Figma: ${err instanceof Error ? err.message : String(err)}`);
7367
+ warn("Config will be saved anyway. Check your token and re-run pncli config init or pncli config test.");
7368
+ }
7369
+ }
7370
+ }
7371
+ process.stderr.write("\n\u2500\u2500 Defaults \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
7372
+ const jiraProject = await input({
7373
+ message: "Default Jira project key (optional):",
7374
+ default: ""
7375
+ });
7376
+ const sonarProject = useSonar ? await input({
7377
+ message: "Default SonarQube project key (optional):",
7378
+ default: ""
7379
+ }) : "";
7380
+ const sdeProject = useSde ? await input({
6947
7381
  message: "Default SDElements project ID (optional, numeric):",
6948
7382
  default: ""
6949
7383
  }) : "";
@@ -6957,7 +7391,10 @@ async function initGlobalConfig(start) {
6957
7391
  process.exitCode = ExitCode.SUCCESS;
6958
7392
  return;
6959
7393
  }
7394
+ const existingGlobal = loadJsonFile(getGlobalConfigPath());
7395
+ const existingInstances = Array.isArray(existingGlobal?.jenkinsInstances) ? existingGlobal.jenkinsInstances : [];
6960
7396
  writeGlobalConfig({
7397
+ jenkinsInstances: existingInstances,
6961
7398
  user: {
6962
7399
  email: userEmail || void 0,
6963
7400
  userId: userId || void 0
@@ -7018,14 +7455,6 @@ async function initGlobalConfig(start) {
7018
7455
  ...jenkinsApiToken ? { apiToken: jenkinsApiToken } : {}
7019
7456
  }
7020
7457
  } : {},
7021
- ...useUdeploy && udeployBaseUrl ? {
7022
- udeploy: {
7023
- baseUrl: normalizeBaseUrl(udeployBaseUrl),
7024
- ...udeployPat ? { pat: udeployPat } : {},
7025
- ...udeployUsername ? { username: udeployUsername } : {},
7026
- ...udeployPassword ? { password: udeployPassword } : {}
7027
- }
7028
- } : {},
7029
7458
  ...useCheckmarx && checkmarxBaseUrl ? {
7030
7459
  checkmarx: {
7031
7460
  baseUrl: normalizeBaseUrl(checkmarxBaseUrl),
@@ -7079,6 +7508,18 @@ async function initGlobalConfig(start) {
7079
7508
  token: logscaleToken || void 0
7080
7509
  }
7081
7510
  } : {},
7511
+ ...useSplitio && splitioBaseUrl ? {
7512
+ splitio: {
7513
+ baseUrl: normalizeBaseUrl(splitioBaseUrl),
7514
+ adminApiKey: splitioAdminApiKey || void 0
7515
+ }
7516
+ } : {},
7517
+ ...useFigma && figmaToken ? {
7518
+ figma: {
7519
+ baseUrl: "https://api.figma.com",
7520
+ token: figmaToken
7521
+ }
7522
+ } : {},
7082
7523
  defaults: {
7083
7524
  jira: {
7084
7525
  project: jiraProject || void 0
@@ -7098,12 +7539,6 @@ async function initGlobalConfig(start) {
7098
7539
  collection: adoCollection || void 0,
7099
7540
  project: adoProject || void 0
7100
7541
  }
7101
- } : {},
7102
- ...useUdeploy && (udeployDefaultApp || udeployDefaultEnv) ? {
7103
- udeploy: {
7104
- application: udeployDefaultApp || void 0,
7105
- environment: udeployDefaultEnv || void 0
7106
- }
7107
7542
  } : {}
7108
7543
  }
7109
7544
  });
@@ -7835,10 +8270,10 @@ function registerAdoWorkCommands(ado) {
7835
8270
  const types = await discoverTypes(workClient, collection, project);
7836
8271
  if (opts.save) {
7837
8272
  const globalConfigPath = getGlobalConfigPath(globalOpts.config);
7838
- const fs5 = await import("fs");
7839
- const existing = JSON.parse(fs5.readFileSync(globalConfigPath, "utf8") || "{}");
8273
+ const fs6 = await import("fs");
8274
+ const existing = JSON.parse(fs6.readFileSync(globalConfigPath, "utf8") || "{}");
7840
8275
  existing.ado = { ...existing.ado ?? {}, discoveredTypes: types };
7841
- fs5.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
8276
+ fs6.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
7842
8277
  warn(`Saved ${types.length} types to ${globalConfigPath}`);
7843
8278
  }
7844
8279
  success(types, "ado", "work-types", start);
@@ -7867,11 +8302,11 @@ function registerAdoWorkCommands(ado) {
7867
8302
  ) : allFields;
7868
8303
  if (opts.save) {
7869
8304
  const globalConfigPath = getGlobalConfigPath(globalOpts.config);
7870
- const fs5 = await import("fs");
7871
- const existing = JSON.parse(fs5.readFileSync(globalConfigPath, "utf8") || "{}");
8305
+ const fs6 = await import("fs");
8306
+ const existing = JSON.parse(fs6.readFileSync(globalConfigPath, "utf8") || "{}");
7872
8307
  const aliases = buildDefaultAliases(allFields);
7873
8308
  existing.ado = { ...existing.ado ?? {}, discoveredFields: allFields, fieldAliases: aliases };
7874
- fs5.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
8309
+ fs6.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
7875
8310
  warn(`Saved ${allFields.length} fields and ${Object.keys(aliases).length} aliases to ${globalConfigPath}`);
7876
8311
  }
7877
8312
  success(fields, "ado", "work-fields", start);
@@ -8168,11 +8603,11 @@ function registerAdoRepoCommands(ado) {
8168
8603
  const start = Date.now();
8169
8604
  try {
8170
8605
  const globalOpts = ado.optsWithGlobals();
8171
- const { loadConfig: lc } = await import("./config-SQ3YHJHI.js");
8606
+ const { loadConfig: lc } = await import("./config-YHYQTGKT.js");
8172
8607
  const config = lc({ configPath: globalOpts.config });
8173
8608
  const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
8174
8609
  const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
8175
- const { createHttpClient: createHttpClient2 } = await import("./http-WCK6AZW6.js");
8610
+ const { createHttpClient: createHttpClient2 } = await import("./http-6QJU2SWP.js");
8176
8611
  const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
8177
8612
  const coreClient = new AdoCoreClient2(http);
8178
8613
  const me = await coreClient.getConnectionData(collection);
@@ -8430,11 +8865,22 @@ var JenkinsClient = class {
8430
8865
  };
8431
8866
 
8432
8867
  // src/services/jenkins/commands.ts
8433
- function getClient7(program2) {
8434
- const opts = program2.optsWithGlobals();
8868
+ function getClient7(cmd) {
8869
+ const opts = cmd.optsWithGlobals();
8435
8870
  const config = loadConfig({ configPath: opts.config });
8436
- if (!config.jenkins.baseUrl) throw new PncliError("Jenkins not configured. Run: pncli config init");
8437
- const http = createHttpClient(config, Boolean(opts.dryRun));
8871
+ let jenkins = config.jenkins;
8872
+ if (opts.instance) {
8873
+ const inst = config.jenkinsInstances.find((i) => i.name === opts.instance);
8874
+ if (!inst) throw new PncliError(`Jenkins instance "${opts.instance}" not found. Add it to your global config using pncli config set jenkinsInstances.`);
8875
+ jenkins = { baseUrl: inst.baseUrl, username: inst.username, apiToken: inst.apiToken };
8876
+ }
8877
+ if (!jenkins.baseUrl) {
8878
+ if (opts.instance) {
8879
+ throw new PncliError(`Jenkins instance "${opts.instance}" has no baseUrl configured. Add a baseUrl to that entry in jenkinsInstances.`);
8880
+ }
8881
+ throw new PncliError("Jenkins not configured. Run: pncli config init");
8882
+ }
8883
+ const http = createHttpClient({ ...config, jenkins }, Boolean(opts.dryRun));
8438
8884
  return new JenkinsClient(http);
8439
8885
  }
8440
8886
  async function pollQueueItem(client, queueItemId, pollMs, timeoutMs) {
@@ -8459,12 +8905,70 @@ async function pollBuildComplete(client, jobName, buildNumber, pollMs, timeoutMs
8459
8905
  throw new PncliError(`Timed out waiting for build #${buildNumber} to complete after ${timeoutMs / 1e3}s`, 1);
8460
8906
  }
8461
8907
  function registerJenkinsCommands(program2) {
8462
- const jenkins = program2.command("jenkins").description("Jenkins Data Center operations");
8908
+ const jenkins = program2.command("jenkins").description("Jenkins operations").option("--instance <name>", "Named Jenkins instance from global config (overrides default jenkins config)");
8463
8909
  const pipeline = jenkins.command("pipeline").description("Pipeline (job/build) operations");
8910
+ const instance = jenkins.command("instance").description("Manage named Jenkins instances in the global config");
8911
+ instance.command("list").description("List configured Jenkins instances (API tokens masked)").action(() => {
8912
+ const start = Date.now();
8913
+ try {
8914
+ const opts = jenkins.optsWithGlobals();
8915
+ const config = loadConfig({ configPath: opts.config });
8916
+ const instances = config.jenkinsInstances.map((i) => ({
8917
+ ...i,
8918
+ apiToken: i.apiToken ? "***" : void 0
8919
+ }));
8920
+ success({ instances }, "jenkins", "instance-list", start);
8921
+ } catch (err) {
8922
+ fail(err, "jenkins", "instance-list", start);
8923
+ }
8924
+ });
8925
+ instance.command("add").description("Add a named Jenkins instance to the global config (appends \u2014 existing instances are preserved)").requiredOption("--name <name>", "Instance name used with --instance").requiredOption("--base-url <url>", "Jenkins base URL (e.g. jenkins.imagile.dev)").option("--username <user>", "Jenkins username").option("--api-token <token>", "Jenkins API token (prompted for when omitted on an interactive terminal, keeping it out of shell history)").option("--force", "Overwrite an instance that already uses this name").action(async (o) => {
8926
+ const start = Date.now();
8927
+ try {
8928
+ const opts = jenkins.optsWithGlobals();
8929
+ const configPath = opts.config;
8930
+ const existing = loadConfig({ configPath }).jenkinsInstances;
8931
+ const idx = existing.findIndex((i) => i.name === o.name);
8932
+ if (idx >= 0 && !o.force) {
8933
+ throw new PncliError(`Jenkins instance "${o.name}" already exists. Pass --force to overwrite it, or run: pncli jenkins instance remove --name ${o.name}`, 1);
8934
+ }
8935
+ let apiToken = o.apiToken;
8936
+ if (!apiToken && process.stdin.isTTY) {
8937
+ const { default: password2 } = await import("@inquirer/password");
8938
+ apiToken = await password2({ message: `API token for "${o.name}" (leave blank to skip):` });
8939
+ }
8940
+ const entry = {
8941
+ name: o.name,
8942
+ baseUrl: normalizeBaseUrl(o.baseUrl),
8943
+ ...o.username ? { username: o.username } : {},
8944
+ ...apiToken ? { apiToken } : {}
8945
+ };
8946
+ const next = idx >= 0 ? existing.map((i, n) => n === idx ? entry : i) : [...existing, entry];
8947
+ setConfigValue("jenkinsInstances", JSON.stringify(next), configPath);
8948
+ success({ name: entry.name, baseUrl: entry.baseUrl, overwritten: idx >= 0 }, "jenkins", "instance-add", start);
8949
+ } catch (err) {
8950
+ fail(err, "jenkins", "instance-add", start);
8951
+ }
8952
+ });
8953
+ instance.command("remove").description("Remove a named Jenkins instance from the global config").requiredOption("--name <name>", "Instance name to remove").action((o) => {
8954
+ const start = Date.now();
8955
+ try {
8956
+ const opts = jenkins.optsWithGlobals();
8957
+ const configPath = opts.config;
8958
+ const existing = loadConfig({ configPath }).jenkinsInstances;
8959
+ if (!existing.some((i) => i.name === o.name)) {
8960
+ throw new PncliError(`Jenkins instance "${o.name}" not found. Run: pncli jenkins instance list`, 1);
8961
+ }
8962
+ setConfigValue("jenkinsInstances", JSON.stringify(existing.filter((i) => i.name !== o.name)), configPath);
8963
+ success({ removed: o.name }, "jenkins", "instance-remove", start);
8964
+ } catch (err) {
8965
+ fail(err, "jenkins", "instance-remove", start);
8966
+ }
8967
+ });
8464
8968
  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) => {
8465
8969
  const start = Date.now();
8466
8970
  try {
8467
- const client = getClient7(program2);
8971
+ const client = getClient7(jenkins);
8468
8972
  const data = await client.listJobs(opts.folder);
8469
8973
  success(data, "jenkins", "pipeline-list", start);
8470
8974
  } catch (err) {
@@ -8474,7 +8978,7 @@ function registerJenkinsCommands(program2) {
8474
8978
  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) => {
8475
8979
  const start = Date.now();
8476
8980
  try {
8477
- const client = getClient7(program2);
8981
+ const client = getClient7(jenkins);
8478
8982
  const data = await client.getJob(opts.name);
8479
8983
  success(data, "jenkins", "pipeline-get", start);
8480
8984
  } catch (err) {
@@ -8487,7 +8991,7 @@ function registerJenkinsCommands(program2) {
8487
8991
  }, []).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) => {
8488
8992
  const start = Date.now();
8489
8993
  try {
8490
- const client = getClient7(program2);
8994
+ const client = getClient7(jenkins);
8491
8995
  for (const p of opts.parameter) {
8492
8996
  if (!p.includes("=")) throw new PncliError(`Invalid --parameter "${p}". Expected format: key=value`, 1);
8493
8997
  }
@@ -8520,7 +9024,7 @@ function registerJenkinsCommands(program2) {
8520
9024
  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) => {
8521
9025
  const start = Date.now();
8522
9026
  try {
8523
- const client = getClient7(program2);
9027
+ const client = getClient7(jenkins);
8524
9028
  const top = parseInt(opts.top, 10);
8525
9029
  if (isNaN(top) || top <= 0) throw new PncliError(`Invalid --top "${opts.top}". Expected a positive integer.`, 1);
8526
9030
  const data = await client.listBuilds(opts.name, top);
@@ -8532,7 +9036,7 @@ function registerJenkinsCommands(program2) {
8532
9036
  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) => {
8533
9037
  const start = Date.now();
8534
9038
  try {
8535
- const client = getClient7(program2);
9039
+ const client = getClient7(jenkins);
8536
9040
  const buildNumber = parseInt(opts.number, 10);
8537
9041
  if (isNaN(buildNumber)) throw new PncliError(`Invalid --number "${opts.number}". Expected an integer.`, 1);
8538
9042
  const data = await client.getBuild(opts.name, buildNumber);
@@ -8544,7 +9048,7 @@ function registerJenkinsCommands(program2) {
8544
9048
  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) => {
8545
9049
  const start = Date.now();
8546
9050
  try {
8547
- const client = getClient7(program2);
9051
+ const client = getClient7(jenkins);
8548
9052
  const buildNumber = parseInt(opts.number, 10);
8549
9053
  if (isNaN(buildNumber)) throw new PncliError(`Invalid --number "${opts.number}". Expected an integer.`, 1);
8550
9054
  const text = await client.getConsoleLog(opts.name, buildNumber);
@@ -8621,7 +9125,7 @@ var ArtifactoryClient = class {
8621
9125
  }));
8622
9126
  const result = { builds, uri: raw.uri };
8623
9127
  if (builds.length === 0) {
8624
- result.hint = 'No builds are registered in Artifactory Build Integration. Build names may differ from ADO pipeline or uDeploy component names. Use "artifactory repos" to list repositories, then "artifactory search --repo <repo>" to locate artifacts directly.';
9128
+ result.hint = 'No builds are registered in Artifactory Build Integration. Build names may differ from ADO pipeline names. Use "artifactory repos" to list repositories, then "artifactory search --repo <repo>" to locate artifacts directly.';
8625
9129
  }
8626
9130
  return result;
8627
9131
  }
@@ -8724,7 +9228,7 @@ function registerArtifactoryCommands(program2) {
8724
9228
  fail(err, "artifactory", "search", start);
8725
9229
  }
8726
9230
  });
8727
- 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) => {
9231
+ art.command("builds").description('List builds registered in Artifactory Build Integration (names may differ from ADO pipeline names; use "search --repo" when empty)').option("--timeout <s>", "Request timeout in seconds (default 60; increase for large Artifactory instances)", "60").action(async (opts) => {
8728
9232
  const start = Date.now();
8729
9233
  try {
8730
9234
  const client = getClient8(program2);
@@ -8759,226 +9263,6 @@ function registerArtifactoryCommands(program2) {
8759
9263
  });
8760
9264
  }
8761
9265
 
8762
- // src/services/udeploy/client.ts
8763
- var UdeployClient = class {
8764
- constructor(http) {
8765
- this.http = http;
8766
- }
8767
- http;
8768
- listApplications() {
8769
- return this.http.udeploy("/cli/application");
8770
- }
8771
- listEnvironments(application) {
8772
- return this.http.udeploy("/cli/application/environmentsInApplication", {
8773
- params: { application }
8774
- });
8775
- }
8776
- listComponents() {
8777
- return this.http.udeploy("/cli/component");
8778
- }
8779
- listVersions(component) {
8780
- return this.http.udeploy("/cli/component/versions", {
8781
- params: { component }
8782
- });
8783
- }
8784
- createVersion(component, name) {
8785
- return this.http.udeploy("/cli/version/createVersion", {
8786
- method: "POST",
8787
- params: { component, name }
8788
- });
8789
- }
8790
- finishImporting(component, version) {
8791
- return this.http.udeploy("/cli/version/finishedImporting", {
8792
- method: "POST",
8793
- params: { component, version }
8794
- });
8795
- }
8796
- runProcess(opts) {
8797
- return this.http.udeploy("/cli/applicationProcessRequest/request", {
8798
- method: "PUT",
8799
- body: {
8800
- application: opts.application,
8801
- applicationProcess: opts.applicationProcess,
8802
- environment: opts.environment,
8803
- onlyChanged: opts.onlyChanged ?? false,
8804
- ...opts.versions ? { versions: opts.versions } : {},
8805
- ...opts.snapshot ? { snapshot: opts.snapshot } : {},
8806
- ...opts.properties ? { properties: opts.properties } : {}
8807
- }
8808
- });
8809
- }
8810
- getRequestStatus(requestId) {
8811
- return this.http.udeploy("/cli/applicationProcessRequest/requestStatus", {
8812
- params: { requestId }
8813
- });
8814
- }
8815
- getRequestInfo(requestId) {
8816
- return this.http.udeploy("/cli/applicationProcessRequest/requestInfo", {
8817
- params: { requestId }
8818
- });
8819
- }
8820
- };
8821
-
8822
- // src/services/udeploy/commands.ts
8823
- var POLL_INTERVAL_MS2 = 5e3;
8824
- var DEFAULT_TIMEOUT_MS = 10 * 60 * 1e3;
8825
- function getClient9(program2) {
8826
- const opts = program2.optsWithGlobals();
8827
- const config = loadConfig({ configPath: opts.config });
8828
- if (!config.udeploy.baseUrl) {
8829
- throw new PncliError("UDeploy baseUrl not configured. Run: pncli config init", 78);
8830
- }
8831
- const http = createHttpClient(config, opts.dryRun);
8832
- return { client: new UdeployClient(http), config };
8833
- }
8834
- function resolveApplication(config, cliValue) {
8835
- const app = cliValue ?? config.defaults.udeploy.application;
8836
- if (!app) throw new PncliError("--application is required (or set defaults.udeploy.application in config)", 2);
8837
- return app;
8838
- }
8839
- function resolveEnvironment(config, cliValue) {
8840
- const env = cliValue ?? config.defaults.udeploy.environment;
8841
- if (!env) throw new PncliError("--environment is required (or set defaults.udeploy.environment in config)", 2);
8842
- return env;
8843
- }
8844
- function registerUdeployCommands(program2) {
8845
- const udeploy = program2.command("udeploy").description("IBM UrbanCode Deploy \u2014 component versions and deployment processes").option("--application <name>", "Application name or ID").option("--environment <name>", "Environment name or ID");
8846
- udeploy.command("apps").description("List all applications").action(async () => {
8847
- const start = Date.now();
8848
- try {
8849
- const { client } = getClient9(program2);
8850
- const apps = await client.listApplications();
8851
- success(apps, "udeploy", "apps", start);
8852
- } catch (err) {
8853
- fail(err, "udeploy", "apps", start);
8854
- }
8855
- });
8856
- udeploy.command("environments").description("List environments for an application").action(async () => {
8857
- const start = Date.now();
8858
- try {
8859
- const { client, config } = getClient9(program2);
8860
- const parentOpts = udeploy.opts();
8861
- const application = resolveApplication(config, parentOpts.application);
8862
- const envs = await client.listEnvironments(application);
8863
- success(envs, "udeploy", "environments", start);
8864
- } catch (err) {
8865
- fail(err, "udeploy", "environments", start);
8866
- }
8867
- });
8868
- udeploy.command("components").description("List all components").action(async () => {
8869
- const start = Date.now();
8870
- try {
8871
- const { client } = getClient9(program2);
8872
- const components = await client.listComponents();
8873
- success(components, "udeploy", "components", start);
8874
- } catch (err) {
8875
- fail(err, "udeploy", "components", start);
8876
- }
8877
- });
8878
- udeploy.command("versions").description("List versions for a component").requiredOption("--component <name>", "Component name or ID").action(async (cmdOpts) => {
8879
- const start = Date.now();
8880
- try {
8881
- const { client } = getClient9(program2);
8882
- const versions = await client.listVersions(cmdOpts.component);
8883
- success(versions, "udeploy", "versions", start);
8884
- } catch (err) {
8885
- fail(err, "udeploy", "versions", start);
8886
- }
8887
- });
8888
- 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) => {
8889
- const start = Date.now();
8890
- try {
8891
- const { client } = getClient9(program2);
8892
- const created = await client.createVersion(cmdOpts.component, cmdOpts.name);
8893
- if (cmdOpts.finish) {
8894
- await client.finishImporting(cmdOpts.component, cmdOpts.name);
8895
- }
8896
- success({ ...created, finishedImporting: cmdOpts.finish }, "udeploy", "import-version", start);
8897
- } catch (err) {
8898
- fail(err, "udeploy", "import-version", start);
8899
- }
8900
- });
8901
- 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) => {
8902
- const start = Date.now();
8903
- try {
8904
- const { client, config } = getClient9(program2);
8905
- const parentOpts = udeploy.opts();
8906
- const application = resolveApplication(config, parentOpts.application);
8907
- const environment = resolveEnvironment(config, parentOpts.environment);
8908
- const components = cmdOpts.component;
8909
- const versions = cmdOpts.componentVersion;
8910
- const versionEntries = components.map((c, i) => ({
8911
- component: c,
8912
- version: versions[i] ?? "latest"
8913
- }));
8914
- const result = await client.runProcess({
8915
- application,
8916
- applicationProcess: cmdOpts.process,
8917
- environment,
8918
- onlyChanged: cmdOpts.onlyChanged,
8919
- ...versionEntries.length > 0 ? { versions: versionEntries } : {},
8920
- ...cmdOpts.snapshot ? { snapshot: cmdOpts.snapshot } : {}
8921
- });
8922
- if (!cmdOpts.wait) {
8923
- success(result, "udeploy", "run", start);
8924
- return;
8925
- }
8926
- const timeoutMs = parseInt(cmdOpts.timeout, 10);
8927
- const requestId = result.requestId;
8928
- log(`Process started. Request ID: ${requestId}. Polling for completion...`);
8929
- while (true) {
8930
- const elapsed = Date.now() - start;
8931
- if (elapsed >= timeoutMs) {
8932
- throw new PncliError(`Timed out after ${timeoutMs}ms waiting for request ${requestId}`, 1);
8933
- }
8934
- await sleep(POLL_INTERVAL_MS2);
8935
- const status = await client.getRequestStatus(requestId);
8936
- if (status.status === "CLOSED") {
8937
- const outcome = { requestId, status: status.status, result: status.result, elapsed_ms: Date.now() - start };
8938
- if (status.result === "SUCCEEDED") {
8939
- success(outcome, "udeploy", "run", start);
8940
- } else {
8941
- throw new PncliError(
8942
- `Deployment ${status.result ?? "failed"} (requestId: ${requestId})`,
8943
- 1
8944
- );
8945
- }
8946
- return;
8947
- }
8948
- log(`Status: ${status.status} (${Math.round((Date.now() - start) / 1e3)}s elapsed)`);
8949
- }
8950
- } catch (err) {
8951
- fail(err, "udeploy", "run", start);
8952
- }
8953
- });
8954
- 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) => {
8955
- const start = Date.now();
8956
- try {
8957
- const { client } = getClient9(program2);
8958
- const status = await client.getRequestStatus(cmdOpts.requestId);
8959
- success(status, "udeploy", "request-status", start);
8960
- } catch (err) {
8961
- fail(err, "udeploy", "request-status", start);
8962
- }
8963
- });
8964
- 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) => {
8965
- const start = Date.now();
8966
- try {
8967
- const { client } = getClient9(program2);
8968
- const info = await client.getRequestInfo(cmdOpts.requestId);
8969
- success(info, "udeploy", "request-info", start);
8970
- } catch (err) {
8971
- fail(err, "udeploy", "request-info", start);
8972
- }
8973
- });
8974
- }
8975
- function collect(val, prev) {
8976
- return [...prev, val];
8977
- }
8978
- function sleep(ms) {
8979
- return new Promise((resolve) => setTimeout(resolve, ms));
8980
- }
8981
-
8982
9266
  // src/services/checkmarx/client.ts
8983
9267
  var CheckmarxClient = class {
8984
9268
  constructor(http) {
@@ -9019,7 +9303,7 @@ var CheckmarxClient = class {
9019
9303
  };
9020
9304
 
9021
9305
  // src/services/checkmarx/commands.ts
9022
- function getClient10(program2) {
9306
+ function getClient9(program2) {
9023
9307
  const opts = program2.optsWithGlobals();
9024
9308
  const config = loadConfig({ configPath: opts.config });
9025
9309
  if (!config.checkmarx.baseUrl) throw new PncliError("Checkmarx not configured. Run: pncli config init");
@@ -9033,7 +9317,7 @@ function registerCheckmarxCommands(program2) {
9033
9317
  project.command("list").description("List all projects").action(async () => {
9034
9318
  const start = Date.now();
9035
9319
  try {
9036
- const data = await getClient10(program2).listProjects();
9320
+ const data = await getClient9(program2).listProjects();
9037
9321
  success(data, "checkmarx", "project-list", start);
9038
9322
  } catch (err) {
9039
9323
  fail(err, "checkmarx", "project-list", start);
@@ -9042,7 +9326,7 @@ function registerCheckmarxCommands(program2) {
9042
9326
  project.command("get").description("Get a project by ID").requiredOption("--id <id>", "Project UUID").action(async (opts) => {
9043
9327
  const start = Date.now();
9044
9328
  try {
9045
- const data = await getClient10(program2).getProject(opts.id);
9329
+ const data = await getClient9(program2).getProject(opts.id);
9046
9330
  success(data, "checkmarx", "project-get", start);
9047
9331
  } catch (err) {
9048
9332
  fail(err, "checkmarx", "project-get", start);
@@ -9053,7 +9337,7 @@ function registerCheckmarxCommands(program2) {
9053
9337
  try {
9054
9338
  const last = opts.last ? parseInt(opts.last, 10) : void 0;
9055
9339
  if (opts.last && isNaN(last)) throw new PncliError(`Invalid --last "${opts.last}". Expected an integer.`, 1);
9056
- const data = await getClient10(program2).listScans({ projectId: opts.project, last });
9340
+ const data = await getClient9(program2).listScans({ projectId: opts.project, last });
9057
9341
  success(data, "checkmarx", "scan-list", start);
9058
9342
  } catch (err) {
9059
9343
  fail(err, "checkmarx", "scan-list", start);
@@ -9062,7 +9346,7 @@ function registerCheckmarxCommands(program2) {
9062
9346
  scan.command("get").description("Get a scan by ID").requiredOption("--id <id>", "Scan UUID").action(async (opts) => {
9063
9347
  const start = Date.now();
9064
9348
  try {
9065
- const data = await getClient10(program2).getScan(opts.id);
9349
+ const data = await getClient9(program2).getScan(opts.id);
9066
9350
  success(data, "checkmarx", "scan-get", start);
9067
9351
  } catch (err) {
9068
9352
  fail(err, "checkmarx", "scan-get", start);
@@ -9071,7 +9355,7 @@ function registerCheckmarxCommands(program2) {
9071
9355
  scan.command("stats").description("Get results statistics for a scan").requiredOption("--id <id>", "Scan UUID").action(async (opts) => {
9072
9356
  const start = Date.now();
9073
9357
  try {
9074
- const data = await getClient10(program2).getScanResultsStatistics(opts.id);
9358
+ const data = await getClient9(program2).getScanResultsStatistics(opts.id);
9075
9359
  success(data, "checkmarx", "scan-stats", start);
9076
9360
  } catch (err) {
9077
9361
  fail(err, "checkmarx", "scan-stats", start);
@@ -10524,9 +10808,32 @@ function podAge(creationTimestamp) {
10524
10808
  if (ageHours < 24) return `${ageHours}h`;
10525
10809
  return `${Math.floor(ageHours / 24)}d`;
10526
10810
  }
10811
+ function parseCpuMillicores(cpu) {
10812
+ if (!cpu) return null;
10813
+ if (cpu.endsWith("n")) return Math.round(Number(cpu.slice(0, -1)) / 1e6);
10814
+ if (cpu.endsWith("u")) return Math.round(Number(cpu.slice(0, -1)) / 1e3);
10815
+ if (cpu.endsWith("m")) return Number(cpu.slice(0, -1));
10816
+ const whole = Number(cpu);
10817
+ if (!Number.isNaN(whole)) return Math.round(whole * 1e3);
10818
+ return null;
10819
+ }
10820
+ function parseMemMiB(mem) {
10821
+ if (!mem) return null;
10822
+ const round2 = (n) => Math.round(n * 100) / 100;
10823
+ if (mem.endsWith("Ki")) return round2(Number(mem.slice(0, -2)) / 1024);
10824
+ if (mem.endsWith("Mi")) return Number(mem.slice(0, -2));
10825
+ if (mem.endsWith("Gi")) return Math.round(Number(mem.slice(0, -2)) * 1024);
10826
+ if (mem.endsWith("Ti")) return Math.round(Number(mem.slice(0, -2)) * 1024 * 1024);
10827
+ if (mem.endsWith("K")) return round2(Number(mem.slice(0, -1)) * 1e3 / (1024 * 1024));
10828
+ if (mem.endsWith("M")) return round2(Number(mem.slice(0, -1)) * 1e3 * 1e3 / (1024 * 1024));
10829
+ if (mem.endsWith("G")) return Math.round(Number(mem.slice(0, -1)) * 1e3 * 1e3 * 1e3 / (1024 * 1024));
10830
+ const bytes = Number(mem);
10831
+ if (!Number.isNaN(bytes)) return round2(bytes / (1024 * 1024));
10832
+ return null;
10833
+ }
10527
10834
  function registerOpenShiftCommands(program2) {
10528
10835
  const oc = program2.command("openshift").description("OpenShift / Kubernetes operations");
10529
- oc.command("pods").description("List pod health summary for a namespace \u2014 pre-processed for LLM analysis").requiredOption("--namespace <ns>", "Kubernetes namespace").option("--label-selector <selector>", "Label selector (e.g. app=my-app)").action(async (opts) => {
10836
+ oc.command("pods").description("List pod health summary for a namespace \u2014 pre-processed for LLM analysis. Use --raw to return the full unmodified Kubernetes pod objects.").requiredOption("--namespace <ns>", "Kubernetes namespace").option("--label-selector <selector>", "Label selector (e.g. app=my-app)").option("--raw", "Return full Kubernetes pod objects without summarization (includes env vars, images, node name, annotations, resources, etc.)", false).action(async (opts) => {
10530
10837
  const start = Date.now();
10531
10838
  try {
10532
10839
  const http = getHttp2(program2);
@@ -10536,6 +10843,15 @@ function registerOpenShiftCommands(program2) {
10536
10843
  `/api/v1/namespaces/${encodeURIComponent(opts.namespace)}/pods`,
10537
10844
  { params }
10538
10845
  );
10846
+ if (opts.raw) {
10847
+ success(
10848
+ { namespace: opts.namespace, count: podList.items.length, pods: podList.items },
10849
+ "openshift",
10850
+ "pods",
10851
+ start
10852
+ );
10853
+ return;
10854
+ }
10539
10855
  const summaryCounters = {
10540
10856
  total: podList.items.length,
10541
10857
  running: 0,
@@ -10712,6 +11028,71 @@ function registerOpenShiftCommands(program2) {
10712
11028
  fail(err, "openshift", "pod-metrics", start);
10713
11029
  }
10714
11030
  });
11031
+ oc.command("resource-usage").description("Combined CPU/memory usage, limits, and requests for all pods in a namespace \u2014 all values normalized to millicores (m) and mebibytes (Mi). Requires metrics-server. Use --csv to emit a spreadsheet-friendly CSV.").requiredOption("--namespace <ns>", "Kubernetes namespace").option("--label-selector <selector>", "Label selector (e.g. app=my-app)").option("--csv", "Output as CSV instead of JSON (suitable for Excel)", false).action(async (opts) => {
11032
+ const start = Date.now();
11033
+ try {
11034
+ const http = getHttp2(program2);
11035
+ const nsEncoded = encodeURIComponent(opts.namespace);
11036
+ const podParams = {};
11037
+ if (opts.labelSelector) podParams["labelSelector"] = opts.labelSelector;
11038
+ const [podList, metricsList] = await Promise.all([
11039
+ http.openshift(`/api/v1/namespaces/${nsEncoded}/pods`, { params: podParams }),
11040
+ http.openshift(`/apis/metrics.k8s.io/v1beta1/namespaces/${nsEncoded}/pods`, { params: podParams })
11041
+ ]);
11042
+ const metricsIndex = /* @__PURE__ */ new Map();
11043
+ for (const pm of metricsList.items) {
11044
+ const containerMap = /* @__PURE__ */ new Map();
11045
+ for (const c of pm.containers) {
11046
+ containerMap.set(c.name, c.usage);
11047
+ }
11048
+ metricsIndex.set(pm.metadata.name, containerMap);
11049
+ }
11050
+ const rows = [];
11051
+ for (const pod of podList.items) {
11052
+ const podName = pod.metadata.name;
11053
+ const podMetrics = metricsIndex.get(podName);
11054
+ const containers = pod.spec.containers ?? [];
11055
+ for (const container of containers) {
11056
+ const usage = podMetrics?.get(container.name);
11057
+ rows.push({
11058
+ pod: podName,
11059
+ container: container.name,
11060
+ cpuUsageMillicores: parseCpuMillicores(usage?.cpu),
11061
+ memUsageMiB: parseMemMiB(usage?.memory),
11062
+ cpuLimitsMillicores: parseCpuMillicores(container.resources?.limits?.cpu),
11063
+ memLimitsMiB: parseMemMiB(container.resources?.limits?.memory),
11064
+ cpuRequestsMillicores: parseCpuMillicores(container.resources?.requests?.cpu),
11065
+ memRequestsMiB: parseMemMiB(container.resources?.requests?.memory)
11066
+ });
11067
+ }
11068
+ }
11069
+ if (opts.csv) {
11070
+ const header = "Pod,Container,CPU Usage (m),Memory Usage (Mi),CPU Limits (m),Memory Limits (Mi),CPU Requests (m),Memory Requests (Mi)";
11071
+ const lines = rows.map(
11072
+ (r) => [
11073
+ r.pod,
11074
+ r.container,
11075
+ r.cpuUsageMillicores ?? "",
11076
+ r.memUsageMiB ?? "",
11077
+ r.cpuLimitsMillicores ?? "",
11078
+ r.memLimitsMiB ?? "",
11079
+ r.cpuRequestsMillicores ?? "",
11080
+ r.memRequestsMiB ?? ""
11081
+ ].join(",")
11082
+ );
11083
+ writeRawOutput([header, ...lines].join("\n") + "\n");
11084
+ } else {
11085
+ success(
11086
+ { namespace: opts.namespace, count: rows.length, units: { cpu: "m", memory: "Mi" }, rows },
11087
+ "openshift",
11088
+ "resource-usage",
11089
+ start
11090
+ );
11091
+ }
11092
+ } catch (err) {
11093
+ fail(err, "openshift", "resource-usage", start);
11094
+ }
11095
+ });
10715
11096
  }
10716
11097
 
10717
11098
  // src/services/dynatrace/commands.ts
@@ -10937,6 +11318,500 @@ function registerLogscaleCommands(program2) {
10937
11318
  });
10938
11319
  }
10939
11320
 
11321
+ // src/services/splitio/commands.ts
11322
+ import fs5 from "fs";
11323
+ import confirm2 from "@inquirer/confirm";
11324
+ function getHttp5(program2) {
11325
+ const opts = program2.optsWithGlobals();
11326
+ return createHttpClient(
11327
+ loadConfig({ configPath: opts.config }),
11328
+ Boolean(opts.dryRun)
11329
+ );
11330
+ }
11331
+ function loadInputFile(filePath) {
11332
+ let raw;
11333
+ if (filePath === "-") {
11334
+ raw = fs5.readFileSync(process.stdin.fd, "utf8");
11335
+ } else {
11336
+ raw = fs5.readFileSync(filePath, "utf8");
11337
+ }
11338
+ try {
11339
+ return JSON.parse(raw);
11340
+ } catch {
11341
+ throw new PncliError(`Could not parse JSON from ${filePath === "-" ? "stdin" : filePath}`);
11342
+ }
11343
+ }
11344
+ async function confirmWrite(opts, opLabel) {
11345
+ if (opts.yes) return;
11346
+ const proceed = await confirm2({
11347
+ message: `Submit Change Request "${opts.mnemonic}" (${opLabel})? This creates an approval request in Split.`,
11348
+ default: false
11349
+ });
11350
+ if (!proceed) throw new PncliError("Aborted by user.", 0);
11351
+ }
11352
+ function buildCrBase(opts) {
11353
+ const base = {
11354
+ title: opts.mnemonic,
11355
+ comment: opts.description,
11356
+ crNumber: opts.changeNumber
11357
+ };
11358
+ if (opts.approvers) {
11359
+ base["approvers"] = opts.approvers.split(",").map((s) => s.trim()).filter(Boolean).map((id) => ({ id }));
11360
+ }
11361
+ return base;
11362
+ }
11363
+ function registerSplitioCommands(program2) {
11364
+ const splitio = program2.command("splitio").description("Split.IO feature flag administration");
11365
+ const workspaces = splitio.command("workspaces").description("Workspace operations");
11366
+ workspaces.command("list").description("List all Split.IO workspaces").action(async () => {
11367
+ const start = Date.now();
11368
+ try {
11369
+ const http = getHttp5(program2);
11370
+ const data = await http.splitio("/internal/api/v2/workspaces");
11371
+ success(
11372
+ { count: data.objects.length, workspaces: data.objects },
11373
+ "splitio",
11374
+ "workspaces list",
11375
+ start
11376
+ );
11377
+ } catch (err) {
11378
+ fail(err, "splitio", "workspaces list", start);
11379
+ }
11380
+ });
11381
+ const environments = splitio.command("environments").description("Environment operations");
11382
+ environments.command("list").description("List environments in a workspace").requiredOption("--workspace <id>", "Workspace ID").action(async (opts) => {
11383
+ const start = Date.now();
11384
+ try {
11385
+ const http = getHttp5(program2);
11386
+ const data = await http.splitio(
11387
+ `/internal/api/v2/workspaces/${encodeURIComponent(opts.workspace)}/environments`
11388
+ );
11389
+ success(
11390
+ { count: data.objects.length, environments: data.objects },
11391
+ "splitio",
11392
+ "environments list",
11393
+ start
11394
+ );
11395
+ } catch (err) {
11396
+ fail(err, "splitio", "environments list", start);
11397
+ }
11398
+ });
11399
+ const flags = splitio.command("flags").description("Feature flag operations");
11400
+ flags.command("list").description("List feature flags in a workspace").requiredOption("--workspace <id>", "Workspace ID").option("--limit <n>", "Maximum number of flags to return", "50").option("--offset <n>", "Pagination offset", "0").option("--flag-set <name>", "Filter by flag set name").action(async (opts) => {
11401
+ const start = Date.now();
11402
+ try {
11403
+ const http = getHttp5(program2);
11404
+ const params = {
11405
+ wsId: opts.workspace,
11406
+ limit: parseInt(opts.limit, 10),
11407
+ offset: parseInt(opts.offset, 10)
11408
+ };
11409
+ if (opts.flagSet) params["flagSetName"] = opts.flagSet;
11410
+ const data = await http.splitio("/internal/api/v2/splits", { params });
11411
+ success(
11412
+ { count: data.objects.length, totalCount: data.totalCount, flags: data.objects },
11413
+ "splitio",
11414
+ "flags list",
11415
+ start
11416
+ );
11417
+ } catch (err) {
11418
+ fail(err, "splitio", "flags list", start);
11419
+ }
11420
+ });
11421
+ flags.command("get").description("Get a flag definition").requiredOption("--workspace <id>", "Workspace ID").requiredOption("--flag <name>", "Feature flag name").option("--environment <id>", "Environment ID \u2014 include targeting rules for this environment").action(async (opts) => {
11422
+ const start = Date.now();
11423
+ try {
11424
+ const http = getHttp5(program2);
11425
+ if (opts.environment) {
11426
+ const data = await http.splitio(
11427
+ `/internal/api/v2/splits/${encodeURIComponent(opts.flag)}/environments/${encodeURIComponent(opts.environment)}`,
11428
+ { params: { wsId: opts.workspace } }
11429
+ );
11430
+ success(data, "splitio", "flags get", start);
11431
+ } else {
11432
+ const data = await http.splitio(
11433
+ `/internal/api/v2/splits/${encodeURIComponent(opts.flag)}`,
11434
+ { params: { wsId: opts.workspace } }
11435
+ );
11436
+ success(data, "splitio", "flags get", start);
11437
+ }
11438
+ } catch (err) {
11439
+ fail(err, "splitio", "flags get", start);
11440
+ }
11441
+ });
11442
+ flags.command("update").description("Submit a Change Request to replace a flag's full definition in an environment").requiredOption("--workspace <id>", "Workspace ID").requiredOption("--flag <name>", "Feature flag name").requiredOption("--environment <id>", "Environment ID").requiredOption("--mnemonic <text>", "Short Change Request title (mnemonic)").requiredOption("--description <text>", "Change Request description").requiredOption("--change-number <CHG>", "ServiceNow change request number (e.g. CHG0001234)").option("--approvers <ids>", "Comma-separated Split user IDs required to approve this CR").option("--input-file <path>", "JSON file with the complete flag definition body (use - for stdin)").option("--yes", "Skip interactive confirmation prompt").action(async (opts) => {
11443
+ const start = Date.now();
11444
+ try {
11445
+ if (!opts.inputFile) throw new PncliError("--input-file is required for flags update");
11446
+ const definition = loadInputFile(opts.inputFile);
11447
+ await confirmWrite(opts, `UPDATE ${opts.flag} in ${opts.environment}`);
11448
+ const http = getHttp5(program2);
11449
+ const body = {
11450
+ ...buildCrBase(opts),
11451
+ operationType: "UPDATE_SPLIT_DEFINITION",
11452
+ split: {
11453
+ name: opts.flag,
11454
+ environment: { id: opts.environment },
11455
+ definition
11456
+ },
11457
+ workspace: { id: opts.workspace }
11458
+ };
11459
+ const data = await http.splitio("/internal/api/v2/changeRequests", {
11460
+ method: "POST",
11461
+ body
11462
+ });
11463
+ success(data, "splitio", "flags update", start);
11464
+ } catch (err) {
11465
+ fail(err, "splitio", "flags update", start);
11466
+ }
11467
+ });
11468
+ flags.command("set-rule").description("Change one targeting rule without altering the rest of the flag definition").requiredOption("--workspace <id>", "Workspace ID").requiredOption("--flag <name>", "Feature flag name").requiredOption("--environment <id>", "Environment ID").requiredOption("--rule-index <n>", "Zero-based index of the targeting rule to update").option("--treatment <name>", "Treatment to assign to the entire rule bucket").option("--weights <json>", "JSON array of {treatment, size} objects for weighted distribution").requiredOption("--mnemonic <text>", "Short Change Request title (mnemonic)").requiredOption("--description <text>", "Change Request description").requiredOption("--change-number <CHG>", "ServiceNow change request number (e.g. CHG0001234)").option("--approvers <ids>", "Comma-separated Split user IDs required to approve this CR").option("--yes", "Skip interactive confirmation prompt").action(async (opts) => {
11469
+ const start = Date.now();
11470
+ try {
11471
+ if (!opts.treatment && !opts.weights) {
11472
+ throw new PncliError("Either --treatment or --weights must be specified");
11473
+ }
11474
+ const http = getHttp5(program2);
11475
+ const current = await http.splitio(
11476
+ `/internal/api/v2/splits/${encodeURIComponent(opts.flag)}/environments/${encodeURIComponent(opts.environment)}`,
11477
+ { params: { wsId: opts.workspace } }
11478
+ );
11479
+ const rules = current.rules ?? [];
11480
+ const idx = parseInt(opts.ruleIndex, 10);
11481
+ if (Number.isNaN(idx) || idx < 0 || idx >= rules.length) {
11482
+ throw new PncliError(`Rule index ${Number.isNaN(idx) ? JSON.stringify(opts.ruleIndex) : idx} is out of range (flag has ${rules.length} rules)`);
11483
+ }
11484
+ if (opts.treatment) {
11485
+ rules[idx] = { ...rules[idx], buckets: [{ treatment: opts.treatment, size: 1e5 }] };
11486
+ } else {
11487
+ const parsed = JSON.parse(opts.weights);
11488
+ rules[idx] = { ...rules[idx], buckets: parsed };
11489
+ }
11490
+ await confirmWrite(opts, `SET_RULE ${idx} on ${opts.flag} in ${opts.environment}`);
11491
+ const body = {
11492
+ ...buildCrBase(opts),
11493
+ operationType: "UPDATE_SPLIT_DEFINITION",
11494
+ split: {
11495
+ name: opts.flag,
11496
+ environment: { id: opts.environment },
11497
+ definition: { ...current, rules }
11498
+ },
11499
+ workspace: { id: opts.workspace }
11500
+ };
11501
+ const data = await http.splitio("/internal/api/v2/changeRequests", {
11502
+ method: "POST",
11503
+ body
11504
+ });
11505
+ success(data, "splitio", "flags set-rule", start);
11506
+ } catch (err) {
11507
+ fail(err, "splitio", "flags set-rule", start);
11508
+ }
11509
+ });
11510
+ flags.command("set-default").description("Update the fallback default rule for traffic matching no targeting rule").requiredOption("--workspace <id>", "Workspace ID").requiredOption("--flag <name>", "Feature flag name").requiredOption("--environment <id>", "Environment ID").requiredOption("--treatment <name>", "Treatment to assign as the default").requiredOption("--mnemonic <text>", "Short Change Request title (mnemonic)").requiredOption("--description <text>", "Change Request description").requiredOption("--change-number <CHG>", "ServiceNow change request number (e.g. CHG0001234)").option("--approvers <ids>", "Comma-separated Split user IDs required to approve this CR").option("--yes", "Skip interactive confirmation prompt").action(async (opts) => {
11511
+ const start = Date.now();
11512
+ try {
11513
+ const http = getHttp5(program2);
11514
+ const current = await http.splitio(
11515
+ `/internal/api/v2/splits/${encodeURIComponent(opts.flag)}/environments/${encodeURIComponent(opts.environment)}`,
11516
+ { params: { wsId: opts.workspace } }
11517
+ );
11518
+ await confirmWrite(opts, `SET_DEFAULT ${opts.treatment} on ${opts.flag} in ${opts.environment}`);
11519
+ const body = {
11520
+ ...buildCrBase(opts),
11521
+ operationType: "UPDATE_SPLIT_DEFINITION",
11522
+ split: {
11523
+ name: opts.flag,
11524
+ environment: { id: opts.environment },
11525
+ definition: {
11526
+ ...current,
11527
+ defaultRule: [{ treatment: opts.treatment, size: 1e5 }]
11528
+ }
11529
+ },
11530
+ workspace: { id: opts.workspace }
11531
+ };
11532
+ const data = await http.splitio("/internal/api/v2/changeRequests", {
11533
+ method: "POST",
11534
+ body
11535
+ });
11536
+ success(data, "splitio", "flags set-default", start);
11537
+ } catch (err) {
11538
+ fail(err, "splitio", "flags set-default", start);
11539
+ }
11540
+ });
11541
+ flags.command("toggle").description("Enable or disable a single flag via a Change Request").requiredOption("--workspace <id>", "Workspace ID").requiredOption("--flag <name>", "Feature flag name").requiredOption("--environment <id>", "Environment ID").requiredOption("--enabled <bool>", "true to restore / false to kill the flag").requiredOption("--mnemonic <text>", "Short Change Request title (mnemonic)").requiredOption("--description <text>", "Change Request description").requiredOption("--change-number <CHG>", "ServiceNow change request number (e.g. CHG0001234)").option("--approvers <ids>", "Comma-separated Split user IDs required to approve this CR").option("--yes", "Skip interactive confirmation prompt").action(async (opts) => {
11542
+ const start = Date.now();
11543
+ try {
11544
+ const enable = opts.enabled.toLowerCase() !== "false" && opts.enabled !== "0";
11545
+ const opType = enable ? "RESTORE_SPLIT" : "KILL_SPLIT";
11546
+ await confirmWrite(opts, `${opType} ${opts.flag} in ${opts.environment}`);
11547
+ const http = getHttp5(program2);
11548
+ const body = {
11549
+ ...buildCrBase(opts),
11550
+ operationType: opType,
11551
+ split: { name: opts.flag, environment: { id: opts.environment } },
11552
+ workspace: { id: opts.workspace }
11553
+ };
11554
+ const data = await http.splitio("/internal/api/v2/changeRequests", {
11555
+ method: "POST",
11556
+ body
11557
+ });
11558
+ success(data, "splitio", "flags toggle", start);
11559
+ } catch (err) {
11560
+ fail(err, "splitio", "flags toggle", start);
11561
+ }
11562
+ });
11563
+ flags.command("batch-toggle").description("Enable or disable multiple flags from a JSON file (each creates its own Change Request)").requiredOption("--workspace <id>", "Workspace ID").requiredOption("--environment <id>", "Environment ID").requiredOption("--mnemonic <text>", "Short Change Request title prefix (mnemonic)").requiredOption("--description <text>", "Change Request description").requiredOption("--change-number <CHG>", "ServiceNow change request number (e.g. CHG0001234)").option("--approvers <ids>", "Comma-separated Split user IDs required to approve each CR").option("--input-file <path>", "JSON file: array of {flag, enabled} objects (use - for stdin)").option("--yes", "Skip interactive confirmation prompt").action(async (opts) => {
11564
+ const start = Date.now();
11565
+ try {
11566
+ if (!opts.inputFile) throw new PncliError("--input-file is required for batch-toggle");
11567
+ const entries = loadInputFile(opts.inputFile);
11568
+ if (!Array.isArray(entries)) throw new PncliError("Input file must be a JSON array of {flag, enabled} objects");
11569
+ await confirmWrite(opts, `BATCH_TOGGLE ${entries.length} flags in ${opts.environment}`);
11570
+ const http = getHttp5(program2);
11571
+ const results = [];
11572
+ for (const entry of entries) {
11573
+ try {
11574
+ const opType = entry.enabled ? "RESTORE_SPLIT" : "KILL_SPLIT";
11575
+ const body = {
11576
+ ...buildCrBase(opts),
11577
+ title: `${opts.mnemonic} \u2014 ${entry.flag}`,
11578
+ operationType: opType,
11579
+ split: { name: entry.flag, environment: { id: opts.environment } },
11580
+ workspace: { id: opts.workspace }
11581
+ };
11582
+ const cr = await http.splitio("/internal/api/v2/changeRequests", {
11583
+ method: "POST",
11584
+ body
11585
+ });
11586
+ results.push({ flag: entry.flag, ok: true, id: cr.id });
11587
+ } catch (err) {
11588
+ results.push({
11589
+ flag: entry.flag,
11590
+ ok: false,
11591
+ error: err instanceof Error ? err.message : String(err)
11592
+ });
11593
+ }
11594
+ }
11595
+ success({ submitted: results.length, results }, "splitio", "flags batch-toggle", start);
11596
+ } catch (err) {
11597
+ fail(err, "splitio", "flags batch-toggle", start);
11598
+ }
11599
+ });
11600
+ flags.command("kill").description("Submit a Change Request to kill (disable) a flag").requiredOption("--workspace <id>", "Workspace ID").requiredOption("--flag <name>", "Feature flag name").requiredOption("--environment <id>", "Environment ID").requiredOption("--mnemonic <text>", "Short Change Request title (mnemonic)").requiredOption("--description <text>", "Change Request description").requiredOption("--change-number <CHG>", "ServiceNow change request number (e.g. CHG0001234)").option("--approvers <ids>", "Comma-separated Split user IDs required to approve this CR").option("--yes", "Skip interactive confirmation prompt").action(async (opts) => {
11601
+ const start = Date.now();
11602
+ try {
11603
+ await confirmWrite(opts, `KILL ${opts.flag} in ${opts.environment}`);
11604
+ const http = getHttp5(program2);
11605
+ const body = {
11606
+ ...buildCrBase(opts),
11607
+ operationType: "KILL_SPLIT",
11608
+ split: { name: opts.flag, environment: { id: opts.environment } },
11609
+ workspace: { id: opts.workspace }
11610
+ };
11611
+ const data = await http.splitio("/internal/api/v2/changeRequests", {
11612
+ method: "POST",
11613
+ body
11614
+ });
11615
+ success(data, "splitio", "flags kill", start);
11616
+ } catch (err) {
11617
+ fail(err, "splitio", "flags kill", start);
11618
+ }
11619
+ });
11620
+ flags.command("restore").description("Submit a Change Request to restore a killed flag").requiredOption("--workspace <id>", "Workspace ID").requiredOption("--flag <name>", "Feature flag name").requiredOption("--environment <id>", "Environment ID").requiredOption("--mnemonic <text>", "Short Change Request title (mnemonic)").requiredOption("--description <text>", "Change Request description").requiredOption("--change-number <CHG>", "ServiceNow change request number (e.g. CHG0001234)").option("--approvers <ids>", "Comma-separated Split user IDs required to approve this CR").option("--yes", "Skip interactive confirmation prompt").action(async (opts) => {
11621
+ const start = Date.now();
11622
+ try {
11623
+ await confirmWrite(opts, `RESTORE ${opts.flag} in ${opts.environment}`);
11624
+ const http = getHttp5(program2);
11625
+ const body = {
11626
+ ...buildCrBase(opts),
11627
+ operationType: "RESTORE_SPLIT",
11628
+ split: { name: opts.flag, environment: { id: opts.environment } },
11629
+ workspace: { id: opts.workspace }
11630
+ };
11631
+ const data = await http.splitio("/internal/api/v2/changeRequests", {
11632
+ method: "POST",
11633
+ body
11634
+ });
11635
+ success(data, "splitio", "flags restore", start);
11636
+ } catch (err) {
11637
+ fail(err, "splitio", "flags restore", start);
11638
+ }
11639
+ });
11640
+ flags.command("archive").description("Submit a Change Request to archive (delete definition of) a flag").requiredOption("--workspace <id>", "Workspace ID").requiredOption("--flag <name>", "Feature flag name").requiredOption("--mnemonic <text>", "Short Change Request title (mnemonic)").requiredOption("--description <text>", "Change Request description").requiredOption("--change-number <CHG>", "ServiceNow change request number (e.g. CHG0001234)").option("--yes", "Skip interactive confirmation prompt").action(async (opts) => {
11641
+ const start = Date.now();
11642
+ try {
11643
+ await confirmWrite(opts, `ARCHIVE ${opts.flag}`);
11644
+ const http = getHttp5(program2);
11645
+ const body = {
11646
+ ...buildCrBase(opts),
11647
+ operationType: "ARCHIVE_SPLIT",
11648
+ split: { name: opts.flag },
11649
+ workspace: { id: opts.workspace }
11650
+ };
11651
+ const data = await http.splitio("/internal/api/v2/changeRequests", {
11652
+ method: "POST",
11653
+ body
11654
+ });
11655
+ success(data, "splitio", "flags archive", start);
11656
+ } catch (err) {
11657
+ fail(err, "splitio", "flags archive", start);
11658
+ }
11659
+ });
11660
+ const changeRequests = splitio.command("change-requests").description("Change Request tracking");
11661
+ changeRequests.command("list").description("List Change Requests for a workspace").requiredOption("--workspace <id>", "Workspace ID").option("--environment <id>", "Filter by environment ID").option("--status <status>", "Filter by status (REQUESTED, APPROVED, REJECTED, PUBLISHED)").option("--limit <n>", "Maximum number of results", "25").option("--offset <n>", "Pagination offset", "0").action(async (opts) => {
11662
+ const start = Date.now();
11663
+ try {
11664
+ const http = getHttp5(program2);
11665
+ const params = {
11666
+ wsId: opts.workspace,
11667
+ limit: parseInt(opts.limit, 10),
11668
+ offset: parseInt(opts.offset, 10)
11669
+ };
11670
+ if (opts.environment) params["environmentId"] = opts.environment;
11671
+ if (opts.status) params["status"] = opts.status;
11672
+ const data = await http.splitio(
11673
+ "/internal/api/v2/changeRequests",
11674
+ { params }
11675
+ );
11676
+ success(
11677
+ { count: data.objects.length, totalCount: data.totalCount, changeRequests: data.objects },
11678
+ "splitio",
11679
+ "change-requests list",
11680
+ start
11681
+ );
11682
+ } catch (err) {
11683
+ fail(err, "splitio", "change-requests list", start);
11684
+ }
11685
+ });
11686
+ changeRequests.command("get").description("Get a Change Request by ID").requiredOption("--id <id>", "Change Request ID").action(async (opts) => {
11687
+ const start = Date.now();
11688
+ try {
11689
+ const http = getHttp5(program2);
11690
+ const data = await http.splitio(
11691
+ `/internal/api/v2/changeRequests/${encodeURIComponent(opts.id)}`
11692
+ );
11693
+ success(data, "splitio", "change-requests get", start);
11694
+ } catch (err) {
11695
+ fail(err, "splitio", "change-requests get", start);
11696
+ }
11697
+ });
11698
+ }
11699
+
11700
+ // src/services/figma/commands.ts
11701
+ function parseFileKey(input3) {
11702
+ if (input3.startsWith("http://") || input3.startsWith("https://")) {
11703
+ const match = /figma\.com\/(?:design|file)\/([A-Za-z0-9]+)/i.exec(input3);
11704
+ if (!match?.[1]) {
11705
+ throw new Error(`Could not extract Figma file key from URL: ${input3}
11706
+ Expected format: https://www.figma.com/design/<file-key>/...`);
11707
+ }
11708
+ return match[1];
11709
+ }
11710
+ return input3;
11711
+ }
11712
+ function getHttp6(program2) {
11713
+ const opts = program2.optsWithGlobals();
11714
+ return createHttpClient(
11715
+ loadConfig({ configPath: opts.config }),
11716
+ Boolean(opts.dryRun)
11717
+ );
11718
+ }
11719
+ function registerFigmaCommands(program2) {
11720
+ const figma = program2.command("figma").description("Figma design operations (files, comments, versions)");
11721
+ figma.command("file").description("Get a Figma file \u2014 metadata, structure, and component/style inventory").argument("<file-key-or-url>", "Figma file key or full Figma URL (https://www.figma.com/design/<key>/...)").option("--document", "Include the full document node tree (can be large; omitted by default)").action(async (fileKeyOrUrl, opts) => {
11722
+ const start = Date.now();
11723
+ const { success: success2, fail: fail2 } = await import("./output-XT3WT5L5.js");
11724
+ try {
11725
+ const fileKey = parseFileKey(fileKeyOrUrl);
11726
+ const params = {};
11727
+ if (!opts.document) {
11728
+ params["geometry"] = "paths";
11729
+ params["depth"] = 0;
11730
+ }
11731
+ const data = await getHttp6(program2).figma(`/v1/files/${encodeURIComponent(fileKey)}`, { params });
11732
+ success2(
11733
+ {
11734
+ fileKey,
11735
+ name: data.name,
11736
+ lastModified: data.lastModified,
11737
+ version: data.version,
11738
+ thumbnailUrl: data.thumbnailUrl,
11739
+ role: data.role,
11740
+ editorType: data.editorType,
11741
+ schemaVersion: data.schemaVersion,
11742
+ ...opts.document ? { document: data.document } : {},
11743
+ componentCount: data.components ? Object.keys(data.components).length : 0,
11744
+ styleCount: data.styles ? Object.keys(data.styles).length : 0
11745
+ },
11746
+ "figma",
11747
+ "file",
11748
+ start
11749
+ );
11750
+ } catch (err) {
11751
+ fail2(err, "figma", "file", start);
11752
+ }
11753
+ });
11754
+ figma.command("comments").description("Get comments on a Figma file").argument("<file-key-or-url>", "Figma file key or full Figma URL").option("--as-of <date>", "ISO 8601 timestamp \u2014 return only comments created before this time").action(async (fileKeyOrUrl, opts) => {
11755
+ const start = Date.now();
11756
+ const { success: success2, fail: fail2 } = await import("./output-XT3WT5L5.js");
11757
+ try {
11758
+ const fileKey = parseFileKey(fileKeyOrUrl);
11759
+ const params = {};
11760
+ if (opts.asOf) params["as_of"] = opts.asOf;
11761
+ const data = await getHttp6(program2).figma(`/v1/files/${encodeURIComponent(fileKey)}/comments`, { params });
11762
+ success2(
11763
+ { fileKey, count: data.comments.length, comments: data.comments },
11764
+ "figma",
11765
+ "comments",
11766
+ start
11767
+ );
11768
+ } catch (err) {
11769
+ fail2(err, "figma", "comments", start);
11770
+ }
11771
+ });
11772
+ figma.command("versions").description("Get the version history of a Figma file").argument("<file-key-or-url>", "Figma file key or full Figma URL").action(async (fileKeyOrUrl) => {
11773
+ const start = Date.now();
11774
+ const { success: success2, fail: fail2 } = await import("./output-XT3WT5L5.js");
11775
+ try {
11776
+ const fileKey = parseFileKey(fileKeyOrUrl);
11777
+ const data = await getHttp6(program2).figma(`/v1/files/${encodeURIComponent(fileKey)}/versions`);
11778
+ success2(
11779
+ { fileKey, count: data.versions.length, versions: data.versions },
11780
+ "figma",
11781
+ "versions",
11782
+ start
11783
+ );
11784
+ } catch (err) {
11785
+ fail2(err, "figma", "versions", start);
11786
+ }
11787
+ });
11788
+ figma.command("project-files").description("List files in a Figma project").argument("<project-id>", "Figma project ID (visible in the project URL)").action(async (projectId) => {
11789
+ const start = Date.now();
11790
+ const { success: success2, fail: fail2 } = await import("./output-XT3WT5L5.js");
11791
+ try {
11792
+ const data = await getHttp6(program2).figma(`/v1/projects/${encodeURIComponent(projectId)}/files`);
11793
+ success2(
11794
+ { projectId, count: data.files.length, files: data.files },
11795
+ "figma",
11796
+ "project-files",
11797
+ start
11798
+ );
11799
+ } catch (err) {
11800
+ fail2(err, "figma", "project-files", start);
11801
+ }
11802
+ });
11803
+ figma.command("me").description("Get the current Figma user (useful for verifying credentials)").action(async () => {
11804
+ const start = Date.now();
11805
+ const { success: success2, fail: fail2 } = await import("./output-XT3WT5L5.js");
11806
+ try {
11807
+ const data = await getHttp6(program2).figma("/v1/me");
11808
+ success2(data, "figma", "me", start);
11809
+ } catch (err) {
11810
+ fail2(err, "figma", "me", start);
11811
+ }
11812
+ });
11813
+ }
11814
+
10940
11815
  // src/cli.ts
10941
11816
  if (!process.env.PNCLI_VERIFY_TLS) {
10942
11817
  process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
@@ -10973,7 +11848,6 @@ registerConfigCommands(program);
10973
11848
  registerAdoCommands(program);
10974
11849
  registerJenkinsCommands(program);
10975
11850
  registerArtifactoryCommands(program);
10976
- registerUdeployCommands(program);
10977
11851
  registerCheckmarxCommands(program);
10978
11852
  registerServiceNowCommands(program);
10979
11853
  registerContrastCommands(program);
@@ -10983,6 +11857,8 @@ registerJwtCommands(program);
10983
11857
  registerOpenShiftCommands(program);
10984
11858
  registerDynatraceCommands(program);
10985
11859
  registerLogscaleCommands(program);
11860
+ registerSplitioCommands(program);
11861
+ registerFigmaCommands(program);
10986
11862
  program.addHelpText("after", `
10987
11863
  Services:
10988
11864
  git Local git operations (status, diff, log, branch)
@@ -10994,9 +11870,8 @@ Services:
10994
11870
  sonar SonarQube Server (quality gates, issues, metrics, hotspots)
10995
11871
  sde SDElements (threat modeling, countermeasures, compliance)
10996
11872
  ado Azure DevOps Server (work items, repos, PRs, pipelines)
10997
- jenkins Jenkins Data Center (jobs, builds, logs)
11873
+ jenkins Jenkins (jobs, builds, logs, named instances)
10998
11874
  artifactory Artifactory (repos, artifact search, build info, properties)
10999
- udeploy IBM UrbanCode Deploy (component versions, deployment processes)
11000
11875
  checkmarx Checkmarx One (projects, scans, scan statistics)
11001
11876
  servicenow ServiceNow (change requests)
11002
11877
  contrast Contrast IAST (applications, vulnerability findings)
@@ -11004,6 +11879,8 @@ Services:
11004
11879
  openshift OpenShift / Kubernetes (pods, events, logs, metrics)
11005
11880
  dynatrace Dynatrace (services, entities, problems, traces, Kubernetes workloads)
11006
11881
  logscale LogScale (log query, repositories)
11882
+ splitio Split.IO (feature flags, Change Requests)
11883
+ figma Figma (files, comments, version history)
11007
11884
  config Manage pncli configuration
11008
11885
  skills Download and manage Claude Code skills
11009
11886
  jwt JWT token utilities (decode header and payload)