@kolatts/pncli 1.13.0 → 1.14.1
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/{chunk-BNT4SEZD.js → chunk-BNMFIGXF.js} +82 -1
- package/dist/chunk-BNMFIGXF.js.map +1 -0
- package/dist/{chunk-AWSXVWRN.js → chunk-CFUUOW7B.js} +11 -1
- package/dist/chunk-CFUUOW7B.js.map +1 -0
- package/dist/cli.js +393 -34
- package/dist/cli.js.map +1 -1
- package/dist/{config-OFLPO5DN.js → config-TL5X47RY.js} +2 -2
- package/dist/{http-BNCZJQTD.js → http-UN6N4WIB.js} +2 -2
- package/package.json +1 -1
- package/skills/pncli/SKILL.md +3 -1
- package/skills/pncli/jwt.md +41 -0
- package/skills/pncli/openshift.md +106 -0
- package/dist/chunk-AWSXVWRN.js.map +0 -1
- package/dist/chunk-BNT4SEZD.js.map +0 -1
- /package/dist/{config-OFLPO5DN.js.map → config-TL5X47RY.js.map} +0 -0
- /package/dist/{http-BNCZJQTD.js.map → http-UN6N4WIB.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
setRepoConfigValue,
|
|
9
9
|
writeGlobalConfig,
|
|
10
10
|
writeRepoConfig
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-CFUUOW7B.js";
|
|
12
12
|
import {
|
|
13
13
|
ExitCode,
|
|
14
14
|
PncliError,
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
success,
|
|
21
21
|
warn,
|
|
22
22
|
writeRawOutput
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-BNMFIGXF.js";
|
|
24
24
|
import {
|
|
25
25
|
AdoCoreClient
|
|
26
26
|
} from "./chunk-VDL32PSG.js";
|
|
@@ -798,7 +798,39 @@ import path from "path";
|
|
|
798
798
|
|
|
799
799
|
// src/services/jira/client.ts
|
|
800
800
|
import { readFileSync } from "fs";
|
|
801
|
-
import { basename
|
|
801
|
+
import { basename } from "path";
|
|
802
|
+
|
|
803
|
+
// src/lib/mime.ts
|
|
804
|
+
import { extname } from "path";
|
|
805
|
+
function guessMimeType(filePath) {
|
|
806
|
+
const ext = extname(filePath).toLowerCase();
|
|
807
|
+
const map = {
|
|
808
|
+
".jpg": "image/jpeg",
|
|
809
|
+
".jpeg": "image/jpeg",
|
|
810
|
+
".png": "image/png",
|
|
811
|
+
".gif": "image/gif",
|
|
812
|
+
".svg": "image/svg+xml",
|
|
813
|
+
".pdf": "application/pdf",
|
|
814
|
+
".txt": "text/plain",
|
|
815
|
+
".log": "text/plain",
|
|
816
|
+
".csv": "text/csv",
|
|
817
|
+
".json": "application/json",
|
|
818
|
+
".xml": "application/xml",
|
|
819
|
+
".zip": "application/zip",
|
|
820
|
+
".tar": "application/x-tar",
|
|
821
|
+
".gz": "application/gzip",
|
|
822
|
+
".md": "text/markdown",
|
|
823
|
+
".html": "text/html",
|
|
824
|
+
".htm": "text/html",
|
|
825
|
+
".doc": "application/msword",
|
|
826
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
827
|
+
".xls": "application/vnd.ms-excel",
|
|
828
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
829
|
+
};
|
|
830
|
+
return map[ext] ?? "application/octet-stream";
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// src/services/jira/client.ts
|
|
802
834
|
var API2 = "/rest/api/2";
|
|
803
835
|
var JiraClient = class {
|
|
804
836
|
constructor(http) {
|
|
@@ -996,33 +1028,6 @@ var JiraClient = class {
|
|
|
996
1028
|
});
|
|
997
1029
|
}
|
|
998
1030
|
};
|
|
999
|
-
function guessMimeType(filePath) {
|
|
1000
|
-
const ext = extname(filePath).toLowerCase();
|
|
1001
|
-
const map = {
|
|
1002
|
-
".jpg": "image/jpeg",
|
|
1003
|
-
".jpeg": "image/jpeg",
|
|
1004
|
-
".png": "image/png",
|
|
1005
|
-
".gif": "image/gif",
|
|
1006
|
-
".svg": "image/svg+xml",
|
|
1007
|
-
".pdf": "application/pdf",
|
|
1008
|
-
".txt": "text/plain",
|
|
1009
|
-
".log": "text/plain",
|
|
1010
|
-
".csv": "text/csv",
|
|
1011
|
-
".json": "application/json",
|
|
1012
|
-
".xml": "application/xml",
|
|
1013
|
-
".zip": "application/zip",
|
|
1014
|
-
".tar": "application/x-tar",
|
|
1015
|
-
".gz": "application/gzip",
|
|
1016
|
-
".md": "text/markdown",
|
|
1017
|
-
".html": "text/html",
|
|
1018
|
-
".htm": "text/html",
|
|
1019
|
-
".doc": "application/msword",
|
|
1020
|
-
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
1021
|
-
".xls": "application/vnd.ms-excel",
|
|
1022
|
-
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
1023
|
-
};
|
|
1024
|
-
return map[ext] ?? "application/octet-stream";
|
|
1025
|
-
}
|
|
1026
1031
|
function schemaToPncliType(schema) {
|
|
1027
1032
|
if (!schema) return void 0;
|
|
1028
1033
|
const custom = schema.custom ?? "";
|
|
@@ -4331,6 +4336,8 @@ import password from "@inquirer/password";
|
|
|
4331
4336
|
import confirm from "@inquirer/confirm";
|
|
4332
4337
|
|
|
4333
4338
|
// src/services/ado/client/work.ts
|
|
4339
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
4340
|
+
import { basename as basename2 } from "path";
|
|
4334
4341
|
var API6 = "7.1";
|
|
4335
4342
|
var API_PREVIEW = "7.1-preview.4";
|
|
4336
4343
|
var AdoWorkClient = class {
|
|
@@ -4434,6 +4441,25 @@ var AdoWorkClient = class {
|
|
|
4434
4441
|
async downloadAttachment(absoluteUrl) {
|
|
4435
4442
|
return this.http.adoBuffer(absoluteUrl);
|
|
4436
4443
|
}
|
|
4444
|
+
async uploadAttachment(collection, workItemId, filePath, comment) {
|
|
4445
|
+
const fileContent = readFileSync3(filePath);
|
|
4446
|
+
const fileName = basename2(filePath);
|
|
4447
|
+
const attachment = await this.http.adoUpload(
|
|
4448
|
+
`/${encodeURIComponent(collection)}/_apis/wit/attachments?fileName=${encodeURIComponent(fileName)}&api-version=${API6}`,
|
|
4449
|
+
fileContent,
|
|
4450
|
+
"application/octet-stream"
|
|
4451
|
+
);
|
|
4452
|
+
const relationValue = {
|
|
4453
|
+
rel: "AttachedFile",
|
|
4454
|
+
url: attachment.url,
|
|
4455
|
+
...comment ? { attributes: { comment } } : {}
|
|
4456
|
+
};
|
|
4457
|
+
await this.http.ado(
|
|
4458
|
+
`/${encodeURIComponent(collection)}/_apis/wit/workitems/${workItemId}?api-version=${API6}`,
|
|
4459
|
+
{ method: "PATCH", body: [{ op: "add", path: "/relations/-", value: relationValue }], headers: { "Content-Type": "application/json-patch+json" } }
|
|
4460
|
+
);
|
|
4461
|
+
return attachment;
|
|
4462
|
+
}
|
|
4437
4463
|
};
|
|
4438
4464
|
|
|
4439
4465
|
// src/services/ado/discovery.ts
|
|
@@ -4687,6 +4713,16 @@ function registerConfigCommands(program2) {
|
|
|
4687
4713
|
} else {
|
|
4688
4714
|
results.sonatypeiq = { ok: null, message: "not configured" };
|
|
4689
4715
|
}
|
|
4716
|
+
if (cfg.openshift.baseUrl && cfg.openshift.token) {
|
|
4717
|
+
try {
|
|
4718
|
+
await http.openshift("/api/v1");
|
|
4719
|
+
results.openshift = { ok: true, message: "connected" };
|
|
4720
|
+
} catch (err) {
|
|
4721
|
+
results.openshift = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
4722
|
+
}
|
|
4723
|
+
} else {
|
|
4724
|
+
results.openshift = { ok: null, message: "not configured" };
|
|
4725
|
+
}
|
|
4690
4726
|
success(results, "config", "test", start);
|
|
4691
4727
|
} catch (err) {
|
|
4692
4728
|
fail(err, "config", "test", start);
|
|
@@ -4870,11 +4906,23 @@ function registerConfigCommands(program2) {
|
|
|
4870
4906
|
results.sonatypeiq = categorize2(err);
|
|
4871
4907
|
}
|
|
4872
4908
|
}
|
|
4909
|
+
if (!cfg.openshift.token) {
|
|
4910
|
+
results.openshift = { status: "blank", message: "not configured" };
|
|
4911
|
+
} else if (!cfg.openshift.baseUrl) {
|
|
4912
|
+
results.openshift = { status: "error", message: "baseUrl not configured" };
|
|
4913
|
+
} else {
|
|
4914
|
+
try {
|
|
4915
|
+
await http.openshift("/api/v1", { timeoutMs: 1e4 });
|
|
4916
|
+
results.openshift = { status: "valid", message: "ok" };
|
|
4917
|
+
} catch (err) {
|
|
4918
|
+
results.openshift = categorize2(err);
|
|
4919
|
+
}
|
|
4920
|
+
}
|
|
4873
4921
|
const statuses = Object.values(results).map((r) => r.status);
|
|
4874
4922
|
if (statuses.includes("invalid")) process.exitCode = ExitCode.AUTH_ERROR;
|
|
4875
4923
|
else if (statuses.includes("error")) process.exitCode = ExitCode.NETWORK_ERROR;
|
|
4876
4924
|
if (cmdOpts.output === "table") {
|
|
4877
|
-
const services = ["jira", "bitbucket", "confluence", "sonar", "sde", "ado", "jenkins", "udeploy", "artifactory", "checkmarx", "servicenow", "contrast", "sonatypeiq"];
|
|
4925
|
+
const services = ["jira", "bitbucket", "confluence", "sonar", "sde", "ado", "jenkins", "udeploy", "artifactory", "checkmarx", "servicenow", "contrast", "sonatypeiq", "openshift"];
|
|
4878
4926
|
const labelWidth = 14;
|
|
4879
4927
|
const statusWidth = 9;
|
|
4880
4928
|
for (const svc of services) {
|
|
@@ -4888,7 +4936,7 @@ function registerConfigCommands(program2) {
|
|
|
4888
4936
|
process.stdout.write("\n");
|
|
4889
4937
|
} else {
|
|
4890
4938
|
if (opts.pretty) {
|
|
4891
|
-
const services = ["jira", "bitbucket", "confluence", "sonar", "sde", "ado", "jenkins", "udeploy", "artifactory", "checkmarx", "servicenow", "contrast", "sonatypeiq"];
|
|
4939
|
+
const services = ["jira", "bitbucket", "confluence", "sonar", "sde", "ado", "jenkins", "udeploy", "artifactory", "checkmarx", "servicenow", "contrast", "sonatypeiq", "openshift"];
|
|
4892
4940
|
const labelWidth = 14;
|
|
4893
4941
|
const statusWidth = 9;
|
|
4894
4942
|
for (const svc of services) {
|
|
@@ -5376,6 +5424,41 @@ async function initGlobalConfig(start) {
|
|
|
5376
5424
|
}
|
|
5377
5425
|
}
|
|
5378
5426
|
}
|
|
5427
|
+
process.stderr.write("\n\u2500\u2500 OpenShift / Kubernetes \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");
|
|
5428
|
+
const useOpenShift = await confirm({
|
|
5429
|
+
message: "Configure OpenShift / Kubernetes for pod health monitoring?",
|
|
5430
|
+
default: false
|
|
5431
|
+
});
|
|
5432
|
+
let openShiftBaseUrl = "";
|
|
5433
|
+
let openShiftToken = "";
|
|
5434
|
+
if (useOpenShift) {
|
|
5435
|
+
process.stderr.write(" Service account token: cat /var/run/secrets/kubernetes.io/serviceaccount/token\n");
|
|
5436
|
+
openShiftBaseUrl = await input({
|
|
5437
|
+
message: "OpenShift API server URL (e.g. https://api.cluster.example.com:6443):",
|
|
5438
|
+
default: ""
|
|
5439
|
+
});
|
|
5440
|
+
openShiftToken = await password({
|
|
5441
|
+
message: "Service account token:"
|
|
5442
|
+
});
|
|
5443
|
+
if (openShiftBaseUrl && openShiftToken) {
|
|
5444
|
+
process.stderr.write("\n Verifying connection...\n");
|
|
5445
|
+
try {
|
|
5446
|
+
const tempConfig = {
|
|
5447
|
+
...loadConfig(),
|
|
5448
|
+
openshift: {
|
|
5449
|
+
baseUrl: normalizeBaseUrl(openShiftBaseUrl),
|
|
5450
|
+
token: openShiftToken
|
|
5451
|
+
}
|
|
5452
|
+
};
|
|
5453
|
+
const tempHttp = createHttpClient(tempConfig);
|
|
5454
|
+
await tempHttp.openshift("/api/v1");
|
|
5455
|
+
process.stderr.write(" Connected.\n");
|
|
5456
|
+
} catch (err) {
|
|
5457
|
+
warn(`Could not connect to OpenShift: ${err instanceof Error ? err.message : String(err)}`);
|
|
5458
|
+
warn("Config will be saved anyway. Check your URL and token and re-run pncli config init or pncli config test.");
|
|
5459
|
+
}
|
|
5460
|
+
}
|
|
5461
|
+
}
|
|
5379
5462
|
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");
|
|
5380
5463
|
const jiraProject = await input({
|
|
5381
5464
|
message: "Default Jira project key (optional):",
|
|
@@ -5493,6 +5576,12 @@ async function initGlobalConfig(start) {
|
|
|
5493
5576
|
passcode: sonatypeIqPasscode || void 0
|
|
5494
5577
|
}
|
|
5495
5578
|
} : {},
|
|
5579
|
+
...useOpenShift && openShiftBaseUrl ? {
|
|
5580
|
+
openshift: {
|
|
5581
|
+
baseUrl: normalizeBaseUrl(openShiftBaseUrl),
|
|
5582
|
+
token: openShiftToken || void 0
|
|
5583
|
+
}
|
|
5584
|
+
} : {},
|
|
5496
5585
|
defaults: {
|
|
5497
5586
|
jira: {
|
|
5498
5587
|
project: jiraProject || void 0
|
|
@@ -6117,6 +6206,16 @@ function registerAdoWorkCommands(ado) {
|
|
|
6117
6206
|
fail(err, "ado", "work-list-attachments", start);
|
|
6118
6207
|
}
|
|
6119
6208
|
});
|
|
6209
|
+
work.command("add-attachment").description("Upload a local file and attach it to a work item").requiredOption("--id <n>", "Work item ID").requiredOption("--file <path>", "Path to the file to upload").option("--comment <text>", "Optional comment for the attachment").action(async (opts) => {
|
|
6210
|
+
const start = Date.now();
|
|
6211
|
+
try {
|
|
6212
|
+
const { collection, workClient } = getAdoContext(ado);
|
|
6213
|
+
const data = await workClient.uploadAttachment(collection, parseInt(opts.id, 10), opts.file, opts.comment);
|
|
6214
|
+
success(data, "ado", "work-add-attachment", start);
|
|
6215
|
+
} catch (err) {
|
|
6216
|
+
fail(err, "ado", "work-add-attachment", start);
|
|
6217
|
+
}
|
|
6218
|
+
});
|
|
6120
6219
|
work.command("download-attachment").description("Download a work item attachment to .pncli/ (or --dir)").requiredOption("--id <n>", "Work item ID").requiredOption("--attachment-id <guid>", "Attachment ID from work list-attachments output").option("--dir <path>", "Output directory (default: .pncli relative to cwd)").action(async (opts) => {
|
|
6121
6220
|
const start = Date.now();
|
|
6122
6221
|
try {
|
|
@@ -6455,11 +6554,11 @@ function registerAdoRepoCommands(ado) {
|
|
|
6455
6554
|
const start = Date.now();
|
|
6456
6555
|
try {
|
|
6457
6556
|
const globalOpts = ado.optsWithGlobals();
|
|
6458
|
-
const { loadConfig: lc } = await import("./config-
|
|
6557
|
+
const { loadConfig: lc } = await import("./config-TL5X47RY.js");
|
|
6459
6558
|
const config = lc({ configPath: globalOpts.config });
|
|
6460
6559
|
const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
|
|
6461
6560
|
const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
|
|
6462
|
-
const { createHttpClient: createHttpClient2 } = await import("./http-
|
|
6561
|
+
const { createHttpClient: createHttpClient2 } = await import("./http-UN6N4WIB.js");
|
|
6463
6562
|
const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
|
|
6464
6563
|
const coreClient = new AdoCoreClient2(http);
|
|
6465
6564
|
const me = await coreClient.getConnectionData(collection);
|
|
@@ -7967,6 +8066,262 @@ function copyPluginSkills(skillsSrc, targetDir) {
|
|
|
7967
8066
|
return { installed, failed };
|
|
7968
8067
|
}
|
|
7969
8068
|
|
|
8069
|
+
// src/services/jwt/commands.ts
|
|
8070
|
+
function base64urlDecode(input2) {
|
|
8071
|
+
const base64 = input2.replace(/-/g, "+").replace(/_/g, "/");
|
|
8072
|
+
const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
|
|
8073
|
+
return Buffer.from(padded, "base64").toString("utf8");
|
|
8074
|
+
}
|
|
8075
|
+
function decodeJwt(token) {
|
|
8076
|
+
const parts = token.split(".");
|
|
8077
|
+
if (parts.length !== 3) {
|
|
8078
|
+
throw new PncliError("Invalid JWT: expected 3 dot-separated parts", 1);
|
|
8079
|
+
}
|
|
8080
|
+
const [rawHeader, rawPayload, signature] = parts;
|
|
8081
|
+
let header;
|
|
8082
|
+
let payload;
|
|
8083
|
+
try {
|
|
8084
|
+
header = JSON.parse(base64urlDecode(rawHeader));
|
|
8085
|
+
} catch {
|
|
8086
|
+
throw new PncliError("Invalid JWT: header is not valid JSON", 1);
|
|
8087
|
+
}
|
|
8088
|
+
try {
|
|
8089
|
+
payload = JSON.parse(base64urlDecode(rawPayload));
|
|
8090
|
+
} catch {
|
|
8091
|
+
throw new PncliError("Invalid JWT: payload is not valid JSON", 1);
|
|
8092
|
+
}
|
|
8093
|
+
return { header, payload, signature };
|
|
8094
|
+
}
|
|
8095
|
+
function registerJwtCommands(program2) {
|
|
8096
|
+
const jwt = program2.command("jwt").description("JWT token utilities");
|
|
8097
|
+
jwt.command("decode <token>").description("Decode a JWT token and output header, payload, and signature as JSON").action((token) => {
|
|
8098
|
+
const start = Date.now();
|
|
8099
|
+
try {
|
|
8100
|
+
const data = decodeJwt(token);
|
|
8101
|
+
success(data, "jwt", "decode", start);
|
|
8102
|
+
} catch (err) {
|
|
8103
|
+
fail(err, "jwt", "decode", start);
|
|
8104
|
+
}
|
|
8105
|
+
});
|
|
8106
|
+
}
|
|
8107
|
+
|
|
8108
|
+
// src/services/openshift/commands.ts
|
|
8109
|
+
function containerState(s) {
|
|
8110
|
+
if (!s) return "unknown";
|
|
8111
|
+
if (s.waiting) return s.waiting.reason ?? "Waiting";
|
|
8112
|
+
if (s.terminated) return s.terminated.reason ?? "Terminated";
|
|
8113
|
+
if (s.running) return "Running";
|
|
8114
|
+
return "unknown";
|
|
8115
|
+
}
|
|
8116
|
+
function getHttp2(program2) {
|
|
8117
|
+
const opts = program2.optsWithGlobals();
|
|
8118
|
+
const config = loadConfig({ configPath: opts.config });
|
|
8119
|
+
if (!config.openshift.baseUrl) {
|
|
8120
|
+
throw new PncliError("OpenShift not configured. Run: pncli config init");
|
|
8121
|
+
}
|
|
8122
|
+
return createHttpClient(config, Boolean(opts.dryRun));
|
|
8123
|
+
}
|
|
8124
|
+
function podAge(creationTimestamp) {
|
|
8125
|
+
if (!creationTimestamp) return "unknown";
|
|
8126
|
+
const ageMs = Date.now() - new Date(creationTimestamp).getTime();
|
|
8127
|
+
const ageSeconds = Math.floor(ageMs / 1e3);
|
|
8128
|
+
if (ageSeconds < 60) return `${ageSeconds}s`;
|
|
8129
|
+
const ageMinutes = Math.floor(ageSeconds / 60);
|
|
8130
|
+
if (ageMinutes < 60) return `${ageMinutes}m`;
|
|
8131
|
+
const ageHours = Math.floor(ageMinutes / 60);
|
|
8132
|
+
if (ageHours < 24) return `${ageHours}h`;
|
|
8133
|
+
return `${Math.floor(ageHours / 24)}d`;
|
|
8134
|
+
}
|
|
8135
|
+
function registerOpenShiftCommands(program2) {
|
|
8136
|
+
const oc = program2.command("openshift").description("OpenShift / Kubernetes operations");
|
|
8137
|
+
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) => {
|
|
8138
|
+
const start = Date.now();
|
|
8139
|
+
try {
|
|
8140
|
+
const http = getHttp2(program2);
|
|
8141
|
+
const params = {};
|
|
8142
|
+
if (opts.labelSelector) params["labelSelector"] = opts.labelSelector;
|
|
8143
|
+
const podList = await http.openshift(
|
|
8144
|
+
`/api/v1/namespaces/${encodeURIComponent(opts.namespace)}/pods`,
|
|
8145
|
+
{ params }
|
|
8146
|
+
);
|
|
8147
|
+
const summaryCounters = {
|
|
8148
|
+
total: podList.items.length,
|
|
8149
|
+
running: 0,
|
|
8150
|
+
pending: 0,
|
|
8151
|
+
failed: 0,
|
|
8152
|
+
succeeded: 0,
|
|
8153
|
+
unknown: 0,
|
|
8154
|
+
crashLoopBackOff: 0,
|
|
8155
|
+
oomKilled: 0,
|
|
8156
|
+
imagePullBackOff: 0,
|
|
8157
|
+
totalRestarts: 0
|
|
8158
|
+
};
|
|
8159
|
+
const pods = podList.items.map((pod) => {
|
|
8160
|
+
const phase = pod.status.phase ?? "Unknown";
|
|
8161
|
+
switch (phase.toLowerCase()) {
|
|
8162
|
+
case "running":
|
|
8163
|
+
summaryCounters.running++;
|
|
8164
|
+
break;
|
|
8165
|
+
case "pending":
|
|
8166
|
+
summaryCounters.pending++;
|
|
8167
|
+
break;
|
|
8168
|
+
case "failed":
|
|
8169
|
+
summaryCounters.failed++;
|
|
8170
|
+
break;
|
|
8171
|
+
case "succeeded":
|
|
8172
|
+
summaryCounters.succeeded++;
|
|
8173
|
+
break;
|
|
8174
|
+
default:
|
|
8175
|
+
summaryCounters.unknown++;
|
|
8176
|
+
break;
|
|
8177
|
+
}
|
|
8178
|
+
const allStatuses = [
|
|
8179
|
+
...pod.status.containerStatuses ?? [],
|
|
8180
|
+
...pod.status.initContainerStatuses ?? []
|
|
8181
|
+
];
|
|
8182
|
+
let podRestarts = 0;
|
|
8183
|
+
const containers = allStatuses.map((cs) => {
|
|
8184
|
+
podRestarts += cs.restartCount;
|
|
8185
|
+
summaryCounters.totalRestarts += cs.restartCount;
|
|
8186
|
+
const currentState = containerState(cs.state);
|
|
8187
|
+
const lastState = containerState(cs.lastState);
|
|
8188
|
+
if (currentState === "CrashLoopBackOff") summaryCounters.crashLoopBackOff++;
|
|
8189
|
+
if (currentState === "ImagePullBackOff" || currentState === "ErrImagePull") summaryCounters.imagePullBackOff++;
|
|
8190
|
+
if (lastState === "OOMKilled" || cs.lastState?.terminated?.reason === "OOMKilled") summaryCounters.oomKilled++;
|
|
8191
|
+
const entry2 = {
|
|
8192
|
+
name: cs.name,
|
|
8193
|
+
ready: cs.ready,
|
|
8194
|
+
restartCount: cs.restartCount,
|
|
8195
|
+
state: currentState
|
|
8196
|
+
};
|
|
8197
|
+
if (cs.lastState?.terminated) {
|
|
8198
|
+
entry2["lastExitCode"] = cs.lastState.terminated.exitCode;
|
|
8199
|
+
entry2["lastReason"] = cs.lastState.terminated.reason;
|
|
8200
|
+
}
|
|
8201
|
+
return entry2;
|
|
8202
|
+
});
|
|
8203
|
+
const readyCount = allStatuses.filter((cs) => cs.ready).length;
|
|
8204
|
+
const totalCount = pod.spec.containers?.length ?? allStatuses.length;
|
|
8205
|
+
const entry = {
|
|
8206
|
+
name: pod.metadata.name,
|
|
8207
|
+
phase,
|
|
8208
|
+
ready: `${readyCount}/${totalCount}`,
|
|
8209
|
+
restartCount: podRestarts,
|
|
8210
|
+
age: podAge(pod.metadata.creationTimestamp)
|
|
8211
|
+
};
|
|
8212
|
+
const podReason = pod.status.reason ?? pod.status.conditions?.find((c) => c.status === "False")?.reason;
|
|
8213
|
+
if (podReason) entry["reason"] = podReason;
|
|
8214
|
+
if (pod.status.message) entry["message"] = pod.status.message;
|
|
8215
|
+
if (containers.length > 0) entry["containers"] = containers;
|
|
8216
|
+
if (pod.metadata.labels && Object.keys(pod.metadata.labels).length > 0) {
|
|
8217
|
+
entry["labels"] = pod.metadata.labels;
|
|
8218
|
+
}
|
|
8219
|
+
return entry;
|
|
8220
|
+
});
|
|
8221
|
+
pods.sort((a, b) => {
|
|
8222
|
+
const phasePriority = (p) => {
|
|
8223
|
+
if (p === "Failed") return 0;
|
|
8224
|
+
if (p === "Pending") return 1;
|
|
8225
|
+
if (p === "Unknown") return 2;
|
|
8226
|
+
return 3;
|
|
8227
|
+
};
|
|
8228
|
+
const pDiff = phasePriority(a["phase"]) - phasePriority(b["phase"]);
|
|
8229
|
+
if (pDiff !== 0) return pDiff;
|
|
8230
|
+
return (b["restartCount"] ?? 0) - (a["restartCount"] ?? 0);
|
|
8231
|
+
});
|
|
8232
|
+
success({ namespace: opts.namespace, summary: summaryCounters, pods }, "openshift", "pods", start);
|
|
8233
|
+
} catch (err) {
|
|
8234
|
+
fail(err, "openshift", "pods", start);
|
|
8235
|
+
}
|
|
8236
|
+
});
|
|
8237
|
+
oc.command("events").description("List Warning events for a namespace \u2014 pre-filtered for LLM analysis").requiredOption("--namespace <ns>", "Kubernetes namespace").option("--field-selector <selector>", "Additional field selector (e.g. involvedObject.name=my-pod)").option("--all", "Include Normal events in addition to Warning events", false).action(async (opts) => {
|
|
8238
|
+
const start = Date.now();
|
|
8239
|
+
try {
|
|
8240
|
+
const http = getHttp2(program2);
|
|
8241
|
+
const params = {};
|
|
8242
|
+
const selectors = [];
|
|
8243
|
+
if (!opts.all) selectors.push("type=Warning");
|
|
8244
|
+
if (opts.fieldSelector) selectors.push(opts.fieldSelector);
|
|
8245
|
+
if (selectors.length > 0) params["fieldSelector"] = selectors.join(",");
|
|
8246
|
+
const eventList = await http.openshift(
|
|
8247
|
+
`/api/v1/namespaces/${encodeURIComponent(opts.namespace)}/events`,
|
|
8248
|
+
{ params }
|
|
8249
|
+
);
|
|
8250
|
+
const events = eventList.items.map((ev) => {
|
|
8251
|
+
const obj = ev.involvedObject ?? ev.regarding;
|
|
8252
|
+
const entry = {
|
|
8253
|
+
type: ev.type ?? "Normal",
|
|
8254
|
+
reason: ev.reason,
|
|
8255
|
+
object: obj ? `${obj.kind ?? "Object"}/${obj.name ?? "?"}` : void 0,
|
|
8256
|
+
message: ev.message,
|
|
8257
|
+
count: ev.count ?? 1,
|
|
8258
|
+
firstTime: ev.firstTimestamp ?? ev.eventTime,
|
|
8259
|
+
lastTime: ev.lastTimestamp ?? ev.eventTime
|
|
8260
|
+
};
|
|
8261
|
+
const source = ev.source?.component ?? ev.reportingComponent;
|
|
8262
|
+
if (source) entry["source"] = source;
|
|
8263
|
+
return entry;
|
|
8264
|
+
});
|
|
8265
|
+
events.sort((a, b) => (b["count"] ?? 0) - (a["count"] ?? 0));
|
|
8266
|
+
success({ namespace: opts.namespace, count: events.length, events }, "openshift", "events", start);
|
|
8267
|
+
} catch (err) {
|
|
8268
|
+
fail(err, "openshift", "events", start);
|
|
8269
|
+
}
|
|
8270
|
+
});
|
|
8271
|
+
oc.command("logs").description("Get recent log lines from a pod container").requiredOption("--namespace <ns>", "Kubernetes namespace").requiredOption("--pod <name>", "Pod name").option("--container <name>", "Container name (defaults to first container)").option("--lines <n>", "Number of most-recent lines to return", "100").option("--previous", "Return logs from the previous container instance", false).action(async (opts) => {
|
|
8272
|
+
const start = Date.now();
|
|
8273
|
+
try {
|
|
8274
|
+
const http = getHttp2(program2);
|
|
8275
|
+
const lines = opts.lines ? parseInt(opts.lines, 10) : 100;
|
|
8276
|
+
const logText = await http.openshiftText(
|
|
8277
|
+
`/api/v1/namespaces/${encodeURIComponent(opts.namespace)}/pods/${encodeURIComponent(opts.pod)}/log`,
|
|
8278
|
+
{
|
|
8279
|
+
lines,
|
|
8280
|
+
params: {
|
|
8281
|
+
...opts.container ? { container: opts.container } : {},
|
|
8282
|
+
...opts.previous ? { previous: "true" } : {}
|
|
8283
|
+
}
|
|
8284
|
+
}
|
|
8285
|
+
);
|
|
8286
|
+
success(
|
|
8287
|
+
{
|
|
8288
|
+
pod: opts.pod,
|
|
8289
|
+
namespace: opts.namespace,
|
|
8290
|
+
container: opts.container ?? "(default)",
|
|
8291
|
+
previous: opts.previous ?? false,
|
|
8292
|
+
lines,
|
|
8293
|
+
log: logText
|
|
8294
|
+
},
|
|
8295
|
+
"openshift",
|
|
8296
|
+
"logs",
|
|
8297
|
+
start
|
|
8298
|
+
);
|
|
8299
|
+
} catch (err) {
|
|
8300
|
+
fail(err, "openshift", "logs", start);
|
|
8301
|
+
}
|
|
8302
|
+
});
|
|
8303
|
+
oc.command("pod-metrics").description("Get CPU and memory usage for pods in a namespace (requires metrics-server)").requiredOption("--namespace <ns>", "Kubernetes namespace").action(async (opts) => {
|
|
8304
|
+
const start = Date.now();
|
|
8305
|
+
try {
|
|
8306
|
+
const http = getHttp2(program2);
|
|
8307
|
+
const metricsList = await http.openshift(
|
|
8308
|
+
`/apis/metrics.k8s.io/v1beta1/namespaces/${encodeURIComponent(opts.namespace)}/pods`
|
|
8309
|
+
);
|
|
8310
|
+
const pods = metricsList.items.map((pm) => ({
|
|
8311
|
+
name: pm.metadata.name,
|
|
8312
|
+
containers: pm.containers.map((c) => ({
|
|
8313
|
+
name: c.name,
|
|
8314
|
+
cpu: c.usage.cpu,
|
|
8315
|
+
memory: c.usage.memory
|
|
8316
|
+
}))
|
|
8317
|
+
}));
|
|
8318
|
+
success({ namespace: opts.namespace, pods }, "openshift", "pod-metrics", start);
|
|
8319
|
+
} catch (err) {
|
|
8320
|
+
fail(err, "openshift", "pod-metrics", start);
|
|
8321
|
+
}
|
|
8322
|
+
});
|
|
8323
|
+
}
|
|
8324
|
+
|
|
7970
8325
|
// src/cli.ts
|
|
7971
8326
|
if (!process.env.PNCLI_VERIFY_TLS) {
|
|
7972
8327
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
@@ -8006,6 +8361,8 @@ registerServiceNowCommands(program);
|
|
|
8006
8361
|
registerContrastCommands(program);
|
|
8007
8362
|
registerSonatypeIqCommands(program);
|
|
8008
8363
|
registerSkillsCommands(program);
|
|
8364
|
+
registerJwtCommands(program);
|
|
8365
|
+
registerOpenShiftCommands(program);
|
|
8009
8366
|
program.addHelpText("after", `
|
|
8010
8367
|
Services:
|
|
8011
8368
|
git Local git operations (status, diff, log, branch)
|
|
@@ -8023,8 +8380,10 @@ Services:
|
|
|
8023
8380
|
servicenow ServiceNow (change requests)
|
|
8024
8381
|
contrast Contrast IAST (applications, vulnerability findings)
|
|
8025
8382
|
sonatypeiq Sonatype IQ Server (applications, organizations, policies)
|
|
8383
|
+
openshift OpenShift / Kubernetes (pods, events, logs, metrics)
|
|
8026
8384
|
config Manage pncli configuration
|
|
8027
8385
|
skills Download and manage Claude Code skills
|
|
8386
|
+
jwt JWT token utilities (decode header and payload)
|
|
8028
8387
|
`);
|
|
8029
8388
|
program.parseAsync(process.argv).catch((err) => {
|
|
8030
8389
|
if (process.exitCode !== void 0) return;
|