@kolatts/pncli 2.0.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 +2 -2
- package/dist/{chunk-PXKUQPPF.js → chunk-LH7WBP7W.js} +33 -5
- package/dist/chunk-LH7WBP7W.js.map +1 -0
- package/dist/cli.js +620 -70
- package/dist/cli.js.map +1 -1
- package/dist/{config-YHYQTGKT.js → config-JGBUUXQX.js} +2 -2
- package/package.json +3 -6
- package/skills/pncli/SKILL.md +37 -2
- package/skills/pncli/dynatrace.md +46 -0
- package/skills/pncli/marketplace.md +53 -2
- package/skills/pncli/openshift.md +57 -2
- package/copilot-instructions.md +0 -1351
- package/dist/chunk-PXKUQPPF.js.map +0 -1
- /package/dist/{config-YHYQTGKT.js.map → config-JGBUUXQX.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
setRepoConfigValue,
|
|
10
10
|
writeGlobalConfig,
|
|
11
11
|
writeRepoConfig
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-LH7WBP7W.js";
|
|
13
13
|
import {
|
|
14
14
|
createHttpClient
|
|
15
15
|
} from "./chunk-ZDJYOI3W.js";
|
|
@@ -6365,6 +6365,25 @@ function registerConfigCommands(program2) {
|
|
|
6365
6365
|
} else {
|
|
6366
6366
|
results.openshift = { ok: null, message: "not configured" };
|
|
6367
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
|
+
}
|
|
6368
6387
|
if (cfg.dynatrace.baseUrl && cfg.dynatrace.apiToken) {
|
|
6369
6388
|
try {
|
|
6370
6389
|
await http.dynatrace("/api/v2/entities", {
|
|
@@ -6374,8 +6393,10 @@ function registerConfigCommands(program2) {
|
|
|
6374
6393
|
} catch (err) {
|
|
6375
6394
|
results.dynatrace = { ok: false, message: err instanceof Error ? err.message : String(err) };
|
|
6376
6395
|
}
|
|
6377
|
-
} else {
|
|
6396
|
+
} else if (Object.keys(cfg.dynatrace.environments).length === 0) {
|
|
6378
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" };
|
|
6379
6400
|
}
|
|
6380
6401
|
if (cfg.dynatrace.platformUrl && cfg.dynatrace.platformToken) {
|
|
6381
6402
|
try {
|
|
@@ -6396,6 +6417,46 @@ function registerConfigCommands(program2) {
|
|
|
6396
6417
|
} else {
|
|
6397
6418
|
results.dynatrace_platform = { ok: null, message: "not configured" };
|
|
6398
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
|
+
}
|
|
6399
6460
|
if (cfg.logscale.baseUrl && cfg.logscale.token) {
|
|
6400
6461
|
try {
|
|
6401
6462
|
await http.logscale("/api/v1/repositories");
|
|
@@ -6621,8 +6682,33 @@ function registerConfigCommands(program2) {
|
|
|
6621
6682
|
results.openshift = categorize2(err);
|
|
6622
6683
|
}
|
|
6623
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
|
+
}
|
|
6624
6706
|
if (!cfg.dynatrace.apiToken) {
|
|
6625
|
-
|
|
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
|
+
}
|
|
6626
6712
|
} else if (!cfg.dynatrace.baseUrl) {
|
|
6627
6713
|
results.dynatrace = { status: "error", message: "baseUrl not configured" };
|
|
6628
6714
|
} else {
|
|
@@ -6658,6 +6744,52 @@ function registerConfigCommands(program2) {
|
|
|
6658
6744
|
results.dynatrace_platform = categorize2(err);
|
|
6659
6745
|
}
|
|
6660
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
|
+
}
|
|
6661
6793
|
if (!cfg.logscale.token) {
|
|
6662
6794
|
results.logscale = { status: "blank", message: "not configured" };
|
|
6663
6795
|
} else if (!cfg.logscale.baseUrl) {
|
|
@@ -6697,11 +6829,39 @@ function registerConfigCommands(program2) {
|
|
|
6697
6829
|
const statuses = Object.values(results).map((r) => r.status);
|
|
6698
6830
|
if (statuses.includes("invalid")) process.exitCode = ExitCode.AUTH_ERROR;
|
|
6699
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
|
+
];
|
|
6700
6861
|
if (cmdOpts.output === "table") {
|
|
6701
|
-
const
|
|
6702
|
-
const labelWidth = 14;
|
|
6862
|
+
const labelWidth = Math.max(14, ...allServices.map((s) => s.length + 2));
|
|
6703
6863
|
const statusWidth = 9;
|
|
6704
|
-
for (const svc of
|
|
6864
|
+
for (const svc of allServices) {
|
|
6705
6865
|
const r = results[svc];
|
|
6706
6866
|
if (!r) continue;
|
|
6707
6867
|
const label = svc.padEnd(labelWidth);
|
|
@@ -6712,10 +6872,9 @@ function registerConfigCommands(program2) {
|
|
|
6712
6872
|
process.stdout.write("\n");
|
|
6713
6873
|
} else {
|
|
6714
6874
|
if (opts.pretty) {
|
|
6715
|
-
const
|
|
6716
|
-
const labelWidth = 14;
|
|
6875
|
+
const labelWidth = Math.max(14, ...allServices.map((s) => s.length + 2));
|
|
6717
6876
|
const statusWidth = 9;
|
|
6718
|
-
for (const svc of
|
|
6877
|
+
for (const svc of allServices) {
|
|
6719
6878
|
const r = results[svc];
|
|
6720
6879
|
if (!r) continue;
|
|
6721
6880
|
const label = svc.padEnd(labelWidth);
|
|
@@ -7393,6 +7552,9 @@ async function initGlobalConfig(start) {
|
|
|
7393
7552
|
}
|
|
7394
7553
|
const existingGlobal = loadJsonFile(getGlobalConfigPath());
|
|
7395
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;
|
|
7396
7558
|
writeGlobalConfig({
|
|
7397
7559
|
jenkinsInstances: existingInstances,
|
|
7398
7560
|
user: {
|
|
@@ -7491,15 +7653,25 @@ async function initGlobalConfig(start) {
|
|
|
7491
7653
|
...useOpenShift && openShiftBaseUrl ? {
|
|
7492
7654
|
openshift: {
|
|
7493
7655
|
baseUrl: normalizeBaseUrl(openShiftBaseUrl),
|
|
7494
|
-
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 } : {}
|
|
7495
7660
|
}
|
|
7496
|
-
} : {},
|
|
7661
|
+
} : existingOpenshift ? { openshift: { ...existingOpenshift } } : {},
|
|
7497
7662
|
...useDynatrace && dynatraceBaseUrl ? {
|
|
7498
7663
|
dynatrace: {
|
|
7499
7664
|
baseUrl: normalizeBaseUrl(dynatraceBaseUrl),
|
|
7500
7665
|
apiToken: dynatraceApiToken || void 0,
|
|
7501
7666
|
platformUrl: normalizeBaseUrl(dynatracePlatformUrl) || void 0,
|
|
7502
|
-
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
|
|
7503
7675
|
}
|
|
7504
7676
|
} : {},
|
|
7505
7677
|
...useLogscale && logscaleBaseUrl ? {
|
|
@@ -8603,7 +8775,7 @@ function registerAdoRepoCommands(ado) {
|
|
|
8603
8775
|
const start = Date.now();
|
|
8604
8776
|
try {
|
|
8605
8777
|
const globalOpts = ado.optsWithGlobals();
|
|
8606
|
-
const { loadConfig: lc } = await import("./config-
|
|
8778
|
+
const { loadConfig: lc } = await import("./config-JGBUUXQX.js");
|
|
8607
8779
|
const config = lc({ configPath: globalOpts.config });
|
|
8608
8780
|
const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
|
|
8609
8781
|
const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
|
|
@@ -9644,24 +9816,51 @@ function assertInteractive(hint) {
|
|
|
9644
9816
|
}
|
|
9645
9817
|
}
|
|
9646
9818
|
var AGENT_PATHS = {
|
|
9647
|
-
"
|
|
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") },
|
|
9648
9821
|
"claude-code": { project: ".claude/skills", user: path8.join(os.homedir(), ".claude/skills") }
|
|
9649
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
|
+
}
|
|
9650
9831
|
function resolveAgentName(opts) {
|
|
9651
|
-
return opts.claude ? "claude-code" : opts.agent ??
|
|
9832
|
+
return opts.claude ? "claude-code" : opts.agent ?? DEFAULT_AGENT;
|
|
9652
9833
|
}
|
|
9653
9834
|
function resolveAgentPaths(agentName) {
|
|
9654
9835
|
const agentConfig = AGENT_PATHS[agentName];
|
|
9655
9836
|
if (!agentConfig) {
|
|
9656
|
-
throw new Error(`Unknown agent: "${agentName}". Use: ${
|
|
9837
|
+
throw new Error(`Unknown agent: "${agentName}". Use: ${AGENT_CHOICES}`);
|
|
9657
9838
|
}
|
|
9839
|
+
warnIfRetargetedAgent(agentName);
|
|
9658
9840
|
return agentConfig;
|
|
9659
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
|
+
}
|
|
9660
9860
|
function resolveTargetDir(opts) {
|
|
9661
9861
|
if (opts.target) return path8.resolve(opts.target);
|
|
9662
9862
|
const agentConfig = resolveAgentPaths(resolveAgentName(opts));
|
|
9663
|
-
|
|
9664
|
-
return path8.resolve(scopePath);
|
|
9863
|
+
return resolveScopedPath(agentConfig, opts.scope ?? "user");
|
|
9665
9864
|
}
|
|
9666
9865
|
function getBundledSkillsDir() {
|
|
9667
9866
|
try {
|
|
@@ -9713,14 +9912,16 @@ function resolveSkillRecord(meta, targetDir, skillName) {
|
|
|
9713
9912
|
};
|
|
9714
9913
|
}
|
|
9715
9914
|
function listActiveSkillDirs(targetDir) {
|
|
9915
|
+
if (!fs4.existsSync(targetDir)) return [];
|
|
9716
9916
|
return fs4.readdirSync(targetDir).filter((name) => {
|
|
9717
9917
|
if (name.startsWith(".")) return false;
|
|
9718
|
-
const
|
|
9918
|
+
const dir = path8.join(targetDir, name);
|
|
9719
9919
|
try {
|
|
9720
|
-
|
|
9920
|
+
if (!fs4.statSync(dir).isDirectory()) return false;
|
|
9721
9921
|
} catch {
|
|
9722
9922
|
return false;
|
|
9723
9923
|
}
|
|
9924
|
+
return fs4.existsSync(path8.join(dir, "SKILL.md"));
|
|
9724
9925
|
});
|
|
9725
9926
|
}
|
|
9726
9927
|
function disablePluginSkills(targetDir, plugin, marketplaceFilter) {
|
|
@@ -9832,6 +10033,142 @@ function detectRepoBranch(repoPath) {
|
|
|
9832
10033
|
return void 0;
|
|
9833
10034
|
}
|
|
9834
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
|
+
}
|
|
9835
10172
|
function recordInstalledSkills(targetDir, skillNames, record) {
|
|
9836
10173
|
if (skillNames.length === 0) return;
|
|
9837
10174
|
const meta = readInstalledMeta(targetDir);
|
|
@@ -9995,7 +10332,7 @@ function installAllPlugins(resolvedPath, marketplaceName, url, targetDir) {
|
|
|
9995
10332
|
const { results, totalInstalled } = installPluginsForMarketplace(resolvedPath, marketplaceName, url, pluginChoices.map((p) => p.name), targetDir);
|
|
9996
10333
|
return { pluginResults: results, totalInstalled };
|
|
9997
10334
|
}
|
|
9998
|
-
function syncMarketplacePlugins(m, targetDir, force, pluginFilter) {
|
|
10335
|
+
function syncMarketplacePlugins(m, targetDir, force, pluginFilter, installedOnly = false) {
|
|
9999
10336
|
const marketplaceName = marketplaceLabel(m);
|
|
10000
10337
|
try {
|
|
10001
10338
|
const marketplacePath = m.localPath;
|
|
@@ -10014,7 +10351,16 @@ function syncMarketplacePlugins(m, targetDir, force, pluginFilter) {
|
|
|
10014
10351
|
}
|
|
10015
10352
|
let pluginNames;
|
|
10016
10353
|
if (pluginFilter === "all") {
|
|
10017
|
-
|
|
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
|
+
}
|
|
10018
10364
|
} else if (pluginChoices.some((p) => p.name === pluginFilter)) {
|
|
10019
10365
|
pluginNames = [pluginFilter];
|
|
10020
10366
|
} else {
|
|
@@ -10084,7 +10430,7 @@ async function marketplaceAddAction(url, localPath, opts, commandName) {
|
|
|
10084
10430
|
}
|
|
10085
10431
|
function registerSkillsCommands(program2) {
|
|
10086
10432
|
const skills = program2.command("skills").description("Manage pncli Claude Code skills");
|
|
10087
|
-
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) => {
|
|
10088
10434
|
const start = Date.now();
|
|
10089
10435
|
try {
|
|
10090
10436
|
let targetDir;
|
|
@@ -10092,8 +10438,7 @@ function registerSkillsCommands(program2) {
|
|
|
10092
10438
|
targetDir = path8.resolve(opts.target);
|
|
10093
10439
|
} else {
|
|
10094
10440
|
const agentConfig = resolveAgentPaths(opts.agent);
|
|
10095
|
-
|
|
10096
|
-
targetDir = path8.resolve(scopePath);
|
|
10441
|
+
targetDir = resolveScopedPath(agentConfig, opts.scope === "user" ? "user" : "project");
|
|
10097
10442
|
}
|
|
10098
10443
|
const resolvedTarget = path8.resolve(targetDir);
|
|
10099
10444
|
const bundledDir = getBundledSkillsDir();
|
|
@@ -10142,6 +10487,14 @@ function registerSkillsCommands(program2) {
|
|
|
10142
10487
|
warn(`Failed to install: ${failed.join(", ")}`);
|
|
10143
10488
|
}
|
|
10144
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
|
+
}
|
|
10145
10498
|
success({
|
|
10146
10499
|
installed,
|
|
10147
10500
|
failed,
|
|
@@ -10149,13 +10502,14 @@ function registerSkillsCommands(program2) {
|
|
|
10149
10502
|
total: installed.length,
|
|
10150
10503
|
agent: opts.target ? "custom" : opts.agent,
|
|
10151
10504
|
scope: opts.target ? "custom" : opts.scope,
|
|
10152
|
-
source: "bundled"
|
|
10505
|
+
source: "bundled",
|
|
10506
|
+
...opts.target ? { trackedAsCustomTarget: tracked } : {}
|
|
10153
10507
|
}, "skills", "install", start);
|
|
10154
10508
|
} catch (err) {
|
|
10155
10509
|
fail(err, "skills", "install", start);
|
|
10156
10510
|
}
|
|
10157
10511
|
});
|
|
10158
|
-
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) => {
|
|
10159
10513
|
const start = Date.now();
|
|
10160
10514
|
try {
|
|
10161
10515
|
let targetDir;
|
|
@@ -10163,8 +10517,7 @@ function registerSkillsCommands(program2) {
|
|
|
10163
10517
|
targetDir = path8.resolve(opts.target);
|
|
10164
10518
|
} else {
|
|
10165
10519
|
const agentConfig = resolveAgentPaths(opts.agent);
|
|
10166
|
-
|
|
10167
|
-
targetDir = path8.resolve(scopePath);
|
|
10520
|
+
targetDir = resolveScopedPath(agentConfig, opts.scope === "user" ? "user" : "project");
|
|
10168
10521
|
}
|
|
10169
10522
|
if (!fs4.existsSync(targetDir)) {
|
|
10170
10523
|
success({ skills: [], message: `No skills directory found at ${targetDir}. Run: pncli skills install` }, "skills", "list", start);
|
|
@@ -10216,7 +10569,7 @@ function registerSkillsCommands(program2) {
|
|
|
10216
10569
|
fail(err, "skills", "list", start);
|
|
10217
10570
|
}
|
|
10218
10571
|
});
|
|
10219
|
-
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) => {
|
|
10220
10573
|
const start = Date.now();
|
|
10221
10574
|
try {
|
|
10222
10575
|
let targetDir;
|
|
@@ -10224,8 +10577,7 @@ function registerSkillsCommands(program2) {
|
|
|
10224
10577
|
targetDir = path8.resolve(opts.target);
|
|
10225
10578
|
} else {
|
|
10226
10579
|
const agentConfig = resolveAgentPaths(resolveAgentName(opts));
|
|
10227
|
-
|
|
10228
|
-
targetDir = path8.resolve(scopePath);
|
|
10580
|
+
targetDir = resolveScopedPath(agentConfig, opts.scope === "project" ? "project" : "user");
|
|
10229
10581
|
}
|
|
10230
10582
|
const resolvedTarget = path8.resolve(targetDir);
|
|
10231
10583
|
const skillDir = path8.resolve(targetDir, name);
|
|
@@ -10250,9 +10602,77 @@ function registerSkillsCommands(program2) {
|
|
|
10250
10602
|
fail(err, "skills", "uninstall", start);
|
|
10251
10603
|
}
|
|
10252
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
|
+
});
|
|
10253
10673
|
const marketplace = skills.command("marketplace").description("Manage git-hosted skills marketplaces");
|
|
10254
10674
|
function withMarketplaceAddOptions(cmd) {
|
|
10255
|
-
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");
|
|
10256
10676
|
}
|
|
10257
10677
|
withMarketplaceAddOptions(
|
|
10258
10678
|
marketplace.command("add").description("Register a new marketplace, clone it, and install all its plugins")
|
|
@@ -10270,7 +10690,8 @@ function registerSkillsCommands(program2) {
|
|
|
10270
10690
|
name: marketplaceLabel(m),
|
|
10271
10691
|
repoUrl: m.repoUrl,
|
|
10272
10692
|
localPath: m.localPath,
|
|
10273
|
-
tokenConfigured: !!m.token
|
|
10693
|
+
tokenConfigured: !!m.token,
|
|
10694
|
+
upstreamRemote: m.localPath && fs4.existsSync(m.localPath) ? getRepoRemoteUrl(m.localPath) : null
|
|
10274
10695
|
})),
|
|
10275
10696
|
total: all.length
|
|
10276
10697
|
}, "skills", "marketplace-list", start);
|
|
@@ -10308,7 +10729,7 @@ function registerSkillsCommands(program2) {
|
|
|
10308
10729
|
fail(err, "skills", "marketplace-remove", start);
|
|
10309
10730
|
}
|
|
10310
10731
|
});
|
|
10311
|
-
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) => {
|
|
10312
10733
|
const start = Date.now();
|
|
10313
10734
|
try {
|
|
10314
10735
|
const configPath = getGlobalConfigPath();
|
|
@@ -10318,8 +10739,9 @@ function registerSkillsCommands(program2) {
|
|
|
10318
10739
|
}
|
|
10319
10740
|
const targetDir = resolveAgentPaths(resolveAgentName(opts)).user;
|
|
10320
10741
|
const force = opts.force ?? false;
|
|
10742
|
+
const installedOnly = opts.installedOnly ?? false;
|
|
10321
10743
|
if (opts.marketplace === "all") {
|
|
10322
|
-
const results = allMarketplaces.map((m) => syncMarketplacePlugins(m, targetDir, force, plugin ?? "all"));
|
|
10744
|
+
const results = allMarketplaces.map((m) => syncMarketplacePlugins(m, targetDir, force, plugin ?? "all", installedOnly));
|
|
10323
10745
|
success({ allMarketplaces: true, marketplaces: results, target: targetDir }, "skills", "marketplace-sync", start);
|
|
10324
10746
|
return;
|
|
10325
10747
|
}
|
|
@@ -10347,7 +10769,7 @@ function registerSkillsCommands(program2) {
|
|
|
10347
10769
|
]
|
|
10348
10770
|
});
|
|
10349
10771
|
if (chosen === ALL_MARKETPLACES) {
|
|
10350
|
-
const results2 = allMarketplaces.map((m) => syncMarketplacePlugins(m, targetDir, force, selectedPlugin ?? "all"));
|
|
10772
|
+
const results2 = allMarketplaces.map((m) => syncMarketplacePlugins(m, targetDir, force, selectedPlugin ?? "all", installedOnly));
|
|
10351
10773
|
success({ allMarketplaces: true, marketplaces: results2, target: targetDir }, "skills", "marketplace-sync", start);
|
|
10352
10774
|
return;
|
|
10353
10775
|
}
|
|
@@ -10394,10 +10816,29 @@ function registerSkillsCommands(program2) {
|
|
|
10394
10816
|
}, "skills", "marketplace-sync", start);
|
|
10395
10817
|
return;
|
|
10396
10818
|
}
|
|
10397
|
-
|
|
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
|
+
}
|
|
10398
10839
|
const { results, totalInstalled } = installPluginsForMarketplace(marketplacePath, marketplaceName, selectedMarketplace.repoUrl, pluginNames, targetDir);
|
|
10399
10840
|
if (selectedPlugin === "all") {
|
|
10400
|
-
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);
|
|
10401
10842
|
} else {
|
|
10402
10843
|
const single = results[selectedPlugin] ?? { installed: [], failed: [] };
|
|
10403
10844
|
success({
|
|
@@ -10416,7 +10857,7 @@ function registerSkillsCommands(program2) {
|
|
|
10416
10857
|
fail(err, "skills", "marketplace-sync", start);
|
|
10417
10858
|
}
|
|
10418
10859
|
});
|
|
10419
|
-
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) => {
|
|
10420
10861
|
const start = Date.now();
|
|
10421
10862
|
try {
|
|
10422
10863
|
let targetDir;
|
|
@@ -10424,8 +10865,7 @@ function registerSkillsCommands(program2) {
|
|
|
10424
10865
|
targetDir = path8.resolve(opts.target);
|
|
10425
10866
|
} else {
|
|
10426
10867
|
const agentConfig = resolveAgentPaths(resolveAgentName(opts));
|
|
10427
|
-
|
|
10428
|
-
targetDir = path8.resolve(scopePath);
|
|
10868
|
+
targetDir = resolveScopedPath(agentConfig, opts.scope ?? "user");
|
|
10429
10869
|
}
|
|
10430
10870
|
const resolvedTarget = path8.resolve(targetDir);
|
|
10431
10871
|
if (!fs4.existsSync(targetDir)) {
|
|
@@ -10490,7 +10930,7 @@ function registerSkillsCommands(program2) {
|
|
|
10490
10930
|
fail(err, "skills", "marketplace-purge-plugin", start);
|
|
10491
10931
|
}
|
|
10492
10932
|
});
|
|
10493
|
-
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) => {
|
|
10494
10934
|
const start = Date.now();
|
|
10495
10935
|
try {
|
|
10496
10936
|
const targetDir = resolveTargetDir(opts);
|
|
@@ -10513,7 +10953,7 @@ function registerSkillsCommands(program2) {
|
|
|
10513
10953
|
fail(err, "skills", "marketplace-disable", start);
|
|
10514
10954
|
}
|
|
10515
10955
|
});
|
|
10516
|
-
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) => {
|
|
10517
10957
|
const start = Date.now();
|
|
10518
10958
|
try {
|
|
10519
10959
|
const targetDir = resolveTargetDir(opts);
|
|
@@ -10542,7 +10982,7 @@ function registerSkillsCommands(program2) {
|
|
|
10542
10982
|
fail(err, "skills", "marketplace-enable", start);
|
|
10543
10983
|
}
|
|
10544
10984
|
});
|
|
10545
|
-
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) => {
|
|
10546
10986
|
const start = Date.now();
|
|
10547
10987
|
try {
|
|
10548
10988
|
assertInteractive("Use `pncli skills marketplace enable|disable <plugin>`, `add <url>`, and `remove <name>` to manage non-interactively.");
|
|
@@ -10645,7 +11085,7 @@ function registerSkillsCommands(program2) {
|
|
|
10645
11085
|
fail(err, "skills", "marketplace-manage", start);
|
|
10646
11086
|
}
|
|
10647
11087
|
});
|
|
10648
|
-
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) => {
|
|
10649
11089
|
const start = Date.now();
|
|
10650
11090
|
try {
|
|
10651
11091
|
const userDir = resolveAgentPaths(resolveAgentName(opts)).user;
|
|
@@ -10789,13 +11229,47 @@ function containerState(s) {
|
|
|
10789
11229
|
if (s.running) return "Running";
|
|
10790
11230
|
return "unknown";
|
|
10791
11231
|
}
|
|
10792
|
-
function
|
|
10793
|
-
const
|
|
10794
|
-
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
|
+
}
|
|
10795
11260
|
if (!config.openshift.baseUrl) {
|
|
10796
11261
|
throw new PncliError("OpenShift not configured. Run: pncli config init");
|
|
10797
11262
|
}
|
|
10798
|
-
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
|
+
);
|
|
10799
11273
|
}
|
|
10800
11274
|
function podAge(creationTimestamp) {
|
|
10801
11275
|
if (!creationTimestamp) return "unknown";
|
|
@@ -10832,11 +11306,39 @@ function parseMemMiB(mem) {
|
|
|
10832
11306
|
return null;
|
|
10833
11307
|
}
|
|
10834
11308
|
function registerOpenShiftCommands(program2) {
|
|
10835
|
-
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
|
+
});
|
|
10836
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) => {
|
|
10837
11339
|
const start = Date.now();
|
|
10838
11340
|
try {
|
|
10839
|
-
const http = getHttp2(
|
|
11341
|
+
const http = getHttp2(oc);
|
|
10840
11342
|
const params = {};
|
|
10841
11343
|
if (opts.labelSelector) params["labelSelector"] = opts.labelSelector;
|
|
10842
11344
|
const podList = await http.openshift(
|
|
@@ -10945,7 +11447,7 @@ function registerOpenShiftCommands(program2) {
|
|
|
10945
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) => {
|
|
10946
11448
|
const start = Date.now();
|
|
10947
11449
|
try {
|
|
10948
|
-
const http = getHttp2(
|
|
11450
|
+
const http = getHttp2(oc);
|
|
10949
11451
|
const params = {};
|
|
10950
11452
|
const selectors = [];
|
|
10951
11453
|
if (!opts.all) selectors.push("type=Warning");
|
|
@@ -10979,7 +11481,7 @@ function registerOpenShiftCommands(program2) {
|
|
|
10979
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) => {
|
|
10980
11482
|
const start = Date.now();
|
|
10981
11483
|
try {
|
|
10982
|
-
const http = getHttp2(
|
|
11484
|
+
const http = getHttp2(oc);
|
|
10983
11485
|
const lines = opts.lines ? parseInt(opts.lines, 10) : 100;
|
|
10984
11486
|
const logText = await http.openshiftText(
|
|
10985
11487
|
`/api/v1/namespaces/${encodeURIComponent(opts.namespace)}/pods/${encodeURIComponent(opts.pod)}/log`,
|
|
@@ -11011,7 +11513,7 @@ function registerOpenShiftCommands(program2) {
|
|
|
11011
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) => {
|
|
11012
11514
|
const start = Date.now();
|
|
11013
11515
|
try {
|
|
11014
|
-
const http = getHttp2(
|
|
11516
|
+
const http = getHttp2(oc);
|
|
11015
11517
|
const metricsList = await http.openshift(
|
|
11016
11518
|
`/apis/metrics.k8s.io/v1beta1/namespaces/${encodeURIComponent(opts.namespace)}/pods`
|
|
11017
11519
|
);
|
|
@@ -11031,7 +11533,7 @@ function registerOpenShiftCommands(program2) {
|
|
|
11031
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) => {
|
|
11032
11534
|
const start = Date.now();
|
|
11033
11535
|
try {
|
|
11034
|
-
const http = getHttp2(
|
|
11536
|
+
const http = getHttp2(oc);
|
|
11035
11537
|
const nsEncoded = encodeURIComponent(opts.namespace);
|
|
11036
11538
|
const podParams = {};
|
|
11037
11539
|
if (opts.labelSelector) podParams["labelSelector"] = opts.labelSelector;
|
|
@@ -11096,12 +11598,53 @@ function registerOpenShiftCommands(program2) {
|
|
|
11096
11598
|
}
|
|
11097
11599
|
|
|
11098
11600
|
// src/services/dynatrace/commands.ts
|
|
11099
|
-
function getHttp3(program2) {
|
|
11601
|
+
function getHttp3(program2, envName) {
|
|
11100
11602
|
const opts = program2.optsWithGlobals();
|
|
11101
|
-
|
|
11102
|
-
|
|
11103
|
-
|
|
11104
|
-
|
|
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));
|
|
11105
11648
|
}
|
|
11106
11649
|
async function allPages(fetchPage) {
|
|
11107
11650
|
const items = [];
|
|
@@ -11173,12 +11716,13 @@ async function runTraceQuery(http, traceId) {
|
|
|
11173
11716
|
return response.result;
|
|
11174
11717
|
}
|
|
11175
11718
|
function registerDynatraceCommands(program2) {
|
|
11176
|
-
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>)");
|
|
11177
11720
|
const entities = dynatrace.command("entities").description("Monitored entity operations");
|
|
11178
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) => {
|
|
11179
11722
|
const start = Date.now();
|
|
11180
11723
|
try {
|
|
11181
|
-
const
|
|
11724
|
+
const envName = dynatrace.opts().env;
|
|
11725
|
+
const data = await listEntities(getHttp3(program2, envName), opts.selector, opts);
|
|
11182
11726
|
success({ totalCount: data.totalCount, entities: data.items }, "dynatrace", "entities list", start);
|
|
11183
11727
|
} catch (err) {
|
|
11184
11728
|
fail(err, "dynatrace", "entities list", start);
|
|
@@ -11187,7 +11731,8 @@ function registerDynatraceCommands(program2) {
|
|
|
11187
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) => {
|
|
11188
11732
|
const start = Date.now();
|
|
11189
11733
|
try {
|
|
11190
|
-
const
|
|
11734
|
+
const envName = dynatrace.opts().env;
|
|
11735
|
+
const data = await getHttp3(program2, envName).dynatrace(
|
|
11191
11736
|
`/api/v2/entities/${encodeURIComponent(opts.id)}`,
|
|
11192
11737
|
{ params: { from: opts.from, to: opts.to, fields: opts.fields } }
|
|
11193
11738
|
);
|
|
@@ -11199,7 +11744,8 @@ function registerDynatraceCommands(program2) {
|
|
|
11199
11744
|
dynatrace.command("services").description("List monitored services").option("--from <time>", "Start time").option("--to <time>", "End time").action(async (opts) => {
|
|
11200
11745
|
const start = Date.now();
|
|
11201
11746
|
try {
|
|
11202
|
-
const
|
|
11747
|
+
const envName = dynatrace.opts().env;
|
|
11748
|
+
const data = await listEntities(getHttp3(program2, envName), 'type("SERVICE")', opts);
|
|
11203
11749
|
success({ totalCount: data.totalCount, services: data.items }, "dynatrace", "services", start);
|
|
11204
11750
|
} catch (err) {
|
|
11205
11751
|
fail(err, "dynatrace", "services", start);
|
|
@@ -11208,8 +11754,9 @@ function registerDynatraceCommands(program2) {
|
|
|
11208
11754
|
dynatrace.command("workloads").description("List monitored Kubernetes workloads").option("--from <time>", "Start time").option("--to <time>", "End time").action(async (opts) => {
|
|
11209
11755
|
const start = Date.now();
|
|
11210
11756
|
try {
|
|
11757
|
+
const envName = dynatrace.opts().env;
|
|
11211
11758
|
const selector = 'type("CLOUD_APPLICATION")';
|
|
11212
|
-
const data = await listEntities(getHttp3(program2), selector, opts);
|
|
11759
|
+
const data = await listEntities(getHttp3(program2, envName), selector, opts);
|
|
11213
11760
|
success({ totalCount: data.totalCount, workloads: data.items }, "dynatrace", "workloads", start);
|
|
11214
11761
|
} catch (err) {
|
|
11215
11762
|
fail(err, "dynatrace", "workloads", start);
|
|
@@ -11219,8 +11766,9 @@ function registerDynatraceCommands(program2) {
|
|
|
11219
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) => {
|
|
11220
11767
|
const start = Date.now();
|
|
11221
11768
|
try {
|
|
11769
|
+
const envName = dynatrace.opts().env;
|
|
11222
11770
|
const data = await allPages(async (nextPageKey) => {
|
|
11223
|
-
const page = await getHttp3(program2).dynatrace("/api/v2/problems", {
|
|
11771
|
+
const page = await getHttp3(program2, envName).dynatrace("/api/v2/problems", {
|
|
11224
11772
|
params: nextPageKey ? { nextPageKey } : {
|
|
11225
11773
|
from: opts.from ?? "now-2h",
|
|
11226
11774
|
to: opts.to ?? "now",
|
|
@@ -11239,7 +11787,8 @@ function registerDynatraceCommands(program2) {
|
|
|
11239
11787
|
problems.command("get").description("Get a problem by ID").requiredOption("--id <id>", "Problem ID").option("--fields <fields>", "Additional fields").action(async (opts) => {
|
|
11240
11788
|
const start = Date.now();
|
|
11241
11789
|
try {
|
|
11242
|
-
const
|
|
11790
|
+
const envName = dynatrace.opts().env;
|
|
11791
|
+
const data = await getHttp3(program2, envName).dynatrace(
|
|
11243
11792
|
`/api/v2/problems/${encodeURIComponent(opts.id)}`,
|
|
11244
11793
|
{ params: { fields: opts.fields } }
|
|
11245
11794
|
);
|
|
@@ -11251,7 +11800,8 @@ function registerDynatraceCommands(program2) {
|
|
|
11251
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) => {
|
|
11252
11801
|
const start = Date.now();
|
|
11253
11802
|
try {
|
|
11254
|
-
const
|
|
11803
|
+
const envName = dynatrace.opts().env;
|
|
11804
|
+
const result = await runTraceQuery(getHttp3(program2, envName), opts.id);
|
|
11255
11805
|
success(result, "dynatrace", "trace", start);
|
|
11256
11806
|
} catch (err) {
|
|
11257
11807
|
fail(err, "dynatrace", "trace", start);
|
|
@@ -11882,7 +12432,7 @@ Services:
|
|
|
11882
12432
|
splitio Split.IO (feature flags, Change Requests)
|
|
11883
12433
|
figma Figma (files, comments, version history)
|
|
11884
12434
|
config Manage pncli configuration
|
|
11885
|
-
skills
|
|
12435
|
+
skills Install and manage agent skills (Codex, GitHub Copilot, Claude Code)
|
|
11886
12436
|
jwt JWT token utilities (decode header and payload)
|
|
11887
12437
|
`);
|
|
11888
12438
|
program.parseAsync(process.argv).catch((err) => {
|