@kolatts/pncli 1.26.0 → 3.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/README.md +12 -9
- package/dist/chunk-HZF6WQPU.js +152 -0
- package/dist/chunk-HZF6WQPU.js.map +1 -0
- package/dist/{chunk-JOAWUILY.js → chunk-LH7WBP7W.js} +64 -21
- package/dist/chunk-LH7WBP7W.js.map +1 -0
- package/dist/{chunk-AGELHQKO.js → chunk-ZDJYOI3W.js} +103 -216
- package/dist/chunk-ZDJYOI3W.js.map +1 -0
- package/dist/cli.js +1494 -486
- package/dist/cli.js.map +1 -1
- package/dist/{config-SQ3YHJHI.js → config-JGBUUXQX.js} +4 -2
- package/dist/{http-WCDXXCKQ.js → http-6QJU2SWP.js} +3 -2
- package/dist/output-XT3WT5L5.js +24 -0
- package/dist/output-XT3WT5L5.js.map +1 -0
- package/package.json +3 -6
- package/skills/pncli/SKILL.md +41 -3
- package/skills/pncli/dynatrace.md +46 -0
- package/skills/pncli/figma.md +62 -0
- package/skills/pncli/jenkins.md +47 -0
- package/skills/pncli/marketplace.md +53 -2
- package/skills/pncli/openshift.md +80 -3
- package/skills/pncli/splitio.md +178 -0
- package/copilot-instructions.md +0 -1386
- package/dist/chunk-AGELHQKO.js.map +0 -1
- package/dist/chunk-JOAWUILY.js.map +0 -1
- package/skills/pncli/udeploy.md +0 -48
- /package/dist/{config-SQ3YHJHI.js.map → config-JGBUUXQX.js.map} +0 -0
- /package/dist/{http-WCDXXCKQ.js.map → http-6QJU2SWP.js.map} +0 -0
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-
|
|
12
|
+
} from "./chunk-LH7WBP7W.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-
|
|
26
|
+
} from "./chunk-HZF6WQPU.js";
|
|
24
27
|
import {
|
|
25
28
|
AdoCoreClient
|
|
26
29
|
} from "./chunk-VDL32PSG.js";
|
|
@@ -6311,16 +6314,6 @@ function registerConfigCommands(program2) {
|
|
|
6311
6314
|
} else {
|
|
6312
6315
|
results.artifactory = { ok: null, message: "not configured" };
|
|
6313
6316
|
}
|
|
6314
|
-
if (cfg.udeploy.baseUrl && (cfg.udeploy.pat || cfg.udeploy.username && cfg.udeploy.password)) {
|
|
6315
|
-
try {
|
|
6316
|
-
await http.udeploy("/cli/application");
|
|
6317
|
-
results.udeploy = { ok: true, message: "connected" };
|
|
6318
|
-
} catch (err) {
|
|
6319
|
-
results.udeploy = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
6320
|
-
}
|
|
6321
|
-
} else {
|
|
6322
|
-
results.udeploy = { ok: null, message: "not configured" };
|
|
6323
|
-
}
|
|
6324
6317
|
if (cfg.checkmarx.baseUrl && cfg.checkmarx.tenantName && (cfg.checkmarx.apiKey || cfg.checkmarx.clientId && cfg.checkmarx.clientSecret)) {
|
|
6325
6318
|
try {
|
|
6326
6319
|
await http.checkmarx("projects", { params: { limit: 1 } });
|
|
@@ -6372,6 +6365,25 @@ function registerConfigCommands(program2) {
|
|
|
6372
6365
|
} else {
|
|
6373
6366
|
results.openshift = { ok: null, message: "not configured" };
|
|
6374
6367
|
}
|
|
6368
|
+
for (const [envName, envCfg] of Object.entries(cfg.openshift.environments)) {
|
|
6369
|
+
for (const [instName, instCfg] of Object.entries(envCfg.instances ?? {})) {
|
|
6370
|
+
const key = `openshift:${envName}/${instName}`;
|
|
6371
|
+
if (instCfg.baseUrl && instCfg.token) {
|
|
6372
|
+
const clusterHttp = createHttpClient(
|
|
6373
|
+
{ ...cfg, openshift: { ...cfg.openshift, baseUrl: instCfg.baseUrl, token: instCfg.token } },
|
|
6374
|
+
false
|
|
6375
|
+
);
|
|
6376
|
+
try {
|
|
6377
|
+
await clusterHttp.openshift("/api/v1");
|
|
6378
|
+
results[key] = { ok: true, message: "connected" };
|
|
6379
|
+
} catch (err) {
|
|
6380
|
+
results[key] = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
6381
|
+
}
|
|
6382
|
+
} else {
|
|
6383
|
+
results[key] = { ok: null, message: "not configured" };
|
|
6384
|
+
}
|
|
6385
|
+
}
|
|
6386
|
+
}
|
|
6375
6387
|
if (cfg.dynatrace.baseUrl && cfg.dynatrace.apiToken) {
|
|
6376
6388
|
try {
|
|
6377
6389
|
await http.dynatrace("/api/v2/entities", {
|
|
@@ -6381,8 +6393,10 @@ function registerConfigCommands(program2) {
|
|
|
6381
6393
|
} catch (err) {
|
|
6382
6394
|
results.dynatrace = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
6383
6395
|
}
|
|
6384
|
-
} else {
|
|
6396
|
+
} else if (Object.keys(cfg.dynatrace.environments).length === 0) {
|
|
6385
6397
|
results.dynatrace = { ok: null, message: "not configured" };
|
|
6398
|
+
} else {
|
|
6399
|
+
results.dynatrace = { ok: null, message: "flat config not set \u2014 see named environments below" };
|
|
6386
6400
|
}
|
|
6387
6401
|
if (cfg.dynatrace.platformUrl && cfg.dynatrace.platformToken) {
|
|
6388
6402
|
try {
|
|
@@ -6403,6 +6417,46 @@ function registerConfigCommands(program2) {
|
|
|
6403
6417
|
} else {
|
|
6404
6418
|
results.dynatrace_platform = { ok: null, message: "not configured" };
|
|
6405
6419
|
}
|
|
6420
|
+
for (const [envName, envConfig] of Object.entries(cfg.dynatrace.environments)) {
|
|
6421
|
+
const envHttp = createHttpClient({
|
|
6422
|
+
...cfg,
|
|
6423
|
+
dynatrace: {
|
|
6424
|
+
...cfg.dynatrace,
|
|
6425
|
+
baseUrl: envConfig.baseUrl,
|
|
6426
|
+
apiToken: envConfig.apiToken,
|
|
6427
|
+
platformUrl: envConfig.platformUrl,
|
|
6428
|
+
platformToken: envConfig.platformToken
|
|
6429
|
+
}
|
|
6430
|
+
});
|
|
6431
|
+
const key = `dynatrace.${envName}`;
|
|
6432
|
+
if (envConfig.baseUrl && envConfig.apiToken) {
|
|
6433
|
+
try {
|
|
6434
|
+
await envHttp.dynatrace("/api/v2/entities", {
|
|
6435
|
+
params: { entitySelector: 'type("SERVICE")', pageSize: 1 }
|
|
6436
|
+
});
|
|
6437
|
+
results[key] = { ok: true, message: "connected" };
|
|
6438
|
+
} catch (err) {
|
|
6439
|
+
results[key] = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
6440
|
+
}
|
|
6441
|
+
} else {
|
|
6442
|
+
results[key] = { ok: null, message: "baseUrl or apiToken not configured" };
|
|
6443
|
+
}
|
|
6444
|
+
const platformKey = `dynatrace.${envName}_platform`;
|
|
6445
|
+
if (envConfig.platformUrl && envConfig.platformToken) {
|
|
6446
|
+
try {
|
|
6447
|
+
await envHttp.dynatracePlatform("/platform/storage/query/v1/query:execute", {
|
|
6448
|
+
method: "POST",
|
|
6449
|
+
body: { query: "fetch spans | limit 1", requestTimeoutMilliseconds: 5e3 },
|
|
6450
|
+
timeoutMs: 1e4
|
|
6451
|
+
});
|
|
6452
|
+
results[platformKey] = { ok: true, message: "connected" };
|
|
6453
|
+
} catch (err) {
|
|
6454
|
+
results[platformKey] = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
6455
|
+
}
|
|
6456
|
+
} else if (envConfig.platformUrl || envConfig.platformToken) {
|
|
6457
|
+
results[platformKey] = { ok: false, message: "platformUrl and platformToken must both be configured" };
|
|
6458
|
+
}
|
|
6459
|
+
}
|
|
6406
6460
|
if (cfg.logscale.baseUrl && cfg.logscale.token) {
|
|
6407
6461
|
try {
|
|
6408
6462
|
await http.logscale("/api/v1/repositories");
|
|
@@ -6413,6 +6467,26 @@ function registerConfigCommands(program2) {
|
|
|
6413
6467
|
} else {
|
|
6414
6468
|
results.logscale = { ok: null, message: "not configured" };
|
|
6415
6469
|
}
|
|
6470
|
+
if (cfg.splitio.baseUrl && cfg.splitio.adminApiKey) {
|
|
6471
|
+
try {
|
|
6472
|
+
await http.splitio("/internal/api/v2/workspaces");
|
|
6473
|
+
results.splitio = { ok: true, message: "connected" };
|
|
6474
|
+
} catch (err) {
|
|
6475
|
+
results.splitio = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
6476
|
+
}
|
|
6477
|
+
} else {
|
|
6478
|
+
results.splitio = { ok: null, message: "not configured" };
|
|
6479
|
+
}
|
|
6480
|
+
if (cfg.figma.baseUrl && cfg.figma.token) {
|
|
6481
|
+
try {
|
|
6482
|
+
await http.figma("/v1/me");
|
|
6483
|
+
results.figma = { ok: true, message: "connected" };
|
|
6484
|
+
} catch (err) {
|
|
6485
|
+
results.figma = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
6486
|
+
}
|
|
6487
|
+
} else {
|
|
6488
|
+
results.figma = { ok: null, message: "not configured" };
|
|
6489
|
+
}
|
|
6416
6490
|
success(results, "config", "test", start);
|
|
6417
6491
|
} catch (err) {
|
|
6418
6492
|
fail(err, "config", "test", start);
|
|
@@ -6537,18 +6611,6 @@ function registerConfigCommands(program2) {
|
|
|
6537
6611
|
results.jenkins = categorize2(err);
|
|
6538
6612
|
}
|
|
6539
6613
|
}
|
|
6540
|
-
if (!cfg.udeploy.pat && !(cfg.udeploy.username && cfg.udeploy.password)) {
|
|
6541
|
-
results.udeploy = { status: "blank", message: "not configured" };
|
|
6542
|
-
} else if (!cfg.udeploy.baseUrl) {
|
|
6543
|
-
results.udeploy = { status: "error", message: "baseUrl not configured" };
|
|
6544
|
-
} else {
|
|
6545
|
-
try {
|
|
6546
|
-
await http.udeploy("/cli/application", { timeoutMs: 1e4 });
|
|
6547
|
-
results.udeploy = { status: "valid", message: "ok" };
|
|
6548
|
-
} catch (err) {
|
|
6549
|
-
results.udeploy = categorize2(err);
|
|
6550
|
-
}
|
|
6551
|
-
}
|
|
6552
6614
|
const artResult = await checkArtifactoryConnectivity(cfg.artifactory);
|
|
6553
6615
|
if (!artResult.configured) {
|
|
6554
6616
|
results.artifactory = { status: "blank", message: artResult.error ?? "not configured" };
|
|
@@ -6620,8 +6682,33 @@ function registerConfigCommands(program2) {
|
|
|
6620
6682
|
results.openshift = categorize2(err);
|
|
6621
6683
|
}
|
|
6622
6684
|
}
|
|
6685
|
+
for (const [envName, envCfg] of Object.entries(cfg.openshift.environments)) {
|
|
6686
|
+
for (const [instName, instCfg] of Object.entries(envCfg.instances ?? {})) {
|
|
6687
|
+
const key = `openshift:${envName}/${instName}`;
|
|
6688
|
+
if (!instCfg.token) {
|
|
6689
|
+
results[key] = { status: "blank", message: "token not configured" };
|
|
6690
|
+
} else if (!instCfg.baseUrl) {
|
|
6691
|
+
results[key] = { status: "error", message: "baseUrl not configured" };
|
|
6692
|
+
} else {
|
|
6693
|
+
const clusterHttp = createHttpClient(
|
|
6694
|
+
{ ...cfg, openshift: { ...cfg.openshift, baseUrl: instCfg.baseUrl, token: instCfg.token } },
|
|
6695
|
+
false
|
|
6696
|
+
);
|
|
6697
|
+
try {
|
|
6698
|
+
await clusterHttp.openshift("/api/v1", { timeoutMs: 1e4 });
|
|
6699
|
+
results[key] = { status: "valid", message: "ok" };
|
|
6700
|
+
} catch (err) {
|
|
6701
|
+
results[key] = categorize2(err);
|
|
6702
|
+
}
|
|
6703
|
+
}
|
|
6704
|
+
}
|
|
6705
|
+
}
|
|
6623
6706
|
if (!cfg.dynatrace.apiToken) {
|
|
6624
|
-
|
|
6707
|
+
if (Object.keys(cfg.dynatrace.environments).length === 0) {
|
|
6708
|
+
results.dynatrace = { status: "blank", message: "not configured" };
|
|
6709
|
+
} else {
|
|
6710
|
+
results.dynatrace = { status: "blank", message: "flat config not set \u2014 see named environments below" };
|
|
6711
|
+
}
|
|
6625
6712
|
} else if (!cfg.dynatrace.baseUrl) {
|
|
6626
6713
|
results.dynatrace = { status: "error", message: "baseUrl not configured" };
|
|
6627
6714
|
} else {
|
|
@@ -6657,6 +6744,52 @@ function registerConfigCommands(program2) {
|
|
|
6657
6744
|
results.dynatrace_platform = categorize2(err);
|
|
6658
6745
|
}
|
|
6659
6746
|
}
|
|
6747
|
+
for (const [envName, envConfig] of Object.entries(cfg.dynatrace.environments)) {
|
|
6748
|
+
const envHttp = createHttpClient({
|
|
6749
|
+
...cfg,
|
|
6750
|
+
dynatrace: {
|
|
6751
|
+
...cfg.dynatrace,
|
|
6752
|
+
baseUrl: envConfig.baseUrl,
|
|
6753
|
+
apiToken: envConfig.apiToken,
|
|
6754
|
+
platformUrl: envConfig.platformUrl,
|
|
6755
|
+
platformToken: envConfig.platformToken
|
|
6756
|
+
}
|
|
6757
|
+
}, false);
|
|
6758
|
+
const key = `dynatrace.${envName}`;
|
|
6759
|
+
if (!envConfig.apiToken) {
|
|
6760
|
+
results[key] = { status: "blank", message: "apiToken not configured" };
|
|
6761
|
+
} else if (!envConfig.baseUrl) {
|
|
6762
|
+
results[key] = { status: "error", message: "baseUrl not configured" };
|
|
6763
|
+
} else {
|
|
6764
|
+
try {
|
|
6765
|
+
await envHttp.dynatrace("/api/v2/entities", {
|
|
6766
|
+
params: { entitySelector: 'type("SERVICE")', pageSize: 1 },
|
|
6767
|
+
timeoutMs: 1e4
|
|
6768
|
+
});
|
|
6769
|
+
results[key] = { status: "valid", message: "ok" };
|
|
6770
|
+
} catch (err) {
|
|
6771
|
+
results[key] = categorize2(err);
|
|
6772
|
+
}
|
|
6773
|
+
}
|
|
6774
|
+
const platformKey = `dynatrace.${envName}_platform`;
|
|
6775
|
+
if (envConfig.platformUrl && envConfig.platformToken) {
|
|
6776
|
+
try {
|
|
6777
|
+
await envHttp.dynatracePlatform("/platform/storage/query/v1/query:execute", {
|
|
6778
|
+
method: "POST",
|
|
6779
|
+
body: { query: "fetch spans | limit 1", requestTimeoutMilliseconds: 5e3 },
|
|
6780
|
+
timeoutMs: 1e4
|
|
6781
|
+
});
|
|
6782
|
+
results[platformKey] = { status: "valid", message: "ok" };
|
|
6783
|
+
} catch (err) {
|
|
6784
|
+
results[platformKey] = categorize2(err);
|
|
6785
|
+
}
|
|
6786
|
+
} else if (envConfig.platformUrl || envConfig.platformToken) {
|
|
6787
|
+
results[platformKey] = {
|
|
6788
|
+
status: "error",
|
|
6789
|
+
message: "platformUrl and platformToken must both be configured"
|
|
6790
|
+
};
|
|
6791
|
+
}
|
|
6792
|
+
}
|
|
6660
6793
|
if (!cfg.logscale.token) {
|
|
6661
6794
|
results.logscale = { status: "blank", message: "not configured" };
|
|
6662
6795
|
} else if (!cfg.logscale.baseUrl) {
|
|
@@ -6669,14 +6802,66 @@ function registerConfigCommands(program2) {
|
|
|
6669
6802
|
results.logscale = categorize2(err);
|
|
6670
6803
|
}
|
|
6671
6804
|
}
|
|
6805
|
+
if (!cfg.splitio.adminApiKey) {
|
|
6806
|
+
results.splitio = { status: "blank", message: "not configured" };
|
|
6807
|
+
} else if (!cfg.splitio.baseUrl) {
|
|
6808
|
+
results.splitio = { status: "error", message: "baseUrl not configured" };
|
|
6809
|
+
} else {
|
|
6810
|
+
try {
|
|
6811
|
+
await http.splitio("/internal/api/v2/workspaces", { timeoutMs: 1e4 });
|
|
6812
|
+
results.splitio = { status: "valid", message: "ok" };
|
|
6813
|
+
} catch (err) {
|
|
6814
|
+
results.splitio = categorize2(err);
|
|
6815
|
+
}
|
|
6816
|
+
}
|
|
6817
|
+
if (!cfg.figma.token) {
|
|
6818
|
+
results.figma = { status: "blank", message: "not configured" };
|
|
6819
|
+
} else if (!cfg.figma.baseUrl) {
|
|
6820
|
+
results.figma = { status: "error", message: "baseUrl not configured" };
|
|
6821
|
+
} else {
|
|
6822
|
+
try {
|
|
6823
|
+
await http.figma("/v1/me", { timeoutMs: 1e4 });
|
|
6824
|
+
results.figma = { status: "valid", message: "ok" };
|
|
6825
|
+
} catch (err) {
|
|
6826
|
+
results.figma = categorize2(err);
|
|
6827
|
+
}
|
|
6828
|
+
}
|
|
6672
6829
|
const statuses = Object.values(results).map((r) => r.status);
|
|
6673
6830
|
if (statuses.includes("invalid")) process.exitCode = ExitCode.AUTH_ERROR;
|
|
6674
6831
|
else if (statuses.includes("error")) process.exitCode = ExitCode.NETWORK_ERROR;
|
|
6832
|
+
const clusterKeys = Object.keys(results).filter((k) => k.startsWith("openshift:")).sort();
|
|
6833
|
+
const dynamicEnvKeys = Object.keys(cfg.dynatrace.environments).flatMap((envName) => {
|
|
6834
|
+
const keys = [`dynatrace.${envName}`];
|
|
6835
|
+
if (cfg.dynatrace.environments[envName]?.platformUrl) keys.push(`dynatrace.${envName}_platform`);
|
|
6836
|
+
return keys;
|
|
6837
|
+
});
|
|
6838
|
+
const allServices = [
|
|
6839
|
+
"jira",
|
|
6840
|
+
"bitbucket",
|
|
6841
|
+
"github",
|
|
6842
|
+
"confluence",
|
|
6843
|
+
"sonar",
|
|
6844
|
+
"sde",
|
|
6845
|
+
"ado",
|
|
6846
|
+
"jenkins",
|
|
6847
|
+
"artifactory",
|
|
6848
|
+
"checkmarx",
|
|
6849
|
+
"servicenow",
|
|
6850
|
+
"contrast",
|
|
6851
|
+
"sonatypeiq",
|
|
6852
|
+
"openshift",
|
|
6853
|
+
...clusterKeys,
|
|
6854
|
+
"dynatrace",
|
|
6855
|
+
"dynatrace_platform",
|
|
6856
|
+
...dynamicEnvKeys,
|
|
6857
|
+
"logscale",
|
|
6858
|
+
"splitio",
|
|
6859
|
+
"figma"
|
|
6860
|
+
];
|
|
6675
6861
|
if (cmdOpts.output === "table") {
|
|
6676
|
-
const
|
|
6677
|
-
const labelWidth = 14;
|
|
6862
|
+
const labelWidth = Math.max(14, ...allServices.map((s) => s.length + 2));
|
|
6678
6863
|
const statusWidth = 9;
|
|
6679
|
-
for (const svc of
|
|
6864
|
+
for (const svc of allServices) {
|
|
6680
6865
|
const r = results[svc];
|
|
6681
6866
|
if (!r) continue;
|
|
6682
6867
|
const label = svc.padEnd(labelWidth);
|
|
@@ -6687,10 +6872,9 @@ function registerConfigCommands(program2) {
|
|
|
6687
6872
|
process.stdout.write("\n");
|
|
6688
6873
|
} else {
|
|
6689
6874
|
if (opts.pretty) {
|
|
6690
|
-
const
|
|
6691
|
-
const labelWidth = 14;
|
|
6875
|
+
const labelWidth = Math.max(14, ...allServices.map((s) => s.length + 2));
|
|
6692
6876
|
const statusWidth = 9;
|
|
6693
|
-
for (const svc of
|
|
6877
|
+
for (const svc of allServices) {
|
|
6694
6878
|
const r = results[svc];
|
|
6695
6879
|
if (!r) continue;
|
|
6696
6880
|
const label = svc.padEnd(labelWidth);
|
|
@@ -6707,11 +6891,6 @@ function registerConfigCommands(program2) {
|
|
|
6707
6891
|
}
|
|
6708
6892
|
});
|
|
6709
6893
|
}
|
|
6710
|
-
function normalizeBaseUrl(raw) {
|
|
6711
|
-
const trimmed = raw.trim();
|
|
6712
|
-
if (!trimmed) return trimmed;
|
|
6713
|
-
return /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
6714
|
-
}
|
|
6715
6894
|
async function initGlobalConfig(start) {
|
|
6716
6895
|
process.stderr.write("pncli config init \u2014 Global configuration\n\n");
|
|
6717
6896
|
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");
|
|
@@ -6939,7 +7118,7 @@ async function initGlobalConfig(start) {
|
|
|
6939
7118
|
}
|
|
6940
7119
|
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");
|
|
6941
7120
|
const useJenkins = await confirm({
|
|
6942
|
-
message: "Configure Jenkins
|
|
7121
|
+
message: "Configure Jenkins for pipeline operations?",
|
|
6943
7122
|
default: false
|
|
6944
7123
|
});
|
|
6945
7124
|
let jenkinsBaseUrl = "";
|
|
@@ -6958,66 +7137,6 @@ async function initGlobalConfig(start) {
|
|
|
6958
7137
|
message: "Jenkins API token:"
|
|
6959
7138
|
});
|
|
6960
7139
|
}
|
|
6961
|
-
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");
|
|
6962
|
-
const useUdeploy = await confirm({
|
|
6963
|
-
message: "Configure IBM UrbanCode Deploy for component imports and deployment processes?",
|
|
6964
|
-
default: false
|
|
6965
|
-
});
|
|
6966
|
-
let udeployBaseUrl = "";
|
|
6967
|
-
let udeployPat = "";
|
|
6968
|
-
let udeployUsername = "";
|
|
6969
|
-
let udeployPassword = "";
|
|
6970
|
-
let udeployDefaultApp = "";
|
|
6971
|
-
let udeployDefaultEnv = "";
|
|
6972
|
-
if (useUdeploy) {
|
|
6973
|
-
udeployBaseUrl = await input({
|
|
6974
|
-
message: "UDeploy base URL (e.g. ucd.imagile.dev:8443):",
|
|
6975
|
-
default: ""
|
|
6976
|
-
});
|
|
6977
|
-
udeployUsername = await input({
|
|
6978
|
-
message: "UDeploy username (leave blank to use a PAT instead):",
|
|
6979
|
-
default: ""
|
|
6980
|
-
});
|
|
6981
|
-
if (udeployUsername) {
|
|
6982
|
-
udeployPassword = await password({
|
|
6983
|
-
message: "UDeploy password:",
|
|
6984
|
-
validate: (v) => v.length > 0 || "Password cannot be blank"
|
|
6985
|
-
});
|
|
6986
|
-
} else {
|
|
6987
|
-
udeployPat = await password({
|
|
6988
|
-
message: "UDeploy personal access token:"
|
|
6989
|
-
});
|
|
6990
|
-
}
|
|
6991
|
-
udeployDefaultApp = await input({
|
|
6992
|
-
message: "Default application name (optional):",
|
|
6993
|
-
default: ""
|
|
6994
|
-
});
|
|
6995
|
-
udeployDefaultEnv = await input({
|
|
6996
|
-
message: "Default environment name (optional):",
|
|
6997
|
-
default: ""
|
|
6998
|
-
});
|
|
6999
|
-
const hasCredentials = udeployBaseUrl && (udeployPat || udeployUsername && udeployPassword);
|
|
7000
|
-
if (hasCredentials) {
|
|
7001
|
-
process.stderr.write("\n Verifying connection...\n");
|
|
7002
|
-
try {
|
|
7003
|
-
const tempConfig = {
|
|
7004
|
-
...loadConfig(),
|
|
7005
|
-
udeploy: {
|
|
7006
|
-
baseUrl: normalizeBaseUrl(udeployBaseUrl),
|
|
7007
|
-
pat: udeployPat || void 0,
|
|
7008
|
-
username: udeployUsername || void 0,
|
|
7009
|
-
password: udeployPassword || void 0
|
|
7010
|
-
}
|
|
7011
|
-
};
|
|
7012
|
-
const tempHttp = createHttpClient(tempConfig);
|
|
7013
|
-
await tempHttp.udeploy("/cli/application");
|
|
7014
|
-
process.stderr.write(" Connected.\n");
|
|
7015
|
-
} catch (err) {
|
|
7016
|
-
warn(`Could not connect to UDeploy: ${err instanceof Error ? err.message : String(err)}`);
|
|
7017
|
-
warn("Config will be saved anyway. Check your URL and credentials and re-run pncli config init or pncli config test.");
|
|
7018
|
-
}
|
|
7019
|
-
}
|
|
7020
|
-
}
|
|
7021
7140
|
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");
|
|
7022
7141
|
const useCheckmarx = await confirm({
|
|
7023
7142
|
message: "Configure Checkmarx One for vulnerability scanning?",
|
|
@@ -7344,6 +7463,70 @@ async function initGlobalConfig(start) {
|
|
|
7344
7463
|
}
|
|
7345
7464
|
}
|
|
7346
7465
|
}
|
|
7466
|
+
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");
|
|
7467
|
+
const useSplitio = await confirm({
|
|
7468
|
+
message: "Configure Split.IO for feature flag administration?",
|
|
7469
|
+
default: false
|
|
7470
|
+
});
|
|
7471
|
+
let splitioBaseUrl = "";
|
|
7472
|
+
let splitioAdminApiKey = "";
|
|
7473
|
+
if (useSplitio) {
|
|
7474
|
+
splitioBaseUrl = await input({
|
|
7475
|
+
message: "Split.IO Admin API base URL (e.g. https://api.split.io):",
|
|
7476
|
+
default: ""
|
|
7477
|
+
});
|
|
7478
|
+
splitioAdminApiKey = await password({
|
|
7479
|
+
message: "Split.IO Admin API key:"
|
|
7480
|
+
});
|
|
7481
|
+
if (splitioBaseUrl && splitioAdminApiKey) {
|
|
7482
|
+
process.stderr.write("\n Verifying connection...\n");
|
|
7483
|
+
try {
|
|
7484
|
+
const tempConfig = {
|
|
7485
|
+
...loadConfig(),
|
|
7486
|
+
splitio: {
|
|
7487
|
+
baseUrl: normalizeBaseUrl(splitioBaseUrl),
|
|
7488
|
+
adminApiKey: splitioAdminApiKey
|
|
7489
|
+
}
|
|
7490
|
+
};
|
|
7491
|
+
const tempHttp = createHttpClient(tempConfig);
|
|
7492
|
+
await tempHttp.splitio("/internal/api/v2/workspaces");
|
|
7493
|
+
process.stderr.write(" Connected.\n");
|
|
7494
|
+
} catch (err) {
|
|
7495
|
+
warn(`Could not connect to Split.IO: ${err instanceof Error ? err.message : String(err)}`);
|
|
7496
|
+
warn("Config will be saved anyway. Check your URL and API key and re-run pncli config init or pncli config test.");
|
|
7497
|
+
}
|
|
7498
|
+
}
|
|
7499
|
+
}
|
|
7500
|
+
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");
|
|
7501
|
+
const useFigma = await confirm({
|
|
7502
|
+
message: "Configure Figma for reading design files and comments?",
|
|
7503
|
+
default: false
|
|
7504
|
+
});
|
|
7505
|
+
let figmaToken = "";
|
|
7506
|
+
if (useFigma) {
|
|
7507
|
+
process.stderr.write(" Generate a personal access token in Figma under Account Settings \u2192 Personal access tokens.\n");
|
|
7508
|
+
figmaToken = await password({
|
|
7509
|
+
message: "Figma personal access token:"
|
|
7510
|
+
});
|
|
7511
|
+
if (figmaToken) {
|
|
7512
|
+
process.stderr.write("\n Verifying connection...\n");
|
|
7513
|
+
try {
|
|
7514
|
+
const tempConfig = {
|
|
7515
|
+
...loadConfig(),
|
|
7516
|
+
figma: {
|
|
7517
|
+
baseUrl: "https://api.figma.com",
|
|
7518
|
+
token: figmaToken
|
|
7519
|
+
}
|
|
7520
|
+
};
|
|
7521
|
+
const tempHttp = createHttpClient(tempConfig);
|
|
7522
|
+
await tempHttp.figma("/v1/me");
|
|
7523
|
+
process.stderr.write(" Connected.\n");
|
|
7524
|
+
} catch (err) {
|
|
7525
|
+
warn(`Could not connect to Figma: ${err instanceof Error ? err.message : String(err)}`);
|
|
7526
|
+
warn("Config will be saved anyway. Check your token and re-run pncli config init or pncli config test.");
|
|
7527
|
+
}
|
|
7528
|
+
}
|
|
7529
|
+
}
|
|
7347
7530
|
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");
|
|
7348
7531
|
const jiraProject = await input({
|
|
7349
7532
|
message: "Default Jira project key (optional):",
|
|
@@ -7367,7 +7550,13 @@ async function initGlobalConfig(start) {
|
|
|
7367
7550
|
process.exitCode = ExitCode.SUCCESS;
|
|
7368
7551
|
return;
|
|
7369
7552
|
}
|
|
7553
|
+
const existingGlobal = loadJsonFile(getGlobalConfigPath());
|
|
7554
|
+
const existingInstances = Array.isArray(existingGlobal?.jenkinsInstances) ? existingGlobal.jenkinsInstances : [];
|
|
7555
|
+
const existingDynatraceEnvs = existingGlobal?.dynatrace?.environments;
|
|
7556
|
+
const existingDynatraceDefault = existingGlobal?.dynatrace?.defaultEnvironment;
|
|
7557
|
+
const existingOpenshift = existingGlobal?.openshift;
|
|
7370
7558
|
writeGlobalConfig({
|
|
7559
|
+
jenkinsInstances: existingInstances,
|
|
7371
7560
|
user: {
|
|
7372
7561
|
email: userEmail || void 0,
|
|
7373
7562
|
userId: userId || void 0
|
|
@@ -7428,14 +7617,6 @@ async function initGlobalConfig(start) {
|
|
|
7428
7617
|
...jenkinsApiToken ? { apiToken: jenkinsApiToken } : {}
|
|
7429
7618
|
}
|
|
7430
7619
|
} : {},
|
|
7431
|
-
...useUdeploy && udeployBaseUrl ? {
|
|
7432
|
-
udeploy: {
|
|
7433
|
-
baseUrl: normalizeBaseUrl(udeployBaseUrl),
|
|
7434
|
-
...udeployPat ? { pat: udeployPat } : {},
|
|
7435
|
-
...udeployUsername ? { username: udeployUsername } : {},
|
|
7436
|
-
...udeployPassword ? { password: udeployPassword } : {}
|
|
7437
|
-
}
|
|
7438
|
-
} : {},
|
|
7439
7620
|
...useCheckmarx && checkmarxBaseUrl ? {
|
|
7440
7621
|
checkmarx: {
|
|
7441
7622
|
baseUrl: normalizeBaseUrl(checkmarxBaseUrl),
|
|
@@ -7472,15 +7653,25 @@ async function initGlobalConfig(start) {
|
|
|
7472
7653
|
...useOpenShift && openShiftBaseUrl ? {
|
|
7473
7654
|
openshift: {
|
|
7474
7655
|
baseUrl: normalizeBaseUrl(openShiftBaseUrl),
|
|
7475
|
-
token: openShiftToken || void 0
|
|
7656
|
+
token: openShiftToken || void 0,
|
|
7657
|
+
...existingOpenshift?.defaultEnvironment ? { defaultEnvironment: existingOpenshift.defaultEnvironment } : {},
|
|
7658
|
+
...existingOpenshift?.defaultInstance ? { defaultInstance: existingOpenshift.defaultInstance } : {},
|
|
7659
|
+
...existingOpenshift?.environments ? { environments: existingOpenshift.environments } : {}
|
|
7476
7660
|
}
|
|
7477
|
-
} : {},
|
|
7661
|
+
} : existingOpenshift ? { openshift: { ...existingOpenshift } } : {},
|
|
7478
7662
|
...useDynatrace && dynatraceBaseUrl ? {
|
|
7479
7663
|
dynatrace: {
|
|
7480
7664
|
baseUrl: normalizeBaseUrl(dynatraceBaseUrl),
|
|
7481
7665
|
apiToken: dynatraceApiToken || void 0,
|
|
7482
7666
|
platformUrl: normalizeBaseUrl(dynatracePlatformUrl) || void 0,
|
|
7483
|
-
platformToken: dynatracePlatformToken || void 0
|
|
7667
|
+
platformToken: dynatracePlatformToken || void 0,
|
|
7668
|
+
...existingDynatraceDefault ? { defaultEnvironment: existingDynatraceDefault } : {},
|
|
7669
|
+
...existingDynatraceEnvs && Object.keys(existingDynatraceEnvs).length ? { environments: existingDynatraceEnvs } : {}
|
|
7670
|
+
}
|
|
7671
|
+
} : existingDynatraceEnvs && Object.keys(existingDynatraceEnvs).length ? {
|
|
7672
|
+
dynatrace: {
|
|
7673
|
+
...existingDynatraceDefault ? { defaultEnvironment: existingDynatraceDefault } : {},
|
|
7674
|
+
environments: existingDynatraceEnvs
|
|
7484
7675
|
}
|
|
7485
7676
|
} : {},
|
|
7486
7677
|
...useLogscale && logscaleBaseUrl ? {
|
|
@@ -7489,6 +7680,18 @@ async function initGlobalConfig(start) {
|
|
|
7489
7680
|
token: logscaleToken || void 0
|
|
7490
7681
|
}
|
|
7491
7682
|
} : {},
|
|
7683
|
+
...useSplitio && splitioBaseUrl ? {
|
|
7684
|
+
splitio: {
|
|
7685
|
+
baseUrl: normalizeBaseUrl(splitioBaseUrl),
|
|
7686
|
+
adminApiKey: splitioAdminApiKey || void 0
|
|
7687
|
+
}
|
|
7688
|
+
} : {},
|
|
7689
|
+
...useFigma && figmaToken ? {
|
|
7690
|
+
figma: {
|
|
7691
|
+
baseUrl: "https://api.figma.com",
|
|
7692
|
+
token: figmaToken
|
|
7693
|
+
}
|
|
7694
|
+
} : {},
|
|
7492
7695
|
defaults: {
|
|
7493
7696
|
jira: {
|
|
7494
7697
|
project: jiraProject || void 0
|
|
@@ -7508,12 +7711,6 @@ async function initGlobalConfig(start) {
|
|
|
7508
7711
|
collection: adoCollection || void 0,
|
|
7509
7712
|
project: adoProject || void 0
|
|
7510
7713
|
}
|
|
7511
|
-
} : {},
|
|
7512
|
-
...useUdeploy && (udeployDefaultApp || udeployDefaultEnv) ? {
|
|
7513
|
-
udeploy: {
|
|
7514
|
-
application: udeployDefaultApp || void 0,
|
|
7515
|
-
environment: udeployDefaultEnv || void 0
|
|
7516
|
-
}
|
|
7517
7714
|
} : {}
|
|
7518
7715
|
}
|
|
7519
7716
|
});
|
|
@@ -8245,10 +8442,10 @@ function registerAdoWorkCommands(ado) {
|
|
|
8245
8442
|
const types = await discoverTypes(workClient, collection, project);
|
|
8246
8443
|
if (opts.save) {
|
|
8247
8444
|
const globalConfigPath = getGlobalConfigPath(globalOpts.config);
|
|
8248
|
-
const
|
|
8249
|
-
const existing = JSON.parse(
|
|
8445
|
+
const fs6 = await import("fs");
|
|
8446
|
+
const existing = JSON.parse(fs6.readFileSync(globalConfigPath, "utf8") || "{}");
|
|
8250
8447
|
existing.ado = { ...existing.ado ?? {}, discoveredTypes: types };
|
|
8251
|
-
|
|
8448
|
+
fs6.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
|
|
8252
8449
|
warn(`Saved ${types.length} types to ${globalConfigPath}`);
|
|
8253
8450
|
}
|
|
8254
8451
|
success(types, "ado", "work-types", start);
|
|
@@ -8277,11 +8474,11 @@ function registerAdoWorkCommands(ado) {
|
|
|
8277
8474
|
) : allFields;
|
|
8278
8475
|
if (opts.save) {
|
|
8279
8476
|
const globalConfigPath = getGlobalConfigPath(globalOpts.config);
|
|
8280
|
-
const
|
|
8281
|
-
const existing = JSON.parse(
|
|
8477
|
+
const fs6 = await import("fs");
|
|
8478
|
+
const existing = JSON.parse(fs6.readFileSync(globalConfigPath, "utf8") || "{}");
|
|
8282
8479
|
const aliases = buildDefaultAliases(allFields);
|
|
8283
8480
|
existing.ado = { ...existing.ado ?? {}, discoveredFields: allFields, fieldAliases: aliases };
|
|
8284
|
-
|
|
8481
|
+
fs6.writeFileSync(globalConfigPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
|
|
8285
8482
|
warn(`Saved ${allFields.length} fields and ${Object.keys(aliases).length} aliases to ${globalConfigPath}`);
|
|
8286
8483
|
}
|
|
8287
8484
|
success(fields, "ado", "work-fields", start);
|
|
@@ -8578,11 +8775,11 @@ function registerAdoRepoCommands(ado) {
|
|
|
8578
8775
|
const start = Date.now();
|
|
8579
8776
|
try {
|
|
8580
8777
|
const globalOpts = ado.optsWithGlobals();
|
|
8581
|
-
const { loadConfig: lc } = await import("./config-
|
|
8778
|
+
const { loadConfig: lc } = await import("./config-JGBUUXQX.js");
|
|
8582
8779
|
const config = lc({ configPath: globalOpts.config });
|
|
8583
8780
|
const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
|
|
8584
8781
|
const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
|
|
8585
|
-
const { createHttpClient: createHttpClient2 } = await import("./http-
|
|
8782
|
+
const { createHttpClient: createHttpClient2 } = await import("./http-6QJU2SWP.js");
|
|
8586
8783
|
const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
|
|
8587
8784
|
const coreClient = new AdoCoreClient2(http);
|
|
8588
8785
|
const me = await coreClient.getConnectionData(collection);
|
|
@@ -8840,11 +9037,22 @@ var JenkinsClient = class {
|
|
|
8840
9037
|
};
|
|
8841
9038
|
|
|
8842
9039
|
// src/services/jenkins/commands.ts
|
|
8843
|
-
function getClient7(
|
|
8844
|
-
const opts =
|
|
9040
|
+
function getClient7(cmd) {
|
|
9041
|
+
const opts = cmd.optsWithGlobals();
|
|
8845
9042
|
const config = loadConfig({ configPath: opts.config });
|
|
8846
|
-
|
|
8847
|
-
|
|
9043
|
+
let jenkins = config.jenkins;
|
|
9044
|
+
if (opts.instance) {
|
|
9045
|
+
const inst = config.jenkinsInstances.find((i) => i.name === opts.instance);
|
|
9046
|
+
if (!inst) throw new PncliError(`Jenkins instance "${opts.instance}" not found. Add it to your global config using pncli config set jenkinsInstances.`);
|
|
9047
|
+
jenkins = { baseUrl: inst.baseUrl, username: inst.username, apiToken: inst.apiToken };
|
|
9048
|
+
}
|
|
9049
|
+
if (!jenkins.baseUrl) {
|
|
9050
|
+
if (opts.instance) {
|
|
9051
|
+
throw new PncliError(`Jenkins instance "${opts.instance}" has no baseUrl configured. Add a baseUrl to that entry in jenkinsInstances.`);
|
|
9052
|
+
}
|
|
9053
|
+
throw new PncliError("Jenkins not configured. Run: pncli config init");
|
|
9054
|
+
}
|
|
9055
|
+
const http = createHttpClient({ ...config, jenkins }, Boolean(opts.dryRun));
|
|
8848
9056
|
return new JenkinsClient(http);
|
|
8849
9057
|
}
|
|
8850
9058
|
async function pollQueueItem(client, queueItemId, pollMs, timeoutMs) {
|
|
@@ -8869,12 +9077,70 @@ async function pollBuildComplete(client, jobName, buildNumber, pollMs, timeoutMs
|
|
|
8869
9077
|
throw new PncliError(`Timed out waiting for build #${buildNumber} to complete after ${timeoutMs / 1e3}s`, 1);
|
|
8870
9078
|
}
|
|
8871
9079
|
function registerJenkinsCommands(program2) {
|
|
8872
|
-
const jenkins = program2.command("jenkins").description("Jenkins
|
|
9080
|
+
const jenkins = program2.command("jenkins").description("Jenkins operations").option("--instance <name>", "Named Jenkins instance from global config (overrides default jenkins config)");
|
|
8873
9081
|
const pipeline = jenkins.command("pipeline").description("Pipeline (job/build) operations");
|
|
9082
|
+
const instance = jenkins.command("instance").description("Manage named Jenkins instances in the global config");
|
|
9083
|
+
instance.command("list").description("List configured Jenkins instances (API tokens masked)").action(() => {
|
|
9084
|
+
const start = Date.now();
|
|
9085
|
+
try {
|
|
9086
|
+
const opts = jenkins.optsWithGlobals();
|
|
9087
|
+
const config = loadConfig({ configPath: opts.config });
|
|
9088
|
+
const instances = config.jenkinsInstances.map((i) => ({
|
|
9089
|
+
...i,
|
|
9090
|
+
apiToken: i.apiToken ? "***" : void 0
|
|
9091
|
+
}));
|
|
9092
|
+
success({ instances }, "jenkins", "instance-list", start);
|
|
9093
|
+
} catch (err) {
|
|
9094
|
+
fail(err, "jenkins", "instance-list", start);
|
|
9095
|
+
}
|
|
9096
|
+
});
|
|
9097
|
+
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) => {
|
|
9098
|
+
const start = Date.now();
|
|
9099
|
+
try {
|
|
9100
|
+
const opts = jenkins.optsWithGlobals();
|
|
9101
|
+
const configPath = opts.config;
|
|
9102
|
+
const existing = loadConfig({ configPath }).jenkinsInstances;
|
|
9103
|
+
const idx = existing.findIndex((i) => i.name === o.name);
|
|
9104
|
+
if (idx >= 0 && !o.force) {
|
|
9105
|
+
throw new PncliError(`Jenkins instance "${o.name}" already exists. Pass --force to overwrite it, or run: pncli jenkins instance remove --name ${o.name}`, 1);
|
|
9106
|
+
}
|
|
9107
|
+
let apiToken = o.apiToken;
|
|
9108
|
+
if (!apiToken && process.stdin.isTTY) {
|
|
9109
|
+
const { default: password2 } = await import("@inquirer/password");
|
|
9110
|
+
apiToken = await password2({ message: `API token for "${o.name}" (leave blank to skip):` });
|
|
9111
|
+
}
|
|
9112
|
+
const entry = {
|
|
9113
|
+
name: o.name,
|
|
9114
|
+
baseUrl: normalizeBaseUrl(o.baseUrl),
|
|
9115
|
+
...o.username ? { username: o.username } : {},
|
|
9116
|
+
...apiToken ? { apiToken } : {}
|
|
9117
|
+
};
|
|
9118
|
+
const next = idx >= 0 ? existing.map((i, n) => n === idx ? entry : i) : [...existing, entry];
|
|
9119
|
+
setConfigValue("jenkinsInstances", JSON.stringify(next), configPath);
|
|
9120
|
+
success({ name: entry.name, baseUrl: entry.baseUrl, overwritten: idx >= 0 }, "jenkins", "instance-add", start);
|
|
9121
|
+
} catch (err) {
|
|
9122
|
+
fail(err, "jenkins", "instance-add", start);
|
|
9123
|
+
}
|
|
9124
|
+
});
|
|
9125
|
+
instance.command("remove").description("Remove a named Jenkins instance from the global config").requiredOption("--name <name>", "Instance name to remove").action((o) => {
|
|
9126
|
+
const start = Date.now();
|
|
9127
|
+
try {
|
|
9128
|
+
const opts = jenkins.optsWithGlobals();
|
|
9129
|
+
const configPath = opts.config;
|
|
9130
|
+
const existing = loadConfig({ configPath }).jenkinsInstances;
|
|
9131
|
+
if (!existing.some((i) => i.name === o.name)) {
|
|
9132
|
+
throw new PncliError(`Jenkins instance "${o.name}" not found. Run: pncli jenkins instance list`, 1);
|
|
9133
|
+
}
|
|
9134
|
+
setConfigValue("jenkinsInstances", JSON.stringify(existing.filter((i) => i.name !== o.name)), configPath);
|
|
9135
|
+
success({ removed: o.name }, "jenkins", "instance-remove", start);
|
|
9136
|
+
} catch (err) {
|
|
9137
|
+
fail(err, "jenkins", "instance-remove", start);
|
|
9138
|
+
}
|
|
9139
|
+
});
|
|
8874
9140
|
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) => {
|
|
8875
9141
|
const start = Date.now();
|
|
8876
9142
|
try {
|
|
8877
|
-
const client = getClient7(
|
|
9143
|
+
const client = getClient7(jenkins);
|
|
8878
9144
|
const data = await client.listJobs(opts.folder);
|
|
8879
9145
|
success(data, "jenkins", "pipeline-list", start);
|
|
8880
9146
|
} catch (err) {
|
|
@@ -8884,7 +9150,7 @@ function registerJenkinsCommands(program2) {
|
|
|
8884
9150
|
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) => {
|
|
8885
9151
|
const start = Date.now();
|
|
8886
9152
|
try {
|
|
8887
|
-
const client = getClient7(
|
|
9153
|
+
const client = getClient7(jenkins);
|
|
8888
9154
|
const data = await client.getJob(opts.name);
|
|
8889
9155
|
success(data, "jenkins", "pipeline-get", start);
|
|
8890
9156
|
} catch (err) {
|
|
@@ -8897,7 +9163,7 @@ function registerJenkinsCommands(program2) {
|
|
|
8897
9163
|
}, []).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) => {
|
|
8898
9164
|
const start = Date.now();
|
|
8899
9165
|
try {
|
|
8900
|
-
const client = getClient7(
|
|
9166
|
+
const client = getClient7(jenkins);
|
|
8901
9167
|
for (const p of opts.parameter) {
|
|
8902
9168
|
if (!p.includes("=")) throw new PncliError(`Invalid --parameter "${p}". Expected format: key=value`, 1);
|
|
8903
9169
|
}
|
|
@@ -8930,7 +9196,7 @@ function registerJenkinsCommands(program2) {
|
|
|
8930
9196
|
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) => {
|
|
8931
9197
|
const start = Date.now();
|
|
8932
9198
|
try {
|
|
8933
|
-
const client = getClient7(
|
|
9199
|
+
const client = getClient7(jenkins);
|
|
8934
9200
|
const top = parseInt(opts.top, 10);
|
|
8935
9201
|
if (isNaN(top) || top <= 0) throw new PncliError(`Invalid --top "${opts.top}". Expected a positive integer.`, 1);
|
|
8936
9202
|
const data = await client.listBuilds(opts.name, top);
|
|
@@ -8942,7 +9208,7 @@ function registerJenkinsCommands(program2) {
|
|
|
8942
9208
|
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) => {
|
|
8943
9209
|
const start = Date.now();
|
|
8944
9210
|
try {
|
|
8945
|
-
const client = getClient7(
|
|
9211
|
+
const client = getClient7(jenkins);
|
|
8946
9212
|
const buildNumber = parseInt(opts.number, 10);
|
|
8947
9213
|
if (isNaN(buildNumber)) throw new PncliError(`Invalid --number "${opts.number}". Expected an integer.`, 1);
|
|
8948
9214
|
const data = await client.getBuild(opts.name, buildNumber);
|
|
@@ -8954,7 +9220,7 @@ function registerJenkinsCommands(program2) {
|
|
|
8954
9220
|
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) => {
|
|
8955
9221
|
const start = Date.now();
|
|
8956
9222
|
try {
|
|
8957
|
-
const client = getClient7(
|
|
9223
|
+
const client = getClient7(jenkins);
|
|
8958
9224
|
const buildNumber = parseInt(opts.number, 10);
|
|
8959
9225
|
if (isNaN(buildNumber)) throw new PncliError(`Invalid --number "${opts.number}". Expected an integer.`, 1);
|
|
8960
9226
|
const text = await client.getConsoleLog(opts.name, buildNumber);
|
|
@@ -9031,7 +9297,7 @@ var ArtifactoryClient = class {
|
|
|
9031
9297
|
}));
|
|
9032
9298
|
const result = { builds, uri: raw.uri };
|
|
9033
9299
|
if (builds.length === 0) {
|
|
9034
|
-
result.hint = 'No builds are registered in Artifactory Build Integration. Build names may differ from ADO pipeline
|
|
9300
|
+
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.';
|
|
9035
9301
|
}
|
|
9036
9302
|
return result;
|
|
9037
9303
|
}
|
|
@@ -9134,7 +9400,7 @@ function registerArtifactoryCommands(program2) {
|
|
|
9134
9400
|
fail(err, "artifactory", "search", start);
|
|
9135
9401
|
}
|
|
9136
9402
|
});
|
|
9137
|
-
art.command("builds").description('List builds registered in Artifactory Build Integration (names may differ from ADO pipeline
|
|
9403
|
+
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) => {
|
|
9138
9404
|
const start = Date.now();
|
|
9139
9405
|
try {
|
|
9140
9406
|
const client = getClient8(program2);
|
|
@@ -9169,337 +9435,117 @@ function registerArtifactoryCommands(program2) {
|
|
|
9169
9435
|
});
|
|
9170
9436
|
}
|
|
9171
9437
|
|
|
9172
|
-
// src/services/
|
|
9173
|
-
var
|
|
9438
|
+
// src/services/checkmarx/client.ts
|
|
9439
|
+
var CheckmarxClient = class {
|
|
9174
9440
|
constructor(http) {
|
|
9175
9441
|
this.http = http;
|
|
9176
9442
|
}
|
|
9177
9443
|
http;
|
|
9178
|
-
|
|
9179
|
-
|
|
9180
|
-
|
|
9181
|
-
|
|
9182
|
-
|
|
9183
|
-
|
|
9184
|
-
}
|
|
9185
|
-
|
|
9186
|
-
listComponents() {
|
|
9187
|
-
return this.http.udeploy("/cli/component");
|
|
9188
|
-
}
|
|
9189
|
-
listVersions(component) {
|
|
9190
|
-
return this.http.udeploy("/cli/component/versions", {
|
|
9191
|
-
params: { component }
|
|
9192
|
-
});
|
|
9193
|
-
}
|
|
9194
|
-
createVersion(component, name) {
|
|
9195
|
-
return this.http.udeploy("/cli/version/createVersion", {
|
|
9196
|
-
method: "POST",
|
|
9197
|
-
params: { component, name }
|
|
9198
|
-
});
|
|
9444
|
+
async listProjects() {
|
|
9445
|
+
const res = await this.http.checkmarx("projects", { params: { limit: 100 } });
|
|
9446
|
+
const projects = res.projects ?? [];
|
|
9447
|
+
if (res.filteredTotalCount > projects.length) {
|
|
9448
|
+
process.stderr.write(`warning: ${res.filteredTotalCount} projects found; only showing first ${projects.length}
|
|
9449
|
+
`);
|
|
9450
|
+
}
|
|
9451
|
+
return projects;
|
|
9199
9452
|
}
|
|
9200
|
-
|
|
9201
|
-
return this.http.
|
|
9202
|
-
method: "POST",
|
|
9203
|
-
params: { component, version }
|
|
9204
|
-
});
|
|
9453
|
+
async getProject(id) {
|
|
9454
|
+
return this.http.checkmarx(`projects/${id}`);
|
|
9205
9455
|
}
|
|
9206
|
-
|
|
9207
|
-
|
|
9208
|
-
|
|
9209
|
-
|
|
9210
|
-
|
|
9211
|
-
|
|
9212
|
-
|
|
9213
|
-
|
|
9214
|
-
|
|
9215
|
-
|
|
9216
|
-
...opts.properties ? { properties: opts.properties } : {}
|
|
9217
|
-
}
|
|
9218
|
-
});
|
|
9456
|
+
async listScans(opts = {}) {
|
|
9457
|
+
const params = { limit: opts.last ?? 100 };
|
|
9458
|
+
if (opts.projectId) params["project-id"] = opts.projectId;
|
|
9459
|
+
const res = await this.http.checkmarx("scans", { params });
|
|
9460
|
+
const scans = res.scans ?? [];
|
|
9461
|
+
if (res.filteredTotalCount > scans.length) {
|
|
9462
|
+
process.stderr.write(`warning: ${res.filteredTotalCount} scans found; only showing first ${scans.length}
|
|
9463
|
+
`);
|
|
9464
|
+
}
|
|
9465
|
+
return scans;
|
|
9219
9466
|
}
|
|
9220
|
-
|
|
9221
|
-
return this.http.
|
|
9222
|
-
params: { requestId }
|
|
9223
|
-
});
|
|
9467
|
+
async getScan(id) {
|
|
9468
|
+
return this.http.checkmarx(`scans/${id}`);
|
|
9224
9469
|
}
|
|
9225
|
-
|
|
9226
|
-
return this.http.
|
|
9227
|
-
params: {
|
|
9470
|
+
async getScanResultsStatistics(scanId) {
|
|
9471
|
+
return this.http.checkmarx("results/summary", {
|
|
9472
|
+
params: { "scan-id": scanId }
|
|
9228
9473
|
});
|
|
9229
9474
|
}
|
|
9230
9475
|
};
|
|
9231
9476
|
|
|
9232
|
-
// src/services/
|
|
9233
|
-
var POLL_INTERVAL_MS2 = 5e3;
|
|
9234
|
-
var DEFAULT_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
9477
|
+
// src/services/checkmarx/commands.ts
|
|
9235
9478
|
function getClient9(program2) {
|
|
9236
9479
|
const opts = program2.optsWithGlobals();
|
|
9237
9480
|
const config = loadConfig({ configPath: opts.config });
|
|
9238
|
-
if (!config.
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
const http = createHttpClient(config, opts.dryRun);
|
|
9242
|
-
return { client: new UdeployClient(http), config };
|
|
9243
|
-
}
|
|
9244
|
-
function resolveApplication(config, cliValue) {
|
|
9245
|
-
const app = cliValue ?? config.defaults.udeploy.application;
|
|
9246
|
-
if (!app) throw new PncliError("--application is required (or set defaults.udeploy.application in config)", 2);
|
|
9247
|
-
return app;
|
|
9248
|
-
}
|
|
9249
|
-
function resolveEnvironment(config, cliValue) {
|
|
9250
|
-
const env = cliValue ?? config.defaults.udeploy.environment;
|
|
9251
|
-
if (!env) throw new PncliError("--environment is required (or set defaults.udeploy.environment in config)", 2);
|
|
9252
|
-
return env;
|
|
9481
|
+
if (!config.checkmarx.baseUrl) throw new PncliError("Checkmarx not configured. Run: pncli config init");
|
|
9482
|
+
const http = createHttpClient(config, Boolean(opts.dryRun));
|
|
9483
|
+
return new CheckmarxClient(http);
|
|
9253
9484
|
}
|
|
9254
|
-
function
|
|
9255
|
-
const
|
|
9256
|
-
|
|
9485
|
+
function registerCheckmarxCommands(program2) {
|
|
9486
|
+
const cx = program2.command("checkmarx").description("Checkmarx One operations");
|
|
9487
|
+
const project = cx.command("project").description("Project operations");
|
|
9488
|
+
const scan = cx.command("scan").description("Scan operations");
|
|
9489
|
+
project.command("list").description("List all projects").action(async () => {
|
|
9257
9490
|
const start = Date.now();
|
|
9258
9491
|
try {
|
|
9259
|
-
const
|
|
9260
|
-
|
|
9261
|
-
success(apps, "udeploy", "apps", start);
|
|
9492
|
+
const data = await getClient9(program2).listProjects();
|
|
9493
|
+
success(data, "checkmarx", "project-list", start);
|
|
9262
9494
|
} catch (err) {
|
|
9263
|
-
fail(err, "
|
|
9495
|
+
fail(err, "checkmarx", "project-list", start);
|
|
9264
9496
|
}
|
|
9265
9497
|
});
|
|
9266
|
-
|
|
9498
|
+
project.command("get").description("Get a project by ID").requiredOption("--id <id>", "Project UUID").action(async (opts) => {
|
|
9267
9499
|
const start = Date.now();
|
|
9268
9500
|
try {
|
|
9269
|
-
const
|
|
9270
|
-
|
|
9271
|
-
const application = resolveApplication(config, parentOpts.application);
|
|
9272
|
-
const envs = await client.listEnvironments(application);
|
|
9273
|
-
success(envs, "udeploy", "environments", start);
|
|
9501
|
+
const data = await getClient9(program2).getProject(opts.id);
|
|
9502
|
+
success(data, "checkmarx", "project-get", start);
|
|
9274
9503
|
} catch (err) {
|
|
9275
|
-
fail(err, "
|
|
9504
|
+
fail(err, "checkmarx", "project-get", start);
|
|
9276
9505
|
}
|
|
9277
9506
|
});
|
|
9278
|
-
|
|
9507
|
+
scan.command("list").description("List scans").option("--project <id>", "Filter by project UUID").option("--last <n>", "Return only the last N scans").action(async (opts) => {
|
|
9279
9508
|
const start = Date.now();
|
|
9280
9509
|
try {
|
|
9281
|
-
const
|
|
9282
|
-
|
|
9283
|
-
|
|
9510
|
+
const last = opts.last ? parseInt(opts.last, 10) : void 0;
|
|
9511
|
+
if (opts.last && isNaN(last)) throw new PncliError(`Invalid --last "${opts.last}". Expected an integer.`, 1);
|
|
9512
|
+
const data = await getClient9(program2).listScans({ projectId: opts.project, last });
|
|
9513
|
+
success(data, "checkmarx", "scan-list", start);
|
|
9284
9514
|
} catch (err) {
|
|
9285
|
-
fail(err, "
|
|
9515
|
+
fail(err, "checkmarx", "scan-list", start);
|
|
9286
9516
|
}
|
|
9287
9517
|
});
|
|
9288
|
-
|
|
9518
|
+
scan.command("get").description("Get a scan by ID").requiredOption("--id <id>", "Scan UUID").action(async (opts) => {
|
|
9289
9519
|
const start = Date.now();
|
|
9290
9520
|
try {
|
|
9291
|
-
const
|
|
9292
|
-
|
|
9293
|
-
success(versions, "udeploy", "versions", start);
|
|
9521
|
+
const data = await getClient9(program2).getScan(opts.id);
|
|
9522
|
+
success(data, "checkmarx", "scan-get", start);
|
|
9294
9523
|
} catch (err) {
|
|
9295
|
-
fail(err, "
|
|
9524
|
+
fail(err, "checkmarx", "scan-get", start);
|
|
9296
9525
|
}
|
|
9297
9526
|
});
|
|
9298
|
-
|
|
9527
|
+
scan.command("stats").description("Get results statistics for a scan").requiredOption("--id <id>", "Scan UUID").action(async (opts) => {
|
|
9299
9528
|
const start = Date.now();
|
|
9300
9529
|
try {
|
|
9301
|
-
const
|
|
9302
|
-
|
|
9303
|
-
if (cmdOpts.finish) {
|
|
9304
|
-
await client.finishImporting(cmdOpts.component, cmdOpts.name);
|
|
9305
|
-
}
|
|
9306
|
-
success({ ...created, finishedImporting: cmdOpts.finish }, "udeploy", "import-version", start);
|
|
9530
|
+
const data = await getClient9(program2).getScanResultsStatistics(opts.id);
|
|
9531
|
+
success(data, "checkmarx", "scan-stats", start);
|
|
9307
9532
|
} catch (err) {
|
|
9308
|
-
fail(err, "
|
|
9533
|
+
fail(err, "checkmarx", "scan-stats", start);
|
|
9309
9534
|
}
|
|
9310
9535
|
});
|
|
9311
|
-
|
|
9312
|
-
|
|
9313
|
-
|
|
9314
|
-
|
|
9315
|
-
|
|
9316
|
-
|
|
9317
|
-
|
|
9318
|
-
|
|
9319
|
-
|
|
9320
|
-
|
|
9321
|
-
|
|
9322
|
-
|
|
9323
|
-
|
|
9324
|
-
const result = await client.runProcess({
|
|
9325
|
-
application,
|
|
9326
|
-
applicationProcess: cmdOpts.process,
|
|
9327
|
-
environment,
|
|
9328
|
-
onlyChanged: cmdOpts.onlyChanged,
|
|
9329
|
-
...versionEntries.length > 0 ? { versions: versionEntries } : {},
|
|
9330
|
-
...cmdOpts.snapshot ? { snapshot: cmdOpts.snapshot } : {}
|
|
9331
|
-
});
|
|
9332
|
-
if (!cmdOpts.wait) {
|
|
9333
|
-
success(result, "udeploy", "run", start);
|
|
9334
|
-
return;
|
|
9335
|
-
}
|
|
9336
|
-
const timeoutMs = parseInt(cmdOpts.timeout, 10);
|
|
9337
|
-
const requestId = result.requestId;
|
|
9338
|
-
log(`Process started. Request ID: ${requestId}. Polling for completion...`);
|
|
9339
|
-
while (true) {
|
|
9340
|
-
const elapsed = Date.now() - start;
|
|
9341
|
-
if (elapsed >= timeoutMs) {
|
|
9342
|
-
throw new PncliError(`Timed out after ${timeoutMs}ms waiting for request ${requestId}`, 1);
|
|
9343
|
-
}
|
|
9344
|
-
await sleep(POLL_INTERVAL_MS2);
|
|
9345
|
-
const status = await client.getRequestStatus(requestId);
|
|
9346
|
-
if (status.status === "CLOSED") {
|
|
9347
|
-
const outcome = { requestId, status: status.status, result: status.result, elapsed_ms: Date.now() - start };
|
|
9348
|
-
if (status.result === "SUCCEEDED") {
|
|
9349
|
-
success(outcome, "udeploy", "run", start);
|
|
9350
|
-
} else {
|
|
9351
|
-
throw new PncliError(
|
|
9352
|
-
`Deployment ${status.result ?? "failed"} (requestId: ${requestId})`,
|
|
9353
|
-
1
|
|
9354
|
-
);
|
|
9355
|
-
}
|
|
9356
|
-
return;
|
|
9357
|
-
}
|
|
9358
|
-
log(`Status: ${status.status} (${Math.round((Date.now() - start) / 1e3)}s elapsed)`);
|
|
9359
|
-
}
|
|
9360
|
-
} catch (err) {
|
|
9361
|
-
fail(err, "udeploy", "run", start);
|
|
9362
|
-
}
|
|
9363
|
-
});
|
|
9364
|
-
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) => {
|
|
9365
|
-
const start = Date.now();
|
|
9366
|
-
try {
|
|
9367
|
-
const { client } = getClient9(program2);
|
|
9368
|
-
const status = await client.getRequestStatus(cmdOpts.requestId);
|
|
9369
|
-
success(status, "udeploy", "request-status", start);
|
|
9370
|
-
} catch (err) {
|
|
9371
|
-
fail(err, "udeploy", "request-status", start);
|
|
9372
|
-
}
|
|
9373
|
-
});
|
|
9374
|
-
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) => {
|
|
9375
|
-
const start = Date.now();
|
|
9376
|
-
try {
|
|
9377
|
-
const { client } = getClient9(program2);
|
|
9378
|
-
const info = await client.getRequestInfo(cmdOpts.requestId);
|
|
9379
|
-
success(info, "udeploy", "request-info", start);
|
|
9380
|
-
} catch (err) {
|
|
9381
|
-
fail(err, "udeploy", "request-info", start);
|
|
9382
|
-
}
|
|
9383
|
-
});
|
|
9384
|
-
}
|
|
9385
|
-
function collect(val, prev) {
|
|
9386
|
-
return [...prev, val];
|
|
9387
|
-
}
|
|
9388
|
-
function sleep(ms) {
|
|
9389
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9390
|
-
}
|
|
9391
|
-
|
|
9392
|
-
// src/services/checkmarx/client.ts
|
|
9393
|
-
var CheckmarxClient = class {
|
|
9394
|
-
constructor(http) {
|
|
9395
|
-
this.http = http;
|
|
9396
|
-
}
|
|
9397
|
-
http;
|
|
9398
|
-
async listProjects() {
|
|
9399
|
-
const res = await this.http.checkmarx("projects", { params: { limit: 100 } });
|
|
9400
|
-
const projects = res.projects ?? [];
|
|
9401
|
-
if (res.filteredTotalCount > projects.length) {
|
|
9402
|
-
process.stderr.write(`warning: ${res.filteredTotalCount} projects found; only showing first ${projects.length}
|
|
9403
|
-
`);
|
|
9404
|
-
}
|
|
9405
|
-
return projects;
|
|
9406
|
-
}
|
|
9407
|
-
async getProject(id) {
|
|
9408
|
-
return this.http.checkmarx(`projects/${id}`);
|
|
9409
|
-
}
|
|
9410
|
-
async listScans(opts = {}) {
|
|
9411
|
-
const params = { limit: opts.last ?? 100 };
|
|
9412
|
-
if (opts.projectId) params["project-id"] = opts.projectId;
|
|
9413
|
-
const res = await this.http.checkmarx("scans", { params });
|
|
9414
|
-
const scans = res.scans ?? [];
|
|
9415
|
-
if (res.filteredTotalCount > scans.length) {
|
|
9416
|
-
process.stderr.write(`warning: ${res.filteredTotalCount} scans found; only showing first ${scans.length}
|
|
9417
|
-
`);
|
|
9418
|
-
}
|
|
9419
|
-
return scans;
|
|
9420
|
-
}
|
|
9421
|
-
async getScan(id) {
|
|
9422
|
-
return this.http.checkmarx(`scans/${id}`);
|
|
9423
|
-
}
|
|
9424
|
-
async getScanResultsStatistics(scanId) {
|
|
9425
|
-
return this.http.checkmarx("results/summary", {
|
|
9426
|
-
params: { "scan-id": scanId }
|
|
9427
|
-
});
|
|
9428
|
-
}
|
|
9429
|
-
};
|
|
9430
|
-
|
|
9431
|
-
// src/services/checkmarx/commands.ts
|
|
9432
|
-
function getClient10(program2) {
|
|
9433
|
-
const opts = program2.optsWithGlobals();
|
|
9434
|
-
const config = loadConfig({ configPath: opts.config });
|
|
9435
|
-
if (!config.checkmarx.baseUrl) throw new PncliError("Checkmarx not configured. Run: pncli config init");
|
|
9436
|
-
const http = createHttpClient(config, Boolean(opts.dryRun));
|
|
9437
|
-
return new CheckmarxClient(http);
|
|
9438
|
-
}
|
|
9439
|
-
function registerCheckmarxCommands(program2) {
|
|
9440
|
-
const cx = program2.command("checkmarx").description("Checkmarx One operations");
|
|
9441
|
-
const project = cx.command("project").description("Project operations");
|
|
9442
|
-
const scan = cx.command("scan").description("Scan operations");
|
|
9443
|
-
project.command("list").description("List all projects").action(async () => {
|
|
9444
|
-
const start = Date.now();
|
|
9445
|
-
try {
|
|
9446
|
-
const data = await getClient10(program2).listProjects();
|
|
9447
|
-
success(data, "checkmarx", "project-list", start);
|
|
9448
|
-
} catch (err) {
|
|
9449
|
-
fail(err, "checkmarx", "project-list", start);
|
|
9450
|
-
}
|
|
9451
|
-
});
|
|
9452
|
-
project.command("get").description("Get a project by ID").requiredOption("--id <id>", "Project UUID").action(async (opts) => {
|
|
9453
|
-
const start = Date.now();
|
|
9454
|
-
try {
|
|
9455
|
-
const data = await getClient10(program2).getProject(opts.id);
|
|
9456
|
-
success(data, "checkmarx", "project-get", start);
|
|
9457
|
-
} catch (err) {
|
|
9458
|
-
fail(err, "checkmarx", "project-get", start);
|
|
9459
|
-
}
|
|
9460
|
-
});
|
|
9461
|
-
scan.command("list").description("List scans").option("--project <id>", "Filter by project UUID").option("--last <n>", "Return only the last N scans").action(async (opts) => {
|
|
9462
|
-
const start = Date.now();
|
|
9463
|
-
try {
|
|
9464
|
-
const last = opts.last ? parseInt(opts.last, 10) : void 0;
|
|
9465
|
-
if (opts.last && isNaN(last)) throw new PncliError(`Invalid --last "${opts.last}". Expected an integer.`, 1);
|
|
9466
|
-
const data = await getClient10(program2).listScans({ projectId: opts.project, last });
|
|
9467
|
-
success(data, "checkmarx", "scan-list", start);
|
|
9468
|
-
} catch (err) {
|
|
9469
|
-
fail(err, "checkmarx", "scan-list", start);
|
|
9470
|
-
}
|
|
9471
|
-
});
|
|
9472
|
-
scan.command("get").description("Get a scan by ID").requiredOption("--id <id>", "Scan UUID").action(async (opts) => {
|
|
9473
|
-
const start = Date.now();
|
|
9474
|
-
try {
|
|
9475
|
-
const data = await getClient10(program2).getScan(opts.id);
|
|
9476
|
-
success(data, "checkmarx", "scan-get", start);
|
|
9477
|
-
} catch (err) {
|
|
9478
|
-
fail(err, "checkmarx", "scan-get", start);
|
|
9479
|
-
}
|
|
9480
|
-
});
|
|
9481
|
-
scan.command("stats").description("Get results statistics for a scan").requiredOption("--id <id>", "Scan UUID").action(async (opts) => {
|
|
9482
|
-
const start = Date.now();
|
|
9483
|
-
try {
|
|
9484
|
-
const data = await getClient10(program2).getScanResultsStatistics(opts.id);
|
|
9485
|
-
success(data, "checkmarx", "scan-stats", start);
|
|
9486
|
-
} catch (err) {
|
|
9487
|
-
fail(err, "checkmarx", "scan-stats", start);
|
|
9488
|
-
}
|
|
9489
|
-
});
|
|
9490
|
-
}
|
|
9491
|
-
|
|
9492
|
-
// src/services/servicenow/commands.ts
|
|
9493
|
-
function getHttp(program2) {
|
|
9494
|
-
const opts = program2.optsWithGlobals();
|
|
9495
|
-
const config = loadConfig({ configPath: opts.config });
|
|
9496
|
-
if (!config.servicenow.baseUrl) throw new PncliError("ServiceNow not configured. Run: pncli config init");
|
|
9497
|
-
return createHttpClient(config, Boolean(opts.dryRun));
|
|
9498
|
-
}
|
|
9499
|
-
function registerServiceNowCommands(program2) {
|
|
9500
|
-
const sn = program2.command("servicenow").description("ServiceNow operations");
|
|
9501
|
-
const change = sn.command("change").description("Change request operations");
|
|
9502
|
-
change.command("list").description("List change requests").option("--state <state>", "Filter by state (e.g. -1=Pending, 1=Open, 2=Work in Progress, 3=Closed Complete)").option("--assigned-to <user>", "Filter by assigned user sys_id or display name").option("--limit <n>", "Maximum number of results", "25").option("--fields <fields>", "Comma-separated field names to return").action(async (opts) => {
|
|
9536
|
+
}
|
|
9537
|
+
|
|
9538
|
+
// src/services/servicenow/commands.ts
|
|
9539
|
+
function getHttp(program2) {
|
|
9540
|
+
const opts = program2.optsWithGlobals();
|
|
9541
|
+
const config = loadConfig({ configPath: opts.config });
|
|
9542
|
+
if (!config.servicenow.baseUrl) throw new PncliError("ServiceNow not configured. Run: pncli config init");
|
|
9543
|
+
return createHttpClient(config, Boolean(opts.dryRun));
|
|
9544
|
+
}
|
|
9545
|
+
function registerServiceNowCommands(program2) {
|
|
9546
|
+
const sn = program2.command("servicenow").description("ServiceNow operations");
|
|
9547
|
+
const change = sn.command("change").description("Change request operations");
|
|
9548
|
+
change.command("list").description("List change requests").option("--state <state>", "Filter by state (e.g. -1=Pending, 1=Open, 2=Work in Progress, 3=Closed Complete)").option("--assigned-to <user>", "Filter by assigned user sys_id or display name").option("--limit <n>", "Maximum number of results", "25").option("--fields <fields>", "Comma-separated field names to return").action(async (opts) => {
|
|
9503
9549
|
const start = Date.now();
|
|
9504
9550
|
try {
|
|
9505
9551
|
const http = getHttp(program2);
|
|
@@ -9770,24 +9816,51 @@ function assertInteractive(hint) {
|
|
|
9770
9816
|
}
|
|
9771
9817
|
}
|
|
9772
9818
|
var AGENT_PATHS = {
|
|
9773
|
-
"
|
|
9819
|
+
"codex": { project: ".agents/skills", user: path8.join(os.homedir(), ".agents/skills") },
|
|
9820
|
+
"github-copilot": { project: ".github/skills", user: path8.join(os.homedir(), ".copilot/skills") },
|
|
9774
9821
|
"claude-code": { project: ".claude/skills", user: path8.join(os.homedir(), ".claude/skills") }
|
|
9775
9822
|
};
|
|
9823
|
+
var DEFAULT_AGENT = "codex";
|
|
9824
|
+
var AGENT_CHOICES = Object.keys(AGENT_PATHS).join(" | ");
|
|
9825
|
+
var warnedRetargetedAgent = false;
|
|
9826
|
+
function warnIfRetargetedAgent(agentName) {
|
|
9827
|
+
if (agentName !== "github-copilot" || warnedRetargetedAgent) return;
|
|
9828
|
+
warnedRetargetedAgent = true;
|
|
9829
|
+
warn(`--agent github-copilot now targets .github/skills (project) and ~/.copilot/skills (user). It previously meant .agents/skills \u2014 use --agent codex for that.`);
|
|
9830
|
+
}
|
|
9776
9831
|
function resolveAgentName(opts) {
|
|
9777
|
-
return opts.claude ? "claude-code" : opts.agent ??
|
|
9832
|
+
return opts.claude ? "claude-code" : opts.agent ?? DEFAULT_AGENT;
|
|
9778
9833
|
}
|
|
9779
9834
|
function resolveAgentPaths(agentName) {
|
|
9780
9835
|
const agentConfig = AGENT_PATHS[agentName];
|
|
9781
9836
|
if (!agentConfig) {
|
|
9782
|
-
throw new Error(`Unknown agent: "${agentName}". Use: ${
|
|
9837
|
+
throw new Error(`Unknown agent: "${agentName}". Use: ${AGENT_CHOICES}`);
|
|
9783
9838
|
}
|
|
9839
|
+
warnIfRetargetedAgent(agentName);
|
|
9784
9840
|
return agentConfig;
|
|
9785
9841
|
}
|
|
9842
|
+
function findGitRoot(from = process.cwd()) {
|
|
9843
|
+
try {
|
|
9844
|
+
const root = execFileSync3("git", ["-C", from, "rev-parse", "--show-toplevel"], {
|
|
9845
|
+
encoding: "utf8",
|
|
9846
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
9847
|
+
}).trim();
|
|
9848
|
+
return root ? path8.resolve(root) : null;
|
|
9849
|
+
} catch {
|
|
9850
|
+
return null;
|
|
9851
|
+
}
|
|
9852
|
+
}
|
|
9853
|
+
function resolveProjectPath(relativePath) {
|
|
9854
|
+
const root = findGitRoot();
|
|
9855
|
+
return root ? path8.resolve(root, relativePath) : path8.resolve(relativePath);
|
|
9856
|
+
}
|
|
9857
|
+
function resolveScopedPath(agentPaths, scope) {
|
|
9858
|
+
return scope === "project" ? resolveProjectPath(agentPaths.project) : path8.resolve(agentPaths.user);
|
|
9859
|
+
}
|
|
9786
9860
|
function resolveTargetDir(opts) {
|
|
9787
9861
|
if (opts.target) return path8.resolve(opts.target);
|
|
9788
9862
|
const agentConfig = resolveAgentPaths(resolveAgentName(opts));
|
|
9789
|
-
|
|
9790
|
-
return path8.resolve(scopePath);
|
|
9863
|
+
return resolveScopedPath(agentConfig, opts.scope ?? "user");
|
|
9791
9864
|
}
|
|
9792
9865
|
function getBundledSkillsDir() {
|
|
9793
9866
|
try {
|
|
@@ -9839,14 +9912,16 @@ function resolveSkillRecord(meta, targetDir, skillName) {
|
|
|
9839
9912
|
};
|
|
9840
9913
|
}
|
|
9841
9914
|
function listActiveSkillDirs(targetDir) {
|
|
9915
|
+
if (!fs4.existsSync(targetDir)) return [];
|
|
9842
9916
|
return fs4.readdirSync(targetDir).filter((name) => {
|
|
9843
9917
|
if (name.startsWith(".")) return false;
|
|
9844
|
-
const
|
|
9918
|
+
const dir = path8.join(targetDir, name);
|
|
9845
9919
|
try {
|
|
9846
|
-
|
|
9920
|
+
if (!fs4.statSync(dir).isDirectory()) return false;
|
|
9847
9921
|
} catch {
|
|
9848
9922
|
return false;
|
|
9849
9923
|
}
|
|
9924
|
+
return fs4.existsSync(path8.join(dir, "SKILL.md"));
|
|
9850
9925
|
});
|
|
9851
9926
|
}
|
|
9852
9927
|
function disablePluginSkills(targetDir, plugin, marketplaceFilter) {
|
|
@@ -9958,6 +10033,142 @@ function detectRepoBranch(repoPath) {
|
|
|
9958
10033
|
return void 0;
|
|
9959
10034
|
}
|
|
9960
10035
|
}
|
|
10036
|
+
function getRepoRemoteUrl(repoPath) {
|
|
10037
|
+
try {
|
|
10038
|
+
const raw = execFileSync3("git", ["-C", repoPath, "remote", "get-url", "origin"], {
|
|
10039
|
+
encoding: "utf8",
|
|
10040
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
10041
|
+
}).trim();
|
|
10042
|
+
return raw ? scrubToken(raw) : null;
|
|
10043
|
+
} catch {
|
|
10044
|
+
return null;
|
|
10045
|
+
}
|
|
10046
|
+
}
|
|
10047
|
+
function getInstalledPluginsForMarketplace(targetDir, marketplaceName, repoUrl) {
|
|
10048
|
+
const meta = readInstalledMeta(targetDir);
|
|
10049
|
+
const plugins = /* @__PURE__ */ new Set();
|
|
10050
|
+
for (const records of [meta.skills, meta.disabled ?? {}]) {
|
|
10051
|
+
for (const record of Object.values(records)) {
|
|
10052
|
+
if (record.source === "marketplace" && record.plugin) {
|
|
10053
|
+
if (record.marketplace === marketplaceName || repoUrl && record.installedFrom === repoUrl) {
|
|
10054
|
+
plugins.add(record.plugin);
|
|
10055
|
+
}
|
|
10056
|
+
}
|
|
10057
|
+
}
|
|
10058
|
+
}
|
|
10059
|
+
return [...plugins];
|
|
10060
|
+
}
|
|
10061
|
+
function readCustomTargets(globalConfig) {
|
|
10062
|
+
const raw = globalConfig.skillsTargets;
|
|
10063
|
+
if (!Array.isArray(raw)) return [];
|
|
10064
|
+
return [...new Set(raw.filter((t) => typeof t === "string" && t.length > 0).map((t) => path8.resolve(t)))];
|
|
10065
|
+
}
|
|
10066
|
+
function rememberCustomTarget(configPath, targetDir) {
|
|
10067
|
+
const resolved = path8.resolve(targetDir);
|
|
10068
|
+
const builtIn = Object.values(AGENT_PATHS).flatMap((a) => [path8.resolve(a.user), resolveProjectPath(a.project)]);
|
|
10069
|
+
if (builtIn.includes(resolved)) return false;
|
|
10070
|
+
const existing = loadJsonFile(configPath) ?? {};
|
|
10071
|
+
const targets = readCustomTargets(existing);
|
|
10072
|
+
if (targets.includes(resolved)) return false;
|
|
10073
|
+
writeGlobalConfig({ ...existing, skillsTargets: [...targets, resolved] }, configPath);
|
|
10074
|
+
return true;
|
|
10075
|
+
}
|
|
10076
|
+
function forgetCustomTarget(configPath, targetDir) {
|
|
10077
|
+
const resolved = path8.resolve(targetDir);
|
|
10078
|
+
const existing = loadJsonFile(configPath) ?? {};
|
|
10079
|
+
const targets = readCustomTargets(existing);
|
|
10080
|
+
if (!targets.includes(resolved)) return false;
|
|
10081
|
+
writeGlobalConfig({ ...existing, skillsTargets: targets.filter((t) => t !== resolved) }, configPath);
|
|
10082
|
+
return true;
|
|
10083
|
+
}
|
|
10084
|
+
function summarizeLocation(agent, scope, targetDir) {
|
|
10085
|
+
const resolved = path8.resolve(targetDir);
|
|
10086
|
+
const base = {
|
|
10087
|
+
agent,
|
|
10088
|
+
scope,
|
|
10089
|
+
path: resolved,
|
|
10090
|
+
exists: fs4.existsSync(resolved),
|
|
10091
|
+
totalSkills: 0,
|
|
10092
|
+
marketplaceSkills: 0,
|
|
10093
|
+
bundledSkills: 0,
|
|
10094
|
+
untrackedSkills: 0,
|
|
10095
|
+
disabledSkills: 0,
|
|
10096
|
+
disabledStashMissing: []
|
|
10097
|
+
};
|
|
10098
|
+
if (!base.exists) return base;
|
|
10099
|
+
const meta = readInstalledMeta(resolved);
|
|
10100
|
+
for (const skillName of listActiveSkillDirs(resolved)) {
|
|
10101
|
+
base.totalSkills++;
|
|
10102
|
+
const record = resolveSkillRecord(meta, resolved, skillName);
|
|
10103
|
+
if (record?.source === "marketplace") base.marketplaceSkills++;
|
|
10104
|
+
else if (record?.source === "bundled") base.bundledSkills++;
|
|
10105
|
+
else base.untrackedSkills++;
|
|
10106
|
+
}
|
|
10107
|
+
const disabledNames = Object.keys(meta.disabled ?? {});
|
|
10108
|
+
base.disabledSkills = disabledNames.length;
|
|
10109
|
+
const stashDir = path8.join(resolved, DISABLED_SUBDIR);
|
|
10110
|
+
base.disabledStashMissing = disabledNames.filter((name) => !fs4.existsSync(path8.join(stashDir, name)));
|
|
10111
|
+
return base;
|
|
10112
|
+
}
|
|
10113
|
+
function listKnownLocations(globalConfig) {
|
|
10114
|
+
const builtIn = Object.entries(AGENT_PATHS).flatMap(
|
|
10115
|
+
([agent, paths]) => ["project", "user"].map((scope) => summarizeLocation(agent, scope, resolveScopedPath(paths, scope)))
|
|
10116
|
+
);
|
|
10117
|
+
const seen = new Set(builtIn.map((l) => l.path));
|
|
10118
|
+
const custom = readCustomTargets(globalConfig).filter((dir) => !seen.has(dir)).map((dir) => summarizeLocation("custom", "custom", dir));
|
|
10119
|
+
return [...builtIn, ...custom];
|
|
10120
|
+
}
|
|
10121
|
+
function buildMarketplaceIndex(marketplaces) {
|
|
10122
|
+
const byName = /* @__PURE__ */ new Map();
|
|
10123
|
+
const byUrl = /* @__PURE__ */ new Map();
|
|
10124
|
+
for (const m of marketplaces) {
|
|
10125
|
+
const name = marketplaceLabel(m);
|
|
10126
|
+
if (name) byName.set(name, m);
|
|
10127
|
+
if (m.repoUrl) byUrl.set(m.repoUrl, m);
|
|
10128
|
+
}
|
|
10129
|
+
return { byName, byUrl };
|
|
10130
|
+
}
|
|
10131
|
+
function collectSkillStatus(globalConfig, locations) {
|
|
10132
|
+
const { byName, byUrl } = buildMarketplaceIndex(getAllMarketplaces(globalConfig));
|
|
10133
|
+
const remoteCache = /* @__PURE__ */ new Map();
|
|
10134
|
+
const remoteFor = (localPath) => {
|
|
10135
|
+
if (!localPath || !fs4.existsSync(localPath)) return null;
|
|
10136
|
+
if (!remoteCache.has(localPath)) remoteCache.set(localPath, getRepoRemoteUrl(localPath));
|
|
10137
|
+
return remoteCache.get(localPath) ?? null;
|
|
10138
|
+
};
|
|
10139
|
+
const records = [];
|
|
10140
|
+
for (const location of locations) {
|
|
10141
|
+
if (!location.exists) continue;
|
|
10142
|
+
const meta = readInstalledMeta(location.path);
|
|
10143
|
+
const push = (skillName, record, enabled) => {
|
|
10144
|
+
const recordedName = record?.marketplace ?? null;
|
|
10145
|
+
const registered = (recordedName ? byName.get(recordedName) : void 0) ?? (record?.installedFrom ? byUrl.get(record.installedFrom) : void 0) ?? null;
|
|
10146
|
+
records.push({
|
|
10147
|
+
skill: skillName,
|
|
10148
|
+
path: enabled ? path8.join(location.path, skillName) : path8.join(location.path, DISABLED_SUBDIR, skillName),
|
|
10149
|
+
agent: location.agent,
|
|
10150
|
+
scope: location.scope,
|
|
10151
|
+
location: location.path,
|
|
10152
|
+
enabled,
|
|
10153
|
+
source: record?.source ?? "untracked",
|
|
10154
|
+
plugin: record?.plugin ?? null,
|
|
10155
|
+
marketplace: recordedName ?? (registered ? marketplaceLabel(registered) : null),
|
|
10156
|
+
repoUrl: registered?.repoUrl ?? record?.installedFrom ?? null,
|
|
10157
|
+
localPath: registered?.localPath ?? null,
|
|
10158
|
+
upstreamRemote: remoteFor(registered?.localPath),
|
|
10159
|
+
branch: record?.branch ?? null,
|
|
10160
|
+
installedAt: record?.installedAt ?? null
|
|
10161
|
+
});
|
|
10162
|
+
};
|
|
10163
|
+
for (const skillName of listActiveSkillDirs(location.path)) {
|
|
10164
|
+
push(skillName, resolveSkillRecord(meta, location.path, skillName), true);
|
|
10165
|
+
}
|
|
10166
|
+
for (const [skillName, record] of Object.entries(meta.disabled ?? {})) {
|
|
10167
|
+
push(skillName, record, false);
|
|
10168
|
+
}
|
|
10169
|
+
}
|
|
10170
|
+
return records;
|
|
10171
|
+
}
|
|
9961
10172
|
function recordInstalledSkills(targetDir, skillNames, record) {
|
|
9962
10173
|
if (skillNames.length === 0) return;
|
|
9963
10174
|
const meta = readInstalledMeta(targetDir);
|
|
@@ -10121,7 +10332,7 @@ function installAllPlugins(resolvedPath, marketplaceName, url, targetDir) {
|
|
|
10121
10332
|
const { results, totalInstalled } = installPluginsForMarketplace(resolvedPath, marketplaceName, url, pluginChoices.map((p) => p.name), targetDir);
|
|
10122
10333
|
return { pluginResults: results, totalInstalled };
|
|
10123
10334
|
}
|
|
10124
|
-
function syncMarketplacePlugins(m, targetDir, force, pluginFilter) {
|
|
10335
|
+
function syncMarketplacePlugins(m, targetDir, force, pluginFilter, installedOnly = false) {
|
|
10125
10336
|
const marketplaceName = marketplaceLabel(m);
|
|
10126
10337
|
try {
|
|
10127
10338
|
const marketplacePath = m.localPath;
|
|
@@ -10140,7 +10351,16 @@ function syncMarketplacePlugins(m, targetDir, force, pluginFilter) {
|
|
|
10140
10351
|
}
|
|
10141
10352
|
let pluginNames;
|
|
10142
10353
|
if (pluginFilter === "all") {
|
|
10143
|
-
|
|
10354
|
+
if (installedOnly) {
|
|
10355
|
+
const installed = getInstalledPluginsForMarketplace(targetDir, marketplaceName, m.repoUrl).filter((name) => pluginChoices.some((p) => p.name === name));
|
|
10356
|
+
if (installed.length === 0) {
|
|
10357
|
+
warn(`No installed plugins found for "${marketplaceName}" \u2014 skipping. Run marketplace sync without --installed-only to install plugins.`);
|
|
10358
|
+
return { marketplace: marketplaceName, skipped: true, installedOnly: true, message: "No installed plugins found \u2014 nothing to sync." };
|
|
10359
|
+
}
|
|
10360
|
+
pluginNames = installed;
|
|
10361
|
+
} else {
|
|
10362
|
+
pluginNames = pluginChoices.map((p) => p.name);
|
|
10363
|
+
}
|
|
10144
10364
|
} else if (pluginChoices.some((p) => p.name === pluginFilter)) {
|
|
10145
10365
|
pluginNames = [pluginFilter];
|
|
10146
10366
|
} else {
|
|
@@ -10210,7 +10430,7 @@ async function marketplaceAddAction(url, localPath, opts, commandName) {
|
|
|
10210
10430
|
}
|
|
10211
10431
|
function registerSkillsCommands(program2) {
|
|
10212
10432
|
const skills = program2.command("skills").description("Manage pncli Claude Code skills");
|
|
10213
|
-
skills.command("install").description("Install pncli skills into the current repo").option("--agent <agent>",
|
|
10433
|
+
skills.command("install").description("Install pncli skills into the current repo").option("--agent <agent>", `Target agent host: ${AGENT_CHOICES}`, DEFAULT_AGENT).option("--scope <scope>", "Installation scope: project | user", "project").option("--target <dir>", "Override install directory (ignores --agent and --scope)").action((opts) => {
|
|
10214
10434
|
const start = Date.now();
|
|
10215
10435
|
try {
|
|
10216
10436
|
let targetDir;
|
|
@@ -10218,8 +10438,7 @@ function registerSkillsCommands(program2) {
|
|
|
10218
10438
|
targetDir = path8.resolve(opts.target);
|
|
10219
10439
|
} else {
|
|
10220
10440
|
const agentConfig = resolveAgentPaths(opts.agent);
|
|
10221
|
-
|
|
10222
|
-
targetDir = path8.resolve(scopePath);
|
|
10441
|
+
targetDir = resolveScopedPath(agentConfig, opts.scope === "user" ? "user" : "project");
|
|
10223
10442
|
}
|
|
10224
10443
|
const resolvedTarget = path8.resolve(targetDir);
|
|
10225
10444
|
const bundledDir = getBundledSkillsDir();
|
|
@@ -10268,6 +10487,14 @@ function registerSkillsCommands(program2) {
|
|
|
10268
10487
|
warn(`Failed to install: ${failed.join(", ")}`);
|
|
10269
10488
|
}
|
|
10270
10489
|
recordInstalledSkills(targetDir, installed, { source: "bundled" });
|
|
10490
|
+
let tracked = false;
|
|
10491
|
+
if (opts.target && installed.length > 0) {
|
|
10492
|
+
try {
|
|
10493
|
+
tracked = rememberCustomTarget(getGlobalConfigPath(), targetDir);
|
|
10494
|
+
} catch {
|
|
10495
|
+
warn(`Installed to ${targetDir}, but could not record it in the global config; it will not appear in "skills locations".`);
|
|
10496
|
+
}
|
|
10497
|
+
}
|
|
10271
10498
|
success({
|
|
10272
10499
|
installed,
|
|
10273
10500
|
failed,
|
|
@@ -10275,13 +10502,14 @@ function registerSkillsCommands(program2) {
|
|
|
10275
10502
|
total: installed.length,
|
|
10276
10503
|
agent: opts.target ? "custom" : opts.agent,
|
|
10277
10504
|
scope: opts.target ? "custom" : opts.scope,
|
|
10278
|
-
source: "bundled"
|
|
10505
|
+
source: "bundled",
|
|
10506
|
+
...opts.target ? { trackedAsCustomTarget: tracked } : {}
|
|
10279
10507
|
}, "skills", "install", start);
|
|
10280
10508
|
} catch (err) {
|
|
10281
10509
|
fail(err, "skills", "install", start);
|
|
10282
10510
|
}
|
|
10283
10511
|
});
|
|
10284
|
-
skills.command("list").description("List locally installed skills").option("--agent <agent>",
|
|
10512
|
+
skills.command("list").description("List locally installed skills").option("--agent <agent>", `Target agent host: ${AGENT_CHOICES}`, DEFAULT_AGENT).option("--scope <scope>", "Installation scope: project | user", "project").option("--target <dir>", "Override skills directory to scan").action((opts) => {
|
|
10285
10513
|
const start = Date.now();
|
|
10286
10514
|
try {
|
|
10287
10515
|
let targetDir;
|
|
@@ -10289,8 +10517,7 @@ function registerSkillsCommands(program2) {
|
|
|
10289
10517
|
targetDir = path8.resolve(opts.target);
|
|
10290
10518
|
} else {
|
|
10291
10519
|
const agentConfig = resolveAgentPaths(opts.agent);
|
|
10292
|
-
|
|
10293
|
-
targetDir = path8.resolve(scopePath);
|
|
10520
|
+
targetDir = resolveScopedPath(agentConfig, opts.scope === "user" ? "user" : "project");
|
|
10294
10521
|
}
|
|
10295
10522
|
if (!fs4.existsSync(targetDir)) {
|
|
10296
10523
|
success({ skills: [], message: `No skills directory found at ${targetDir}. Run: pncli skills install` }, "skills", "list", start);
|
|
@@ -10342,7 +10569,7 @@ function registerSkillsCommands(program2) {
|
|
|
10342
10569
|
fail(err, "skills", "list", start);
|
|
10343
10570
|
}
|
|
10344
10571
|
});
|
|
10345
|
-
skills.command("uninstall").description("Uninstall a skill (defaults to user scope, matching the marketplace install target; pass --scope project for skills installed via `skills install`)").argument("<name>", "Skill name to uninstall (the directory name under your skills folder)").option("--agent <agent>",
|
|
10572
|
+
skills.command("uninstall").description("Uninstall a skill (defaults to user scope, matching the marketplace install target; pass --scope project for skills installed via `skills install`)").argument("<name>", "Skill name to uninstall (the directory name under your skills folder)").option("--agent <agent>", `Target agent host: ${AGENT_CHOICES}`, DEFAULT_AGENT).option("--scope <scope>", "Installation scope to uninstall from: project | user", "user").option("--claude", "Shorthand for --agent claude-code").option("--target <dir>", "Override skills directory").action((name, opts) => {
|
|
10346
10573
|
const start = Date.now();
|
|
10347
10574
|
try {
|
|
10348
10575
|
let targetDir;
|
|
@@ -10350,8 +10577,7 @@ function registerSkillsCommands(program2) {
|
|
|
10350
10577
|
targetDir = path8.resolve(opts.target);
|
|
10351
10578
|
} else {
|
|
10352
10579
|
const agentConfig = resolveAgentPaths(resolveAgentName(opts));
|
|
10353
|
-
|
|
10354
|
-
targetDir = path8.resolve(scopePath);
|
|
10580
|
+
targetDir = resolveScopedPath(agentConfig, opts.scope === "project" ? "project" : "user");
|
|
10355
10581
|
}
|
|
10356
10582
|
const resolvedTarget = path8.resolve(targetDir);
|
|
10357
10583
|
const skillDir = path8.resolve(targetDir, name);
|
|
@@ -10376,9 +10602,77 @@ function registerSkillsCommands(program2) {
|
|
|
10376
10602
|
fail(err, "skills", "uninstall", start);
|
|
10377
10603
|
}
|
|
10378
10604
|
});
|
|
10605
|
+
skills.command("locations").description("List every skills install path pncli knows about, with per-directory skill counts").action(() => {
|
|
10606
|
+
const start = Date.now();
|
|
10607
|
+
try {
|
|
10608
|
+
const globalConfig = loadJsonFile(getGlobalConfigPath()) ?? {};
|
|
10609
|
+
const locations = listKnownLocations(globalConfig);
|
|
10610
|
+
const gitRoot = findGitRoot();
|
|
10611
|
+
success({
|
|
10612
|
+
locations,
|
|
10613
|
+
activeLocations: locations.filter((l) => l.exists).length,
|
|
10614
|
+
totalSkills: locations.reduce((sum, l) => sum + l.totalSkills, 0),
|
|
10615
|
+
defaultAgent: DEFAULT_AGENT,
|
|
10616
|
+
projectRoot: gitRoot
|
|
10617
|
+
}, "skills", "locations", start);
|
|
10618
|
+
} catch (err) {
|
|
10619
|
+
fail(err, "skills", "locations", start);
|
|
10620
|
+
}
|
|
10621
|
+
});
|
|
10622
|
+
skills.command("status").description("Show every installed skill with its plugin, marketplace, and upstream remote").option("--marketplace <name>", "Only show skills installed from this marketplace (name or clone URL)").option("--plugin <name>", "Only show skills belonging to this plugin").option("--source <source>", "Filter by provenance: marketplace | bundled | untracked").option("--agent <agent>", `Only show locations for this agent host: ${AGENT_CHOICES}`).option("--scope <scope>", "Only show this scope: project | user | custom").action((opts) => {
|
|
10623
|
+
const start = Date.now();
|
|
10624
|
+
try {
|
|
10625
|
+
if (opts.agent && !AGENT_PATHS[opts.agent]) {
|
|
10626
|
+
throw new Error(`Unknown agent: "${opts.agent}". Use: ${AGENT_CHOICES}`);
|
|
10627
|
+
}
|
|
10628
|
+
if (opts.source && !["marketplace", "bundled", "untracked"].includes(opts.source)) {
|
|
10629
|
+
throw new Error(`Unknown source: "${opts.source}". Use: marketplace | bundled | untracked`);
|
|
10630
|
+
}
|
|
10631
|
+
if (opts.scope && !["project", "user", "custom"].includes(opts.scope)) {
|
|
10632
|
+
throw new Error(`Unknown scope: "${opts.scope}". Use: project | user | custom`);
|
|
10633
|
+
}
|
|
10634
|
+
const globalConfig = loadJsonFile(getGlobalConfigPath()) ?? {};
|
|
10635
|
+
const locations = listKnownLocations(globalConfig).filter((l) => !opts.agent || l.agent === opts.agent).filter((l) => !opts.scope || l.scope === opts.scope);
|
|
10636
|
+
const skillRecords = collectSkillStatus(globalConfig, locations).filter((r) => !opts.plugin || r.plugin === opts.plugin).filter((r) => !opts.source || r.source === opts.source).filter((r) => !opts.marketplace || r.marketplace === opts.marketplace || r.repoUrl === opts.marketplace).sort((a, b) => a.location.localeCompare(b.location) || a.skill.localeCompare(b.skill));
|
|
10637
|
+
const byMarketplace = {};
|
|
10638
|
+
for (const record of skillRecords) {
|
|
10639
|
+
const key = record.marketplace ?? "(none)";
|
|
10640
|
+
byMarketplace[key] = (byMarketplace[key] ?? 0) + 1;
|
|
10641
|
+
}
|
|
10642
|
+
success({
|
|
10643
|
+
skills: skillRecords,
|
|
10644
|
+
total: skillRecords.length,
|
|
10645
|
+
enabled: skillRecords.filter((r) => r.enabled).length,
|
|
10646
|
+
disabled: skillRecords.filter((r) => !r.enabled).length,
|
|
10647
|
+
untracked: skillRecords.filter((r) => r.source === "untracked").length,
|
|
10648
|
+
byMarketplace,
|
|
10649
|
+
locations
|
|
10650
|
+
}, "skills", "status", start);
|
|
10651
|
+
} catch (err) {
|
|
10652
|
+
fail(err, "skills", "status", start);
|
|
10653
|
+
}
|
|
10654
|
+
});
|
|
10655
|
+
skills.command("forget-target").description("Stop tracking a custom --target directory in skills locations / status (does not delete any files)").argument("<dir>", "The custom install directory to forget").action((dir) => {
|
|
10656
|
+
const start = Date.now();
|
|
10657
|
+
try {
|
|
10658
|
+
const configPath = getGlobalConfigPath();
|
|
10659
|
+
const resolved = path8.resolve(dir);
|
|
10660
|
+
const forgotten = forgetCustomTarget(configPath, resolved);
|
|
10661
|
+
if (!forgotten) {
|
|
10662
|
+
warn(`${resolved} is not a tracked custom target \u2014 nothing to forget.`);
|
|
10663
|
+
}
|
|
10664
|
+
success({
|
|
10665
|
+
target: resolved,
|
|
10666
|
+
forgotten,
|
|
10667
|
+
remaining: readCustomTargets(loadJsonFile(configPath) ?? {})
|
|
10668
|
+
}, "skills", "forget-target", start);
|
|
10669
|
+
} catch (err) {
|
|
10670
|
+
fail(err, "skills", "forget-target", start);
|
|
10671
|
+
}
|
|
10672
|
+
});
|
|
10379
10673
|
const marketplace = skills.command("marketplace").description("Manage git-hosted skills marketplaces");
|
|
10380
10674
|
function withMarketplaceAddOptions(cmd) {
|
|
10381
|
-
return cmd.argument("<url>", "Git clone URL of the marketplace repository").argument("[localPath]", "Local directory to clone into (default: ~/.agents/marketplaces/<repo-name>)").option("--name <name>", "Human-readable name for this marketplace (default: derived from URL)").option("--branch <branch>", "Branch to clone (default: remote HEAD)").option("--token <token>", "HTTP access token for authenticated clone and pull (GitHub PAT or Bitbucket token)").option("--agent <agent>",
|
|
10675
|
+
return cmd.argument("<url>", "Git clone URL of the marketplace repository").argument("[localPath]", "Local directory to clone into (default: ~/.agents/marketplaces/<repo-name>)").option("--name <name>", "Human-readable name for this marketplace (default: derived from URL)").option("--branch <branch>", "Branch to clone (default: remote HEAD)").option("--token <token>", "HTTP access token for authenticated clone and pull (GitHub PAT or Bitbucket token)").option("--agent <agent>", `Target agent host for plugin install: ${AGENT_CHOICES} (default: ${DEFAULT_AGENT})`).option("--claude", "Shorthand for --agent claude-code");
|
|
10382
10676
|
}
|
|
10383
10677
|
withMarketplaceAddOptions(
|
|
10384
10678
|
marketplace.command("add").description("Register a new marketplace, clone it, and install all its plugins")
|
|
@@ -10396,7 +10690,8 @@ function registerSkillsCommands(program2) {
|
|
|
10396
10690
|
name: marketplaceLabel(m),
|
|
10397
10691
|
repoUrl: m.repoUrl,
|
|
10398
10692
|
localPath: m.localPath,
|
|
10399
|
-
tokenConfigured: !!m.token
|
|
10693
|
+
tokenConfigured: !!m.token,
|
|
10694
|
+
upstreamRemote: m.localPath && fs4.existsSync(m.localPath) ? getRepoRemoteUrl(m.localPath) : null
|
|
10400
10695
|
})),
|
|
10401
10696
|
total: all.length
|
|
10402
10697
|
}, "skills", "marketplace-list", start);
|
|
@@ -10434,7 +10729,7 @@ function registerSkillsCommands(program2) {
|
|
|
10434
10729
|
fail(err, "skills", "marketplace-remove", start);
|
|
10435
10730
|
}
|
|
10436
10731
|
});
|
|
10437
|
-
marketplace.command("sync").description("Pull latest marketplace content and install plugin skills (interactive picker when no plugin is given)").argument("[plugin]", 'Plugin name to install, or "all" to install every plugin (skips interactive selection)').option("--marketplace <name>", 'Marketplace name to sync, or "all" to sync every registered marketplace (skips interactive selection)').option("--agent <agent>",
|
|
10732
|
+
marketplace.command("sync").description("Pull latest marketplace content and install plugin skills (interactive picker when no plugin is given)").argument("[plugin]", 'Plugin name to install, or "all" to install every plugin (skips interactive selection)').option("--marketplace <name>", 'Marketplace name to sync, or "all" to sync every registered marketplace (skips interactive selection)').option("--agent <agent>", `Target agent host: ${AGENT_CHOICES} (default: ${DEFAULT_AGENT})`).option("--claude", "Shorthand for --agent claude-code").option("--force", 'Reinstall even if a marketplace has no new changes (applies to single-plugin and "all" installs alike)').option("--installed-only", "Only sync plugins that are already installed \u2014 skip plugins newly added to the marketplace").action(async (plugin, opts) => {
|
|
10438
10733
|
const start = Date.now();
|
|
10439
10734
|
try {
|
|
10440
10735
|
const configPath = getGlobalConfigPath();
|
|
@@ -10444,8 +10739,9 @@ function registerSkillsCommands(program2) {
|
|
|
10444
10739
|
}
|
|
10445
10740
|
const targetDir = resolveAgentPaths(resolveAgentName(opts)).user;
|
|
10446
10741
|
const force = opts.force ?? false;
|
|
10742
|
+
const installedOnly = opts.installedOnly ?? false;
|
|
10447
10743
|
if (opts.marketplace === "all") {
|
|
10448
|
-
const results = allMarketplaces.map((m) => syncMarketplacePlugins(m, targetDir, force, plugin ?? "all"));
|
|
10744
|
+
const results = allMarketplaces.map((m) => syncMarketplacePlugins(m, targetDir, force, plugin ?? "all", installedOnly));
|
|
10449
10745
|
success({ allMarketplaces: true, marketplaces: results, target: targetDir }, "skills", "marketplace-sync", start);
|
|
10450
10746
|
return;
|
|
10451
10747
|
}
|
|
@@ -10473,7 +10769,7 @@ function registerSkillsCommands(program2) {
|
|
|
10473
10769
|
]
|
|
10474
10770
|
});
|
|
10475
10771
|
if (chosen === ALL_MARKETPLACES) {
|
|
10476
|
-
const results2 = allMarketplaces.map((m) => syncMarketplacePlugins(m, targetDir, force, selectedPlugin ?? "all"));
|
|
10772
|
+
const results2 = allMarketplaces.map((m) => syncMarketplacePlugins(m, targetDir, force, selectedPlugin ?? "all", installedOnly));
|
|
10477
10773
|
success({ allMarketplaces: true, marketplaces: results2, target: targetDir }, "skills", "marketplace-sync", start);
|
|
10478
10774
|
return;
|
|
10479
10775
|
}
|
|
@@ -10520,10 +10816,29 @@ function registerSkillsCommands(program2) {
|
|
|
10520
10816
|
}, "skills", "marketplace-sync", start);
|
|
10521
10817
|
return;
|
|
10522
10818
|
}
|
|
10523
|
-
|
|
10819
|
+
let pluginNames;
|
|
10820
|
+
if (selectedPlugin === "all") {
|
|
10821
|
+
if (installedOnly) {
|
|
10822
|
+
const installed = getInstalledPluginsForMarketplace(targetDir, marketplaceName, selectedMarketplace.repoUrl).filter((name) => pluginChoices.some((p) => p.name === name));
|
|
10823
|
+
if (installed.length === 0) {
|
|
10824
|
+
success({
|
|
10825
|
+
marketplace: marketplaceName,
|
|
10826
|
+
skipped: true,
|
|
10827
|
+
installedOnly: true,
|
|
10828
|
+
message: `No installed plugins found for "${marketplaceName}" \u2014 run marketplace sync without --installed-only to install plugins.`
|
|
10829
|
+
}, "skills", "marketplace-sync", start);
|
|
10830
|
+
return;
|
|
10831
|
+
}
|
|
10832
|
+
pluginNames = installed;
|
|
10833
|
+
} else {
|
|
10834
|
+
pluginNames = pluginChoices.map((p) => p.name);
|
|
10835
|
+
}
|
|
10836
|
+
} else {
|
|
10837
|
+
pluginNames = [selectedPlugin];
|
|
10838
|
+
}
|
|
10524
10839
|
const { results, totalInstalled } = installPluginsForMarketplace(marketplacePath, marketplaceName, selectedMarketplace.repoUrl, pluginNames, targetDir);
|
|
10525
10840
|
if (selectedPlugin === "all") {
|
|
10526
|
-
success({ marketplace: marketplaceName, plugins: results, total: totalInstalled, target: targetDir, marketplaceUpdated: updated }, "skills", "marketplace-sync", start);
|
|
10841
|
+
success({ marketplace: marketplaceName, plugins: results, total: totalInstalled, target: targetDir, marketplaceUpdated: updated, ...installedOnly ? { installedOnly: true } : {} }, "skills", "marketplace-sync", start);
|
|
10527
10842
|
} else {
|
|
10528
10843
|
const single = results[selectedPlugin] ?? { installed: [], failed: [] };
|
|
10529
10844
|
success({
|
|
@@ -10542,7 +10857,7 @@ function registerSkillsCommands(program2) {
|
|
|
10542
10857
|
fail(err, "skills", "marketplace-sync", start);
|
|
10543
10858
|
}
|
|
10544
10859
|
});
|
|
10545
|
-
marketplace.command("purge-plugin").description("Remove all skills installed from a specific plugin (or all plugins in a marketplace)").argument("<plugin>", 'Plugin name to purge, or "all" to remove every skill from the marketplace').option("--marketplace <name>", "Restrict purge to skills from this marketplace (by name or repo URL)").option("--agent <agent>",
|
|
10860
|
+
marketplace.command("purge-plugin").description("Remove all skills installed from a specific plugin (or all plugins in a marketplace)").argument("<plugin>", 'Plugin name to purge, or "all" to remove every skill from the marketplace').option("--marketplace <name>", "Restrict purge to skills from this marketplace (by name or repo URL)").option("--agent <agent>", `Target agent host: ${AGENT_CHOICES} (default: ${DEFAULT_AGENT})`).option("--claude", "Shorthand for --agent claude-code").option("--scope <scope>", "Installation scope: project | user (default: user)").option("--target <dir>", "Override skills directory").action((plugin, opts) => {
|
|
10546
10861
|
const start = Date.now();
|
|
10547
10862
|
try {
|
|
10548
10863
|
let targetDir;
|
|
@@ -10550,8 +10865,7 @@ function registerSkillsCommands(program2) {
|
|
|
10550
10865
|
targetDir = path8.resolve(opts.target);
|
|
10551
10866
|
} else {
|
|
10552
10867
|
const agentConfig = resolveAgentPaths(resolveAgentName(opts));
|
|
10553
|
-
|
|
10554
|
-
targetDir = path8.resolve(scopePath);
|
|
10868
|
+
targetDir = resolveScopedPath(agentConfig, opts.scope ?? "user");
|
|
10555
10869
|
}
|
|
10556
10870
|
const resolvedTarget = path8.resolve(targetDir);
|
|
10557
10871
|
if (!fs4.existsSync(targetDir)) {
|
|
@@ -10616,7 +10930,7 @@ function registerSkillsCommands(program2) {
|
|
|
10616
10930
|
fail(err, "skills", "marketplace-purge-plugin", start);
|
|
10617
10931
|
}
|
|
10618
10932
|
});
|
|
10619
|
-
marketplace.command("disable").description("Temporarily deactivate a plugin's skills without deleting them (re-enable with `marketplace enable`)").argument("<plugin>", "Plugin name to disable").option("--marketplace <name>", "Restrict to skills from this marketplace (by name or repo URL)").option("--agent <agent>",
|
|
10933
|
+
marketplace.command("disable").description("Temporarily deactivate a plugin's skills without deleting them (re-enable with `marketplace enable`)").argument("<plugin>", "Plugin name to disable").option("--marketplace <name>", "Restrict to skills from this marketplace (by name or repo URL)").option("--agent <agent>", `Target agent host: ${AGENT_CHOICES} (default: ${DEFAULT_AGENT})`).option("--claude", "Shorthand for --agent claude-code").option("--scope <scope>", "Installation scope: project | user (default: user)").option("--target <dir>", "Override skills directory").action((plugin, opts) => {
|
|
10620
10934
|
const start = Date.now();
|
|
10621
10935
|
try {
|
|
10622
10936
|
const targetDir = resolveTargetDir(opts);
|
|
@@ -10639,7 +10953,7 @@ function registerSkillsCommands(program2) {
|
|
|
10639
10953
|
fail(err, "skills", "marketplace-disable", start);
|
|
10640
10954
|
}
|
|
10641
10955
|
});
|
|
10642
|
-
marketplace.command("enable").description("Re-activate a previously disabled plugin's skills").argument("<plugin>", "Plugin name to enable").option("--marketplace <name>", "Restrict to skills from this marketplace (by name or repo URL)").option("--agent <agent>",
|
|
10956
|
+
marketplace.command("enable").description("Re-activate a previously disabled plugin's skills").argument("<plugin>", "Plugin name to enable").option("--marketplace <name>", "Restrict to skills from this marketplace (by name or repo URL)").option("--agent <agent>", `Target agent host: ${AGENT_CHOICES} (default: ${DEFAULT_AGENT})`).option("--claude", "Shorthand for --agent claude-code").option("--scope <scope>", "Installation scope: project | user (default: user)").option("--target <dir>", "Override skills directory").action((plugin, opts) => {
|
|
10643
10957
|
const start = Date.now();
|
|
10644
10958
|
try {
|
|
10645
10959
|
const targetDir = resolveTargetDir(opts);
|
|
@@ -10668,7 +10982,7 @@ function registerSkillsCommands(program2) {
|
|
|
10668
10982
|
fail(err, "skills", "marketplace-enable", start);
|
|
10669
10983
|
}
|
|
10670
10984
|
});
|
|
10671
|
-
marketplace.command("manage").description("Manage marketplaces and plugins: toggle plugins on/off, add or remove marketplaces (interactive)").option("--agent <agent>",
|
|
10985
|
+
marketplace.command("manage").description("Manage marketplaces and plugins: toggle plugins on/off, add or remove marketplaces (interactive)").option("--agent <agent>", `Target agent host: ${AGENT_CHOICES} (default: ${DEFAULT_AGENT})`).option("--claude", "Shorthand for --agent claude-code").option("--scope <scope>", "Installation scope: project | user (default: user)").option("--target <dir>", "Override skills directory").action(async (opts) => {
|
|
10672
10986
|
const start = Date.now();
|
|
10673
10987
|
try {
|
|
10674
10988
|
assertInteractive("Use `pncli skills marketplace enable|disable <plugin>`, `add <url>`, and `remove <name>` to manage non-interactively.");
|
|
@@ -10771,7 +11085,7 @@ function registerSkillsCommands(program2) {
|
|
|
10771
11085
|
fail(err, "skills", "marketplace-manage", start);
|
|
10772
11086
|
}
|
|
10773
11087
|
});
|
|
10774
|
-
skills.command("purge-user").description("Remove all skills from the user-level skills folder for the target agent").option("--agent <agent>",
|
|
11088
|
+
skills.command("purge-user").description("Remove all skills from the user-level skills folder for the target agent").option("--agent <agent>", `Target agent host: ${AGENT_CHOICES} (default: ${DEFAULT_AGENT})`).option("--claude", "Shorthand for --agent claude-code").option("--force", "Skip confirmation \u2014 remove all skills without prompting").action((opts) => {
|
|
10775
11089
|
const start = Date.now();
|
|
10776
11090
|
try {
|
|
10777
11091
|
const userDir = resolveAgentPaths(resolveAgentName(opts)).user;
|
|
@@ -10915,13 +11229,47 @@ function containerState(s) {
|
|
|
10915
11229
|
if (s.running) return "Running";
|
|
10916
11230
|
return "unknown";
|
|
10917
11231
|
}
|
|
10918
|
-
function
|
|
10919
|
-
const
|
|
10920
|
-
const
|
|
11232
|
+
function resolveCluster(config, env, instance) {
|
|
11233
|
+
const effectiveEnv = env ?? config.openshift.defaultEnvironment;
|
|
11234
|
+
const effectiveInstance = instance ?? config.openshift.defaultInstance;
|
|
11235
|
+
if (effectiveEnv && effectiveInstance) {
|
|
11236
|
+
const envCfg = config.openshift.environments[effectiveEnv];
|
|
11237
|
+
const instCfg = envCfg?.instances?.[effectiveInstance];
|
|
11238
|
+
if (!instCfg?.baseUrl) {
|
|
11239
|
+
throw new PncliError(
|
|
11240
|
+
`OpenShift cluster "${effectiveEnv}/${effectiveInstance}" is not configured or missing baseUrl. Run: pncli config set openshift.environments.${effectiveEnv}.instances.${effectiveInstance}.baseUrl <url>`
|
|
11241
|
+
);
|
|
11242
|
+
}
|
|
11243
|
+
if (!instCfg.token) {
|
|
11244
|
+
throw new PncliError(
|
|
11245
|
+
`OpenShift cluster "${effectiveEnv}/${effectiveInstance}" is missing a token. Run: pncli config set openshift.environments.${effectiveEnv}.instances.${effectiveInstance}.token <token>`
|
|
11246
|
+
);
|
|
11247
|
+
}
|
|
11248
|
+
return { baseUrl: instCfg.baseUrl, token: instCfg.token };
|
|
11249
|
+
}
|
|
11250
|
+
if (effectiveEnv && !effectiveInstance) {
|
|
11251
|
+
throw new PncliError(
|
|
11252
|
+
`OpenShift environment "${effectiveEnv}" requires a cluster instance. Pass --instance <name> or run: pncli config set openshift.defaultInstance <name>`
|
|
11253
|
+
);
|
|
11254
|
+
}
|
|
11255
|
+
if (!effectiveEnv && effectiveInstance) {
|
|
11256
|
+
throw new PncliError(
|
|
11257
|
+
`OpenShift instance "${effectiveInstance}" requires an environment. Pass --env <name> or run: pncli config set openshift.defaultEnvironment <name>`
|
|
11258
|
+
);
|
|
11259
|
+
}
|
|
10921
11260
|
if (!config.openshift.baseUrl) {
|
|
10922
11261
|
throw new PncliError("OpenShift not configured. Run: pncli config init");
|
|
10923
11262
|
}
|
|
10924
|
-
return
|
|
11263
|
+
return { baseUrl: config.openshift.baseUrl, token: config.openshift.token };
|
|
11264
|
+
}
|
|
11265
|
+
function getHttp2(oc) {
|
|
11266
|
+
const opts = oc.optsWithGlobals();
|
|
11267
|
+
const config = loadConfig({ configPath: opts.config });
|
|
11268
|
+
const cluster = resolveCluster(config, opts.env, opts.instance);
|
|
11269
|
+
return createHttpClient(
|
|
11270
|
+
{ ...config, openshift: { ...config.openshift, ...cluster } },
|
|
11271
|
+
Boolean(opts.dryRun)
|
|
11272
|
+
);
|
|
10925
11273
|
}
|
|
10926
11274
|
function podAge(creationTimestamp) {
|
|
10927
11275
|
if (!creationTimestamp) return "unknown";
|
|
@@ -10934,12 +11282,63 @@ function podAge(creationTimestamp) {
|
|
|
10934
11282
|
if (ageHours < 24) return `${ageHours}h`;
|
|
10935
11283
|
return `${Math.floor(ageHours / 24)}d`;
|
|
10936
11284
|
}
|
|
11285
|
+
function parseCpuMillicores(cpu) {
|
|
11286
|
+
if (!cpu) return null;
|
|
11287
|
+
if (cpu.endsWith("n")) return Math.round(Number(cpu.slice(0, -1)) / 1e6);
|
|
11288
|
+
if (cpu.endsWith("u")) return Math.round(Number(cpu.slice(0, -1)) / 1e3);
|
|
11289
|
+
if (cpu.endsWith("m")) return Number(cpu.slice(0, -1));
|
|
11290
|
+
const whole = Number(cpu);
|
|
11291
|
+
if (!Number.isNaN(whole)) return Math.round(whole * 1e3);
|
|
11292
|
+
return null;
|
|
11293
|
+
}
|
|
11294
|
+
function parseMemMiB(mem) {
|
|
11295
|
+
if (!mem) return null;
|
|
11296
|
+
const round2 = (n) => Math.round(n * 100) / 100;
|
|
11297
|
+
if (mem.endsWith("Ki")) return round2(Number(mem.slice(0, -2)) / 1024);
|
|
11298
|
+
if (mem.endsWith("Mi")) return Number(mem.slice(0, -2));
|
|
11299
|
+
if (mem.endsWith("Gi")) return Math.round(Number(mem.slice(0, -2)) * 1024);
|
|
11300
|
+
if (mem.endsWith("Ti")) return Math.round(Number(mem.slice(0, -2)) * 1024 * 1024);
|
|
11301
|
+
if (mem.endsWith("K")) return round2(Number(mem.slice(0, -1)) * 1e3 / (1024 * 1024));
|
|
11302
|
+
if (mem.endsWith("M")) return round2(Number(mem.slice(0, -1)) * 1e3 * 1e3 / (1024 * 1024));
|
|
11303
|
+
if (mem.endsWith("G")) return Math.round(Number(mem.slice(0, -1)) * 1e3 * 1e3 * 1e3 / (1024 * 1024));
|
|
11304
|
+
const bytes = Number(mem);
|
|
11305
|
+
if (!Number.isNaN(bytes)) return round2(bytes / (1024 * 1024));
|
|
11306
|
+
return null;
|
|
11307
|
+
}
|
|
10937
11308
|
function registerOpenShiftCommands(program2) {
|
|
10938
|
-
const oc = program2.command("openshift").description("OpenShift / Kubernetes operations");
|
|
11309
|
+
const oc = program2.command("openshift").description("OpenShift / Kubernetes operations").option("--env <name>", "Named environment (openshift.environments.<name>) to target").option("--instance <name>", "Named cluster instance within the environment to target");
|
|
11310
|
+
const cluster = oc.command("cluster").description("Manage named OpenShift cluster configurations");
|
|
11311
|
+
cluster.command("list").description("List all configured OpenShift environments and cluster instances").action(() => {
|
|
11312
|
+
const start = Date.now();
|
|
11313
|
+
try {
|
|
11314
|
+
const opts = oc.optsWithGlobals();
|
|
11315
|
+
const config = loadConfig({ configPath: opts.config });
|
|
11316
|
+
const clusters = [];
|
|
11317
|
+
for (const [envName, envCfg] of Object.entries(config.openshift.environments)) {
|
|
11318
|
+
for (const [instName, instCfg] of Object.entries(envCfg.instances ?? {})) {
|
|
11319
|
+
clusters.push({ environment: envName, instance: instName, baseUrl: instCfg.baseUrl });
|
|
11320
|
+
}
|
|
11321
|
+
}
|
|
11322
|
+
const legacy = config.openshift.baseUrl ? [{ environment: "(default)", instance: "(default)", baseUrl: config.openshift.baseUrl }] : [];
|
|
11323
|
+
success(
|
|
11324
|
+
{
|
|
11325
|
+
count: clusters.length + legacy.length,
|
|
11326
|
+
clusters: [...legacy, ...clusters],
|
|
11327
|
+
defaultEnvironment: config.openshift.defaultEnvironment,
|
|
11328
|
+
defaultInstance: config.openshift.defaultInstance
|
|
11329
|
+
},
|
|
11330
|
+
"openshift",
|
|
11331
|
+
"cluster-list",
|
|
11332
|
+
start
|
|
11333
|
+
);
|
|
11334
|
+
} catch (err) {
|
|
11335
|
+
fail(err, "openshift", "cluster-list", start);
|
|
11336
|
+
}
|
|
11337
|
+
});
|
|
10939
11338
|
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) => {
|
|
10940
11339
|
const start = Date.now();
|
|
10941
11340
|
try {
|
|
10942
|
-
const http = getHttp2(
|
|
11341
|
+
const http = getHttp2(oc);
|
|
10943
11342
|
const params = {};
|
|
10944
11343
|
if (opts.labelSelector) params["labelSelector"] = opts.labelSelector;
|
|
10945
11344
|
const podList = await http.openshift(
|
|
@@ -11048,7 +11447,7 @@ function registerOpenShiftCommands(program2) {
|
|
|
11048
11447
|
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) => {
|
|
11049
11448
|
const start = Date.now();
|
|
11050
11449
|
try {
|
|
11051
|
-
const http = getHttp2(
|
|
11450
|
+
const http = getHttp2(oc);
|
|
11052
11451
|
const params = {};
|
|
11053
11452
|
const selectors = [];
|
|
11054
11453
|
if (!opts.all) selectors.push("type=Warning");
|
|
@@ -11082,7 +11481,7 @@ function registerOpenShiftCommands(program2) {
|
|
|
11082
11481
|
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) => {
|
|
11083
11482
|
const start = Date.now();
|
|
11084
11483
|
try {
|
|
11085
|
-
const http = getHttp2(
|
|
11484
|
+
const http = getHttp2(oc);
|
|
11086
11485
|
const lines = opts.lines ? parseInt(opts.lines, 10) : 100;
|
|
11087
11486
|
const logText = await http.openshiftText(
|
|
11088
11487
|
`/api/v1/namespaces/${encodeURIComponent(opts.namespace)}/pods/${encodeURIComponent(opts.pod)}/log`,
|
|
@@ -11114,7 +11513,7 @@ function registerOpenShiftCommands(program2) {
|
|
|
11114
11513
|
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) => {
|
|
11115
11514
|
const start = Date.now();
|
|
11116
11515
|
try {
|
|
11117
|
-
const http = getHttp2(
|
|
11516
|
+
const http = getHttp2(oc);
|
|
11118
11517
|
const metricsList = await http.openshift(
|
|
11119
11518
|
`/apis/metrics.k8s.io/v1beta1/namespaces/${encodeURIComponent(opts.namespace)}/pods`
|
|
11120
11519
|
);
|
|
@@ -11131,15 +11530,121 @@ function registerOpenShiftCommands(program2) {
|
|
|
11131
11530
|
fail(err, "openshift", "pod-metrics", start);
|
|
11132
11531
|
}
|
|
11133
11532
|
});
|
|
11533
|
+
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) => {
|
|
11534
|
+
const start = Date.now();
|
|
11535
|
+
try {
|
|
11536
|
+
const http = getHttp2(oc);
|
|
11537
|
+
const nsEncoded = encodeURIComponent(opts.namespace);
|
|
11538
|
+
const podParams = {};
|
|
11539
|
+
if (opts.labelSelector) podParams["labelSelector"] = opts.labelSelector;
|
|
11540
|
+
const [podList, metricsList] = await Promise.all([
|
|
11541
|
+
http.openshift(`/api/v1/namespaces/${nsEncoded}/pods`, { params: podParams }),
|
|
11542
|
+
http.openshift(`/apis/metrics.k8s.io/v1beta1/namespaces/${nsEncoded}/pods`, { params: podParams })
|
|
11543
|
+
]);
|
|
11544
|
+
const metricsIndex = /* @__PURE__ */ new Map();
|
|
11545
|
+
for (const pm of metricsList.items) {
|
|
11546
|
+
const containerMap = /* @__PURE__ */ new Map();
|
|
11547
|
+
for (const c of pm.containers) {
|
|
11548
|
+
containerMap.set(c.name, c.usage);
|
|
11549
|
+
}
|
|
11550
|
+
metricsIndex.set(pm.metadata.name, containerMap);
|
|
11551
|
+
}
|
|
11552
|
+
const rows = [];
|
|
11553
|
+
for (const pod of podList.items) {
|
|
11554
|
+
const podName = pod.metadata.name;
|
|
11555
|
+
const podMetrics = metricsIndex.get(podName);
|
|
11556
|
+
const containers = pod.spec.containers ?? [];
|
|
11557
|
+
for (const container of containers) {
|
|
11558
|
+
const usage = podMetrics?.get(container.name);
|
|
11559
|
+
rows.push({
|
|
11560
|
+
pod: podName,
|
|
11561
|
+
container: container.name,
|
|
11562
|
+
cpuUsageMillicores: parseCpuMillicores(usage?.cpu),
|
|
11563
|
+
memUsageMiB: parseMemMiB(usage?.memory),
|
|
11564
|
+
cpuLimitsMillicores: parseCpuMillicores(container.resources?.limits?.cpu),
|
|
11565
|
+
memLimitsMiB: parseMemMiB(container.resources?.limits?.memory),
|
|
11566
|
+
cpuRequestsMillicores: parseCpuMillicores(container.resources?.requests?.cpu),
|
|
11567
|
+
memRequestsMiB: parseMemMiB(container.resources?.requests?.memory)
|
|
11568
|
+
});
|
|
11569
|
+
}
|
|
11570
|
+
}
|
|
11571
|
+
if (opts.csv) {
|
|
11572
|
+
const header = "Pod,Container,CPU Usage (m),Memory Usage (Mi),CPU Limits (m),Memory Limits (Mi),CPU Requests (m),Memory Requests (Mi)";
|
|
11573
|
+
const lines = rows.map(
|
|
11574
|
+
(r) => [
|
|
11575
|
+
r.pod,
|
|
11576
|
+
r.container,
|
|
11577
|
+
r.cpuUsageMillicores ?? "",
|
|
11578
|
+
r.memUsageMiB ?? "",
|
|
11579
|
+
r.cpuLimitsMillicores ?? "",
|
|
11580
|
+
r.memLimitsMiB ?? "",
|
|
11581
|
+
r.cpuRequestsMillicores ?? "",
|
|
11582
|
+
r.memRequestsMiB ?? ""
|
|
11583
|
+
].join(",")
|
|
11584
|
+
);
|
|
11585
|
+
writeRawOutput([header, ...lines].join("\n") + "\n");
|
|
11586
|
+
} else {
|
|
11587
|
+
success(
|
|
11588
|
+
{ namespace: opts.namespace, count: rows.length, units: { cpu: "m", memory: "Mi" }, rows },
|
|
11589
|
+
"openshift",
|
|
11590
|
+
"resource-usage",
|
|
11591
|
+
start
|
|
11592
|
+
);
|
|
11593
|
+
}
|
|
11594
|
+
} catch (err) {
|
|
11595
|
+
fail(err, "openshift", "resource-usage", start);
|
|
11596
|
+
}
|
|
11597
|
+
});
|
|
11134
11598
|
}
|
|
11135
11599
|
|
|
11136
11600
|
// src/services/dynatrace/commands.ts
|
|
11137
|
-
function getHttp3(program2) {
|
|
11601
|
+
function getHttp3(program2, envName) {
|
|
11138
11602
|
const opts = program2.optsWithGlobals();
|
|
11139
|
-
|
|
11140
|
-
|
|
11141
|
-
|
|
11142
|
-
|
|
11603
|
+
const cfg = loadConfig({ configPath: opts.config });
|
|
11604
|
+
if (envName) {
|
|
11605
|
+
const envConfig = cfg.dynatrace.environments[envName];
|
|
11606
|
+
if (!envConfig) {
|
|
11607
|
+
throw new PncliError(
|
|
11608
|
+
`Dynatrace environment "${envName}" not configured. Run: pncli config set dynatrace.environments.${envName}.baseUrl <url>`
|
|
11609
|
+
);
|
|
11610
|
+
}
|
|
11611
|
+
return createHttpClient(
|
|
11612
|
+
{
|
|
11613
|
+
...cfg,
|
|
11614
|
+
dynatrace: {
|
|
11615
|
+
...cfg.dynatrace,
|
|
11616
|
+
baseUrl: envConfig.baseUrl ?? cfg.dynatrace.baseUrl,
|
|
11617
|
+
apiToken: envConfig.apiToken ?? cfg.dynatrace.apiToken,
|
|
11618
|
+
platformUrl: envConfig.platformUrl ?? cfg.dynatrace.platformUrl,
|
|
11619
|
+
platformToken: envConfig.platformToken ?? cfg.dynatrace.platformToken
|
|
11620
|
+
}
|
|
11621
|
+
},
|
|
11622
|
+
Boolean(opts.dryRun)
|
|
11623
|
+
);
|
|
11624
|
+
}
|
|
11625
|
+
const defaultEnv = cfg.dynatrace.defaultEnvironment;
|
|
11626
|
+
if (defaultEnv) {
|
|
11627
|
+
const envConfig = cfg.dynatrace.environments[defaultEnv];
|
|
11628
|
+
if (!envConfig) {
|
|
11629
|
+
throw new PncliError(
|
|
11630
|
+
`Dynatrace defaultEnvironment "${defaultEnv}" is not configured. Run: pncli config set dynatrace.environments.${defaultEnv}.baseUrl <url> or pncli config set dynatrace.defaultEnvironment <name>`
|
|
11631
|
+
);
|
|
11632
|
+
}
|
|
11633
|
+
return createHttpClient(
|
|
11634
|
+
{
|
|
11635
|
+
...cfg,
|
|
11636
|
+
dynatrace: {
|
|
11637
|
+
...cfg.dynatrace,
|
|
11638
|
+
baseUrl: envConfig.baseUrl ?? cfg.dynatrace.baseUrl,
|
|
11639
|
+
apiToken: envConfig.apiToken ?? cfg.dynatrace.apiToken,
|
|
11640
|
+
platformUrl: envConfig.platformUrl ?? cfg.dynatrace.platformUrl,
|
|
11641
|
+
platformToken: envConfig.platformToken ?? cfg.dynatrace.platformToken
|
|
11642
|
+
}
|
|
11643
|
+
},
|
|
11644
|
+
Boolean(opts.dryRun)
|
|
11645
|
+
);
|
|
11646
|
+
}
|
|
11647
|
+
return createHttpClient(cfg, Boolean(opts.dryRun));
|
|
11143
11648
|
}
|
|
11144
11649
|
async function allPages(fetchPage) {
|
|
11145
11650
|
const items = [];
|
|
@@ -11211,12 +11716,13 @@ async function runTraceQuery(http, traceId) {
|
|
|
11211
11716
|
return response.result;
|
|
11212
11717
|
}
|
|
11213
11718
|
function registerDynatraceCommands(program2) {
|
|
11214
|
-
const dynatrace = program2.command("dynatrace").description("Dynatrace observability operations");
|
|
11719
|
+
const dynatrace = program2.command("dynatrace").description("Dynatrace observability operations").option("--env <name>", "Named Dynatrace environment to use (from dynatrace.environments.<name>)");
|
|
11215
11720
|
const entities = dynatrace.command("entities").description("Monitored entity operations");
|
|
11216
11721
|
entities.command("list").description("List monitored entities using an entity selector").requiredOption("--selector <selector>", 'Dynatrace entity selector, e.g. type("SERVICE")').option("--from <time>", "Start time (ISO 8601 or relative, e.g. now-2h)").option("--to <time>", "End time (ISO 8601 or relative)").option("--fields <fields>", "Additional entity fields").action(async (opts) => {
|
|
11217
11722
|
const start = Date.now();
|
|
11218
11723
|
try {
|
|
11219
|
-
const
|
|
11724
|
+
const envName = dynatrace.opts().env;
|
|
11725
|
+
const data = await listEntities(getHttp3(program2, envName), opts.selector, opts);
|
|
11220
11726
|
success({ totalCount: data.totalCount, entities: data.items }, "dynatrace", "entities list", start);
|
|
11221
11727
|
} catch (err) {
|
|
11222
11728
|
fail(err, "dynatrace", "entities list", start);
|
|
@@ -11225,7 +11731,8 @@ function registerDynatraceCommands(program2) {
|
|
|
11225
11731
|
entities.command("get").description("Get a monitored entity by ID").requiredOption("--id <id>", "Entity ID, e.g. SERVICE-1234567890ABCDEF").option("--from <time>", "Start time").option("--to <time>", "End time").option("--fields <fields>", "Additional entity fields").action(async (opts) => {
|
|
11226
11732
|
const start = Date.now();
|
|
11227
11733
|
try {
|
|
11228
|
-
const
|
|
11734
|
+
const envName = dynatrace.opts().env;
|
|
11735
|
+
const data = await getHttp3(program2, envName).dynatrace(
|
|
11229
11736
|
`/api/v2/entities/${encodeURIComponent(opts.id)}`,
|
|
11230
11737
|
{ params: { from: opts.from, to: opts.to, fields: opts.fields } }
|
|
11231
11738
|
);
|
|
@@ -11237,7 +11744,8 @@ function registerDynatraceCommands(program2) {
|
|
|
11237
11744
|
dynatrace.command("services").description("List monitored services").option("--from <time>", "Start time").option("--to <time>", "End time").action(async (opts) => {
|
|
11238
11745
|
const start = Date.now();
|
|
11239
11746
|
try {
|
|
11240
|
-
const
|
|
11747
|
+
const envName = dynatrace.opts().env;
|
|
11748
|
+
const data = await listEntities(getHttp3(program2, envName), 'type("SERVICE")', opts);
|
|
11241
11749
|
success({ totalCount: data.totalCount, services: data.items }, "dynatrace", "services", start);
|
|
11242
11750
|
} catch (err) {
|
|
11243
11751
|
fail(err, "dynatrace", "services", start);
|
|
@@ -11246,8 +11754,9 @@ function registerDynatraceCommands(program2) {
|
|
|
11246
11754
|
dynatrace.command("workloads").description("List monitored Kubernetes workloads").option("--from <time>", "Start time").option("--to <time>", "End time").action(async (opts) => {
|
|
11247
11755
|
const start = Date.now();
|
|
11248
11756
|
try {
|
|
11757
|
+
const envName = dynatrace.opts().env;
|
|
11249
11758
|
const selector = 'type("CLOUD_APPLICATION")';
|
|
11250
|
-
const data = await listEntities(getHttp3(program2), selector, opts);
|
|
11759
|
+
const data = await listEntities(getHttp3(program2, envName), selector, opts);
|
|
11251
11760
|
success({ totalCount: data.totalCount, workloads: data.items }, "dynatrace", "workloads", start);
|
|
11252
11761
|
} catch (err) {
|
|
11253
11762
|
fail(err, "dynatrace", "workloads", start);
|
|
@@ -11257,8 +11766,9 @@ function registerDynatraceCommands(program2) {
|
|
|
11257
11766
|
problems.command("list").description("List problems").option("--from <time>", "Start time (default: now-2h)").option("--to <time>", "End time (default: now)").option("--problem-selector <selector>", "Dynatrace problem selector").option("--entity-selector <selector>", "Affected entity selector").action(async (opts) => {
|
|
11258
11767
|
const start = Date.now();
|
|
11259
11768
|
try {
|
|
11769
|
+
const envName = dynatrace.opts().env;
|
|
11260
11770
|
const data = await allPages(async (nextPageKey) => {
|
|
11261
|
-
const page = await getHttp3(program2).dynatrace("/api/v2/problems", {
|
|
11771
|
+
const page = await getHttp3(program2, envName).dynatrace("/api/v2/problems", {
|
|
11262
11772
|
params: nextPageKey ? { nextPageKey } : {
|
|
11263
11773
|
from: opts.from ?? "now-2h",
|
|
11264
11774
|
to: opts.to ?? "now",
|
|
@@ -11277,7 +11787,8 @@ function registerDynatraceCommands(program2) {
|
|
|
11277
11787
|
problems.command("get").description("Get a problem by ID").requiredOption("--id <id>", "Problem ID").option("--fields <fields>", "Additional fields").action(async (opts) => {
|
|
11278
11788
|
const start = Date.now();
|
|
11279
11789
|
try {
|
|
11280
|
-
const
|
|
11790
|
+
const envName = dynatrace.opts().env;
|
|
11791
|
+
const data = await getHttp3(program2, envName).dynatrace(
|
|
11281
11792
|
`/api/v2/problems/${encodeURIComponent(opts.id)}`,
|
|
11282
11793
|
{ params: { fields: opts.fields } }
|
|
11283
11794
|
);
|
|
@@ -11289,7 +11800,8 @@ function registerDynatraceCommands(program2) {
|
|
|
11289
11800
|
dynatrace.command("trace").description("Get distributed-trace spans from Grail").requiredOption("--id <trace-id>", "16- or 32-character hexadecimal trace ID").action(async (opts) => {
|
|
11290
11801
|
const start = Date.now();
|
|
11291
11802
|
try {
|
|
11292
|
-
const
|
|
11803
|
+
const envName = dynatrace.opts().env;
|
|
11804
|
+
const result = await runTraceQuery(getHttp3(program2, envName), opts.id);
|
|
11293
11805
|
success(result, "dynatrace", "trace", start);
|
|
11294
11806
|
} catch (err) {
|
|
11295
11807
|
fail(err, "dynatrace", "trace", start);
|
|
@@ -11356,6 +11868,500 @@ function registerLogscaleCommands(program2) {
|
|
|
11356
11868
|
});
|
|
11357
11869
|
}
|
|
11358
11870
|
|
|
11871
|
+
// src/services/splitio/commands.ts
|
|
11872
|
+
import fs5 from "fs";
|
|
11873
|
+
import confirm2 from "@inquirer/confirm";
|
|
11874
|
+
function getHttp5(program2) {
|
|
11875
|
+
const opts = program2.optsWithGlobals();
|
|
11876
|
+
return createHttpClient(
|
|
11877
|
+
loadConfig({ configPath: opts.config }),
|
|
11878
|
+
Boolean(opts.dryRun)
|
|
11879
|
+
);
|
|
11880
|
+
}
|
|
11881
|
+
function loadInputFile(filePath) {
|
|
11882
|
+
let raw;
|
|
11883
|
+
if (filePath === "-") {
|
|
11884
|
+
raw = fs5.readFileSync(process.stdin.fd, "utf8");
|
|
11885
|
+
} else {
|
|
11886
|
+
raw = fs5.readFileSync(filePath, "utf8");
|
|
11887
|
+
}
|
|
11888
|
+
try {
|
|
11889
|
+
return JSON.parse(raw);
|
|
11890
|
+
} catch {
|
|
11891
|
+
throw new PncliError(`Could not parse JSON from ${filePath === "-" ? "stdin" : filePath}`);
|
|
11892
|
+
}
|
|
11893
|
+
}
|
|
11894
|
+
async function confirmWrite(opts, opLabel) {
|
|
11895
|
+
if (opts.yes) return;
|
|
11896
|
+
const proceed = await confirm2({
|
|
11897
|
+
message: `Submit Change Request "${opts.mnemonic}" (${opLabel})? This creates an approval request in Split.`,
|
|
11898
|
+
default: false
|
|
11899
|
+
});
|
|
11900
|
+
if (!proceed) throw new PncliError("Aborted by user.", 0);
|
|
11901
|
+
}
|
|
11902
|
+
function buildCrBase(opts) {
|
|
11903
|
+
const base = {
|
|
11904
|
+
title: opts.mnemonic,
|
|
11905
|
+
comment: opts.description,
|
|
11906
|
+
crNumber: opts.changeNumber
|
|
11907
|
+
};
|
|
11908
|
+
if (opts.approvers) {
|
|
11909
|
+
base["approvers"] = opts.approvers.split(",").map((s) => s.trim()).filter(Boolean).map((id) => ({ id }));
|
|
11910
|
+
}
|
|
11911
|
+
return base;
|
|
11912
|
+
}
|
|
11913
|
+
function registerSplitioCommands(program2) {
|
|
11914
|
+
const splitio = program2.command("splitio").description("Split.IO feature flag administration");
|
|
11915
|
+
const workspaces = splitio.command("workspaces").description("Workspace operations");
|
|
11916
|
+
workspaces.command("list").description("List all Split.IO workspaces").action(async () => {
|
|
11917
|
+
const start = Date.now();
|
|
11918
|
+
try {
|
|
11919
|
+
const http = getHttp5(program2);
|
|
11920
|
+
const data = await http.splitio("/internal/api/v2/workspaces");
|
|
11921
|
+
success(
|
|
11922
|
+
{ count: data.objects.length, workspaces: data.objects },
|
|
11923
|
+
"splitio",
|
|
11924
|
+
"workspaces list",
|
|
11925
|
+
start
|
|
11926
|
+
);
|
|
11927
|
+
} catch (err) {
|
|
11928
|
+
fail(err, "splitio", "workspaces list", start);
|
|
11929
|
+
}
|
|
11930
|
+
});
|
|
11931
|
+
const environments = splitio.command("environments").description("Environment operations");
|
|
11932
|
+
environments.command("list").description("List environments in a workspace").requiredOption("--workspace <id>", "Workspace ID").action(async (opts) => {
|
|
11933
|
+
const start = Date.now();
|
|
11934
|
+
try {
|
|
11935
|
+
const http = getHttp5(program2);
|
|
11936
|
+
const data = await http.splitio(
|
|
11937
|
+
`/internal/api/v2/workspaces/${encodeURIComponent(opts.workspace)}/environments`
|
|
11938
|
+
);
|
|
11939
|
+
success(
|
|
11940
|
+
{ count: data.objects.length, environments: data.objects },
|
|
11941
|
+
"splitio",
|
|
11942
|
+
"environments list",
|
|
11943
|
+
start
|
|
11944
|
+
);
|
|
11945
|
+
} catch (err) {
|
|
11946
|
+
fail(err, "splitio", "environments list", start);
|
|
11947
|
+
}
|
|
11948
|
+
});
|
|
11949
|
+
const flags = splitio.command("flags").description("Feature flag operations");
|
|
11950
|
+
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) => {
|
|
11951
|
+
const start = Date.now();
|
|
11952
|
+
try {
|
|
11953
|
+
const http = getHttp5(program2);
|
|
11954
|
+
const params = {
|
|
11955
|
+
wsId: opts.workspace,
|
|
11956
|
+
limit: parseInt(opts.limit, 10),
|
|
11957
|
+
offset: parseInt(opts.offset, 10)
|
|
11958
|
+
};
|
|
11959
|
+
if (opts.flagSet) params["flagSetName"] = opts.flagSet;
|
|
11960
|
+
const data = await http.splitio("/internal/api/v2/splits", { params });
|
|
11961
|
+
success(
|
|
11962
|
+
{ count: data.objects.length, totalCount: data.totalCount, flags: data.objects },
|
|
11963
|
+
"splitio",
|
|
11964
|
+
"flags list",
|
|
11965
|
+
start
|
|
11966
|
+
);
|
|
11967
|
+
} catch (err) {
|
|
11968
|
+
fail(err, "splitio", "flags list", start);
|
|
11969
|
+
}
|
|
11970
|
+
});
|
|
11971
|
+
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) => {
|
|
11972
|
+
const start = Date.now();
|
|
11973
|
+
try {
|
|
11974
|
+
const http = getHttp5(program2);
|
|
11975
|
+
if (opts.environment) {
|
|
11976
|
+
const data = await http.splitio(
|
|
11977
|
+
`/internal/api/v2/splits/${encodeURIComponent(opts.flag)}/environments/${encodeURIComponent(opts.environment)}`,
|
|
11978
|
+
{ params: { wsId: opts.workspace } }
|
|
11979
|
+
);
|
|
11980
|
+
success(data, "splitio", "flags get", start);
|
|
11981
|
+
} else {
|
|
11982
|
+
const data = await http.splitio(
|
|
11983
|
+
`/internal/api/v2/splits/${encodeURIComponent(opts.flag)}`,
|
|
11984
|
+
{ params: { wsId: opts.workspace } }
|
|
11985
|
+
);
|
|
11986
|
+
success(data, "splitio", "flags get", start);
|
|
11987
|
+
}
|
|
11988
|
+
} catch (err) {
|
|
11989
|
+
fail(err, "splitio", "flags get", start);
|
|
11990
|
+
}
|
|
11991
|
+
});
|
|
11992
|
+
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) => {
|
|
11993
|
+
const start = Date.now();
|
|
11994
|
+
try {
|
|
11995
|
+
if (!opts.inputFile) throw new PncliError("--input-file is required for flags update");
|
|
11996
|
+
const definition = loadInputFile(opts.inputFile);
|
|
11997
|
+
await confirmWrite(opts, `UPDATE ${opts.flag} in ${opts.environment}`);
|
|
11998
|
+
const http = getHttp5(program2);
|
|
11999
|
+
const body = {
|
|
12000
|
+
...buildCrBase(opts),
|
|
12001
|
+
operationType: "UPDATE_SPLIT_DEFINITION",
|
|
12002
|
+
split: {
|
|
12003
|
+
name: opts.flag,
|
|
12004
|
+
environment: { id: opts.environment },
|
|
12005
|
+
definition
|
|
12006
|
+
},
|
|
12007
|
+
workspace: { id: opts.workspace }
|
|
12008
|
+
};
|
|
12009
|
+
const data = await http.splitio("/internal/api/v2/changeRequests", {
|
|
12010
|
+
method: "POST",
|
|
12011
|
+
body
|
|
12012
|
+
});
|
|
12013
|
+
success(data, "splitio", "flags update", start);
|
|
12014
|
+
} catch (err) {
|
|
12015
|
+
fail(err, "splitio", "flags update", start);
|
|
12016
|
+
}
|
|
12017
|
+
});
|
|
12018
|
+
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) => {
|
|
12019
|
+
const start = Date.now();
|
|
12020
|
+
try {
|
|
12021
|
+
if (!opts.treatment && !opts.weights) {
|
|
12022
|
+
throw new PncliError("Either --treatment or --weights must be specified");
|
|
12023
|
+
}
|
|
12024
|
+
const http = getHttp5(program2);
|
|
12025
|
+
const current = await http.splitio(
|
|
12026
|
+
`/internal/api/v2/splits/${encodeURIComponent(opts.flag)}/environments/${encodeURIComponent(opts.environment)}`,
|
|
12027
|
+
{ params: { wsId: opts.workspace } }
|
|
12028
|
+
);
|
|
12029
|
+
const rules = current.rules ?? [];
|
|
12030
|
+
const idx = parseInt(opts.ruleIndex, 10);
|
|
12031
|
+
if (Number.isNaN(idx) || idx < 0 || idx >= rules.length) {
|
|
12032
|
+
throw new PncliError(`Rule index ${Number.isNaN(idx) ? JSON.stringify(opts.ruleIndex) : idx} is out of range (flag has ${rules.length} rules)`);
|
|
12033
|
+
}
|
|
12034
|
+
if (opts.treatment) {
|
|
12035
|
+
rules[idx] = { ...rules[idx], buckets: [{ treatment: opts.treatment, size: 1e5 }] };
|
|
12036
|
+
} else {
|
|
12037
|
+
const parsed = JSON.parse(opts.weights);
|
|
12038
|
+
rules[idx] = { ...rules[idx], buckets: parsed };
|
|
12039
|
+
}
|
|
12040
|
+
await confirmWrite(opts, `SET_RULE ${idx} on ${opts.flag} in ${opts.environment}`);
|
|
12041
|
+
const body = {
|
|
12042
|
+
...buildCrBase(opts),
|
|
12043
|
+
operationType: "UPDATE_SPLIT_DEFINITION",
|
|
12044
|
+
split: {
|
|
12045
|
+
name: opts.flag,
|
|
12046
|
+
environment: { id: opts.environment },
|
|
12047
|
+
definition: { ...current, rules }
|
|
12048
|
+
},
|
|
12049
|
+
workspace: { id: opts.workspace }
|
|
12050
|
+
};
|
|
12051
|
+
const data = await http.splitio("/internal/api/v2/changeRequests", {
|
|
12052
|
+
method: "POST",
|
|
12053
|
+
body
|
|
12054
|
+
});
|
|
12055
|
+
success(data, "splitio", "flags set-rule", start);
|
|
12056
|
+
} catch (err) {
|
|
12057
|
+
fail(err, "splitio", "flags set-rule", start);
|
|
12058
|
+
}
|
|
12059
|
+
});
|
|
12060
|
+
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) => {
|
|
12061
|
+
const start = Date.now();
|
|
12062
|
+
try {
|
|
12063
|
+
const http = getHttp5(program2);
|
|
12064
|
+
const current = await http.splitio(
|
|
12065
|
+
`/internal/api/v2/splits/${encodeURIComponent(opts.flag)}/environments/${encodeURIComponent(opts.environment)}`,
|
|
12066
|
+
{ params: { wsId: opts.workspace } }
|
|
12067
|
+
);
|
|
12068
|
+
await confirmWrite(opts, `SET_DEFAULT ${opts.treatment} on ${opts.flag} in ${opts.environment}`);
|
|
12069
|
+
const body = {
|
|
12070
|
+
...buildCrBase(opts),
|
|
12071
|
+
operationType: "UPDATE_SPLIT_DEFINITION",
|
|
12072
|
+
split: {
|
|
12073
|
+
name: opts.flag,
|
|
12074
|
+
environment: { id: opts.environment },
|
|
12075
|
+
definition: {
|
|
12076
|
+
...current,
|
|
12077
|
+
defaultRule: [{ treatment: opts.treatment, size: 1e5 }]
|
|
12078
|
+
}
|
|
12079
|
+
},
|
|
12080
|
+
workspace: { id: opts.workspace }
|
|
12081
|
+
};
|
|
12082
|
+
const data = await http.splitio("/internal/api/v2/changeRequests", {
|
|
12083
|
+
method: "POST",
|
|
12084
|
+
body
|
|
12085
|
+
});
|
|
12086
|
+
success(data, "splitio", "flags set-default", start);
|
|
12087
|
+
} catch (err) {
|
|
12088
|
+
fail(err, "splitio", "flags set-default", start);
|
|
12089
|
+
}
|
|
12090
|
+
});
|
|
12091
|
+
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) => {
|
|
12092
|
+
const start = Date.now();
|
|
12093
|
+
try {
|
|
12094
|
+
const enable = opts.enabled.toLowerCase() !== "false" && opts.enabled !== "0";
|
|
12095
|
+
const opType = enable ? "RESTORE_SPLIT" : "KILL_SPLIT";
|
|
12096
|
+
await confirmWrite(opts, `${opType} ${opts.flag} in ${opts.environment}`);
|
|
12097
|
+
const http = getHttp5(program2);
|
|
12098
|
+
const body = {
|
|
12099
|
+
...buildCrBase(opts),
|
|
12100
|
+
operationType: opType,
|
|
12101
|
+
split: { name: opts.flag, environment: { id: opts.environment } },
|
|
12102
|
+
workspace: { id: opts.workspace }
|
|
12103
|
+
};
|
|
12104
|
+
const data = await http.splitio("/internal/api/v2/changeRequests", {
|
|
12105
|
+
method: "POST",
|
|
12106
|
+
body
|
|
12107
|
+
});
|
|
12108
|
+
success(data, "splitio", "flags toggle", start);
|
|
12109
|
+
} catch (err) {
|
|
12110
|
+
fail(err, "splitio", "flags toggle", start);
|
|
12111
|
+
}
|
|
12112
|
+
});
|
|
12113
|
+
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) => {
|
|
12114
|
+
const start = Date.now();
|
|
12115
|
+
try {
|
|
12116
|
+
if (!opts.inputFile) throw new PncliError("--input-file is required for batch-toggle");
|
|
12117
|
+
const entries = loadInputFile(opts.inputFile);
|
|
12118
|
+
if (!Array.isArray(entries)) throw new PncliError("Input file must be a JSON array of {flag, enabled} objects");
|
|
12119
|
+
await confirmWrite(opts, `BATCH_TOGGLE ${entries.length} flags in ${opts.environment}`);
|
|
12120
|
+
const http = getHttp5(program2);
|
|
12121
|
+
const results = [];
|
|
12122
|
+
for (const entry of entries) {
|
|
12123
|
+
try {
|
|
12124
|
+
const opType = entry.enabled ? "RESTORE_SPLIT" : "KILL_SPLIT";
|
|
12125
|
+
const body = {
|
|
12126
|
+
...buildCrBase(opts),
|
|
12127
|
+
title: `${opts.mnemonic} \u2014 ${entry.flag}`,
|
|
12128
|
+
operationType: opType,
|
|
12129
|
+
split: { name: entry.flag, environment: { id: opts.environment } },
|
|
12130
|
+
workspace: { id: opts.workspace }
|
|
12131
|
+
};
|
|
12132
|
+
const cr = await http.splitio("/internal/api/v2/changeRequests", {
|
|
12133
|
+
method: "POST",
|
|
12134
|
+
body
|
|
12135
|
+
});
|
|
12136
|
+
results.push({ flag: entry.flag, ok: true, id: cr.id });
|
|
12137
|
+
} catch (err) {
|
|
12138
|
+
results.push({
|
|
12139
|
+
flag: entry.flag,
|
|
12140
|
+
ok: false,
|
|
12141
|
+
error: err instanceof Error ? err.message : String(err)
|
|
12142
|
+
});
|
|
12143
|
+
}
|
|
12144
|
+
}
|
|
12145
|
+
success({ submitted: results.length, results }, "splitio", "flags batch-toggle", start);
|
|
12146
|
+
} catch (err) {
|
|
12147
|
+
fail(err, "splitio", "flags batch-toggle", start);
|
|
12148
|
+
}
|
|
12149
|
+
});
|
|
12150
|
+
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) => {
|
|
12151
|
+
const start = Date.now();
|
|
12152
|
+
try {
|
|
12153
|
+
await confirmWrite(opts, `KILL ${opts.flag} in ${opts.environment}`);
|
|
12154
|
+
const http = getHttp5(program2);
|
|
12155
|
+
const body = {
|
|
12156
|
+
...buildCrBase(opts),
|
|
12157
|
+
operationType: "KILL_SPLIT",
|
|
12158
|
+
split: { name: opts.flag, environment: { id: opts.environment } },
|
|
12159
|
+
workspace: { id: opts.workspace }
|
|
12160
|
+
};
|
|
12161
|
+
const data = await http.splitio("/internal/api/v2/changeRequests", {
|
|
12162
|
+
method: "POST",
|
|
12163
|
+
body
|
|
12164
|
+
});
|
|
12165
|
+
success(data, "splitio", "flags kill", start);
|
|
12166
|
+
} catch (err) {
|
|
12167
|
+
fail(err, "splitio", "flags kill", start);
|
|
12168
|
+
}
|
|
12169
|
+
});
|
|
12170
|
+
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) => {
|
|
12171
|
+
const start = Date.now();
|
|
12172
|
+
try {
|
|
12173
|
+
await confirmWrite(opts, `RESTORE ${opts.flag} in ${opts.environment}`);
|
|
12174
|
+
const http = getHttp5(program2);
|
|
12175
|
+
const body = {
|
|
12176
|
+
...buildCrBase(opts),
|
|
12177
|
+
operationType: "RESTORE_SPLIT",
|
|
12178
|
+
split: { name: opts.flag, environment: { id: opts.environment } },
|
|
12179
|
+
workspace: { id: opts.workspace }
|
|
12180
|
+
};
|
|
12181
|
+
const data = await http.splitio("/internal/api/v2/changeRequests", {
|
|
12182
|
+
method: "POST",
|
|
12183
|
+
body
|
|
12184
|
+
});
|
|
12185
|
+
success(data, "splitio", "flags restore", start);
|
|
12186
|
+
} catch (err) {
|
|
12187
|
+
fail(err, "splitio", "flags restore", start);
|
|
12188
|
+
}
|
|
12189
|
+
});
|
|
12190
|
+
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) => {
|
|
12191
|
+
const start = Date.now();
|
|
12192
|
+
try {
|
|
12193
|
+
await confirmWrite(opts, `ARCHIVE ${opts.flag}`);
|
|
12194
|
+
const http = getHttp5(program2);
|
|
12195
|
+
const body = {
|
|
12196
|
+
...buildCrBase(opts),
|
|
12197
|
+
operationType: "ARCHIVE_SPLIT",
|
|
12198
|
+
split: { name: opts.flag },
|
|
12199
|
+
workspace: { id: opts.workspace }
|
|
12200
|
+
};
|
|
12201
|
+
const data = await http.splitio("/internal/api/v2/changeRequests", {
|
|
12202
|
+
method: "POST",
|
|
12203
|
+
body
|
|
12204
|
+
});
|
|
12205
|
+
success(data, "splitio", "flags archive", start);
|
|
12206
|
+
} catch (err) {
|
|
12207
|
+
fail(err, "splitio", "flags archive", start);
|
|
12208
|
+
}
|
|
12209
|
+
});
|
|
12210
|
+
const changeRequests = splitio.command("change-requests").description("Change Request tracking");
|
|
12211
|
+
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) => {
|
|
12212
|
+
const start = Date.now();
|
|
12213
|
+
try {
|
|
12214
|
+
const http = getHttp5(program2);
|
|
12215
|
+
const params = {
|
|
12216
|
+
wsId: opts.workspace,
|
|
12217
|
+
limit: parseInt(opts.limit, 10),
|
|
12218
|
+
offset: parseInt(opts.offset, 10)
|
|
12219
|
+
};
|
|
12220
|
+
if (opts.environment) params["environmentId"] = opts.environment;
|
|
12221
|
+
if (opts.status) params["status"] = opts.status;
|
|
12222
|
+
const data = await http.splitio(
|
|
12223
|
+
"/internal/api/v2/changeRequests",
|
|
12224
|
+
{ params }
|
|
12225
|
+
);
|
|
12226
|
+
success(
|
|
12227
|
+
{ count: data.objects.length, totalCount: data.totalCount, changeRequests: data.objects },
|
|
12228
|
+
"splitio",
|
|
12229
|
+
"change-requests list",
|
|
12230
|
+
start
|
|
12231
|
+
);
|
|
12232
|
+
} catch (err) {
|
|
12233
|
+
fail(err, "splitio", "change-requests list", start);
|
|
12234
|
+
}
|
|
12235
|
+
});
|
|
12236
|
+
changeRequests.command("get").description("Get a Change Request by ID").requiredOption("--id <id>", "Change Request ID").action(async (opts) => {
|
|
12237
|
+
const start = Date.now();
|
|
12238
|
+
try {
|
|
12239
|
+
const http = getHttp5(program2);
|
|
12240
|
+
const data = await http.splitio(
|
|
12241
|
+
`/internal/api/v2/changeRequests/${encodeURIComponent(opts.id)}`
|
|
12242
|
+
);
|
|
12243
|
+
success(data, "splitio", "change-requests get", start);
|
|
12244
|
+
} catch (err) {
|
|
12245
|
+
fail(err, "splitio", "change-requests get", start);
|
|
12246
|
+
}
|
|
12247
|
+
});
|
|
12248
|
+
}
|
|
12249
|
+
|
|
12250
|
+
// src/services/figma/commands.ts
|
|
12251
|
+
function parseFileKey(input3) {
|
|
12252
|
+
if (input3.startsWith("http://") || input3.startsWith("https://")) {
|
|
12253
|
+
const match = /figma\.com\/(?:design|file)\/([A-Za-z0-9]+)/i.exec(input3);
|
|
12254
|
+
if (!match?.[1]) {
|
|
12255
|
+
throw new Error(`Could not extract Figma file key from URL: ${input3}
|
|
12256
|
+
Expected format: https://www.figma.com/design/<file-key>/...`);
|
|
12257
|
+
}
|
|
12258
|
+
return match[1];
|
|
12259
|
+
}
|
|
12260
|
+
return input3;
|
|
12261
|
+
}
|
|
12262
|
+
function getHttp6(program2) {
|
|
12263
|
+
const opts = program2.optsWithGlobals();
|
|
12264
|
+
return createHttpClient(
|
|
12265
|
+
loadConfig({ configPath: opts.config }),
|
|
12266
|
+
Boolean(opts.dryRun)
|
|
12267
|
+
);
|
|
12268
|
+
}
|
|
12269
|
+
function registerFigmaCommands(program2) {
|
|
12270
|
+
const figma = program2.command("figma").description("Figma design operations (files, comments, versions)");
|
|
12271
|
+
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) => {
|
|
12272
|
+
const start = Date.now();
|
|
12273
|
+
const { success: success2, fail: fail2 } = await import("./output-XT3WT5L5.js");
|
|
12274
|
+
try {
|
|
12275
|
+
const fileKey = parseFileKey(fileKeyOrUrl);
|
|
12276
|
+
const params = {};
|
|
12277
|
+
if (!opts.document) {
|
|
12278
|
+
params["geometry"] = "paths";
|
|
12279
|
+
params["depth"] = 0;
|
|
12280
|
+
}
|
|
12281
|
+
const data = await getHttp6(program2).figma(`/v1/files/${encodeURIComponent(fileKey)}`, { params });
|
|
12282
|
+
success2(
|
|
12283
|
+
{
|
|
12284
|
+
fileKey,
|
|
12285
|
+
name: data.name,
|
|
12286
|
+
lastModified: data.lastModified,
|
|
12287
|
+
version: data.version,
|
|
12288
|
+
thumbnailUrl: data.thumbnailUrl,
|
|
12289
|
+
role: data.role,
|
|
12290
|
+
editorType: data.editorType,
|
|
12291
|
+
schemaVersion: data.schemaVersion,
|
|
12292
|
+
...opts.document ? { document: data.document } : {},
|
|
12293
|
+
componentCount: data.components ? Object.keys(data.components).length : 0,
|
|
12294
|
+
styleCount: data.styles ? Object.keys(data.styles).length : 0
|
|
12295
|
+
},
|
|
12296
|
+
"figma",
|
|
12297
|
+
"file",
|
|
12298
|
+
start
|
|
12299
|
+
);
|
|
12300
|
+
} catch (err) {
|
|
12301
|
+
fail2(err, "figma", "file", start);
|
|
12302
|
+
}
|
|
12303
|
+
});
|
|
12304
|
+
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) => {
|
|
12305
|
+
const start = Date.now();
|
|
12306
|
+
const { success: success2, fail: fail2 } = await import("./output-XT3WT5L5.js");
|
|
12307
|
+
try {
|
|
12308
|
+
const fileKey = parseFileKey(fileKeyOrUrl);
|
|
12309
|
+
const params = {};
|
|
12310
|
+
if (opts.asOf) params["as_of"] = opts.asOf;
|
|
12311
|
+
const data = await getHttp6(program2).figma(`/v1/files/${encodeURIComponent(fileKey)}/comments`, { params });
|
|
12312
|
+
success2(
|
|
12313
|
+
{ fileKey, count: data.comments.length, comments: data.comments },
|
|
12314
|
+
"figma",
|
|
12315
|
+
"comments",
|
|
12316
|
+
start
|
|
12317
|
+
);
|
|
12318
|
+
} catch (err) {
|
|
12319
|
+
fail2(err, "figma", "comments", start);
|
|
12320
|
+
}
|
|
12321
|
+
});
|
|
12322
|
+
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) => {
|
|
12323
|
+
const start = Date.now();
|
|
12324
|
+
const { success: success2, fail: fail2 } = await import("./output-XT3WT5L5.js");
|
|
12325
|
+
try {
|
|
12326
|
+
const fileKey = parseFileKey(fileKeyOrUrl);
|
|
12327
|
+
const data = await getHttp6(program2).figma(`/v1/files/${encodeURIComponent(fileKey)}/versions`);
|
|
12328
|
+
success2(
|
|
12329
|
+
{ fileKey, count: data.versions.length, versions: data.versions },
|
|
12330
|
+
"figma",
|
|
12331
|
+
"versions",
|
|
12332
|
+
start
|
|
12333
|
+
);
|
|
12334
|
+
} catch (err) {
|
|
12335
|
+
fail2(err, "figma", "versions", start);
|
|
12336
|
+
}
|
|
12337
|
+
});
|
|
12338
|
+
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) => {
|
|
12339
|
+
const start = Date.now();
|
|
12340
|
+
const { success: success2, fail: fail2 } = await import("./output-XT3WT5L5.js");
|
|
12341
|
+
try {
|
|
12342
|
+
const data = await getHttp6(program2).figma(`/v1/projects/${encodeURIComponent(projectId)}/files`);
|
|
12343
|
+
success2(
|
|
12344
|
+
{ projectId, count: data.files.length, files: data.files },
|
|
12345
|
+
"figma",
|
|
12346
|
+
"project-files",
|
|
12347
|
+
start
|
|
12348
|
+
);
|
|
12349
|
+
} catch (err) {
|
|
12350
|
+
fail2(err, "figma", "project-files", start);
|
|
12351
|
+
}
|
|
12352
|
+
});
|
|
12353
|
+
figma.command("me").description("Get the current Figma user (useful for verifying credentials)").action(async () => {
|
|
12354
|
+
const start = Date.now();
|
|
12355
|
+
const { success: success2, fail: fail2 } = await import("./output-XT3WT5L5.js");
|
|
12356
|
+
try {
|
|
12357
|
+
const data = await getHttp6(program2).figma("/v1/me");
|
|
12358
|
+
success2(data, "figma", "me", start);
|
|
12359
|
+
} catch (err) {
|
|
12360
|
+
fail2(err, "figma", "me", start);
|
|
12361
|
+
}
|
|
12362
|
+
});
|
|
12363
|
+
}
|
|
12364
|
+
|
|
11359
12365
|
// src/cli.ts
|
|
11360
12366
|
if (!process.env.PNCLI_VERIFY_TLS) {
|
|
11361
12367
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
@@ -11392,7 +12398,6 @@ registerConfigCommands(program);
|
|
|
11392
12398
|
registerAdoCommands(program);
|
|
11393
12399
|
registerJenkinsCommands(program);
|
|
11394
12400
|
registerArtifactoryCommands(program);
|
|
11395
|
-
registerUdeployCommands(program);
|
|
11396
12401
|
registerCheckmarxCommands(program);
|
|
11397
12402
|
registerServiceNowCommands(program);
|
|
11398
12403
|
registerContrastCommands(program);
|
|
@@ -11402,6 +12407,8 @@ registerJwtCommands(program);
|
|
|
11402
12407
|
registerOpenShiftCommands(program);
|
|
11403
12408
|
registerDynatraceCommands(program);
|
|
11404
12409
|
registerLogscaleCommands(program);
|
|
12410
|
+
registerSplitioCommands(program);
|
|
12411
|
+
registerFigmaCommands(program);
|
|
11405
12412
|
program.addHelpText("after", `
|
|
11406
12413
|
Services:
|
|
11407
12414
|
git Local git operations (status, diff, log, branch)
|
|
@@ -11413,9 +12420,8 @@ Services:
|
|
|
11413
12420
|
sonar SonarQube Server (quality gates, issues, metrics, hotspots)
|
|
11414
12421
|
sde SDElements (threat modeling, countermeasures, compliance)
|
|
11415
12422
|
ado Azure DevOps Server (work items, repos, PRs, pipelines)
|
|
11416
|
-
jenkins Jenkins
|
|
12423
|
+
jenkins Jenkins (jobs, builds, logs, named instances)
|
|
11417
12424
|
artifactory Artifactory (repos, artifact search, build info, properties)
|
|
11418
|
-
udeploy IBM UrbanCode Deploy (component versions, deployment processes)
|
|
11419
12425
|
checkmarx Checkmarx One (projects, scans, scan statistics)
|
|
11420
12426
|
servicenow ServiceNow (change requests)
|
|
11421
12427
|
contrast Contrast IAST (applications, vulnerability findings)
|
|
@@ -11423,8 +12429,10 @@ Services:
|
|
|
11423
12429
|
openshift OpenShift / Kubernetes (pods, events, logs, metrics)
|
|
11424
12430
|
dynatrace Dynatrace (services, entities, problems, traces, Kubernetes workloads)
|
|
11425
12431
|
logscale LogScale (log query, repositories)
|
|
12432
|
+
splitio Split.IO (feature flags, Change Requests)
|
|
12433
|
+
figma Figma (files, comments, version history)
|
|
11426
12434
|
config Manage pncli configuration
|
|
11427
|
-
skills
|
|
12435
|
+
skills Install and manage agent skills (Codex, GitHub Copilot, Claude Code)
|
|
11428
12436
|
jwt JWT token utilities (decode header and payload)
|
|
11429
12437
|
`);
|
|
11430
12438
|
program.parseAsync(process.argv).catch((err) => {
|