@opscompass/skills 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +78 -0
  2. package/manifest.json +35 -0
  3. package/package.json +23 -0
  4. package/skills/opscompass-oracle-licensing/.opscompass-runtime/tools/cli-entrypoint.js +15 -0
  5. package/skills/opscompass-oracle-licensing/SKILL.md +211 -0
  6. package/skills/opscompass-oracle-licensing/api/entitlement-compliance/entitlement-compliance.json +189 -0
  7. package/skills/opscompass-oracle-licensing/api/entitlement-deployment/entitlement-deployment +26 -0
  8. package/skills/opscompass-oracle-licensing/api/entitlement-usage/entitlement-usage.json +114 -0
  9. package/skills/opscompass-oracle-licensing/assets/mock-entitlements/deployment-details.json +61 -0
  10. package/skills/opscompass-oracle-licensing/assets/mock-entitlements/license-usage-map.json +70 -0
  11. package/skills/opscompass-oracle-licensing/assets/mock-entitlements/overall-compliance.json +36 -0
  12. package/skills/opscompass-oracle-licensing/package.json +3 -0
  13. package/skills/opscompass-oracle-licensing/references/evidence-model.md +90 -0
  14. package/skills/opscompass-oracle-licensing/references/report-structure.md +54 -0
  15. package/skills/opscompass-oracle-licensing/scripts/fake-entitlements-api.tool.js +31 -0
  16. package/skills/opscompass-oracle-licensing/scripts/fake_entitlements_api.js +72 -0
  17. package/skills/opscompass-oracle-licensing/scripts/tools.js +2 -0
  18. package/skills/opscompass-resources/.opscompass-runtime/tools/cli-entrypoint.js +15 -0
  19. package/skills/opscompass-resources/.opscompass-runtime/tools/index.js +2 -0
  20. package/skills/opscompass-resources/.opscompass-runtime/tools/opsc-cli-context.js +24 -0
  21. package/skills/opscompass-resources/.opscompass-runtime/tools/opsc-cli-defaults.js +125 -0
  22. package/skills/opscompass-resources/.opscompass-runtime/tools/opsc-cli.js +90 -0
  23. package/skills/opscompass-resources/SKILL.md +335 -0
  24. package/skills/opscompass-resources/package.json +3 -0
  25. package/skills/opscompass-resources/references/resources-commands.md +178 -0
  26. package/skills/opscompass-resources/scripts/build-resources-command.tool.js +53 -0
  27. package/skills/opscompass-resources/scripts/build_resources_command.js +240 -0
  28. package/skills/opscompass-resources/scripts/list-and-fetch-latest-configs.tool.js +116 -0
  29. package/skills/opscompass-resources/scripts/list_and_fetch_latest_configs.js +714 -0
  30. package/skills/opscompass-resources/scripts/map-oracle-workloads-to-platform-cpus.tool.js +24 -0
  31. package/skills/opscompass-resources/scripts/map_oracle_workloads_to_platform_cpus.js +273 -0
  32. package/skills/opscompass-resources/scripts/opscompass-defaults.tool.js +80 -0
  33. package/skills/opscompass-resources/scripts/opscompass-resources.tool.js +51 -0
  34. package/skills/opscompass-resources/scripts/summarize-latest-configuration.tool.js +24 -0
  35. package/skills/opscompass-resources/scripts/summarize-resources-list.tool.js +24 -0
  36. package/skills/opscompass-resources/scripts/summarize_latest_configuration.js +254 -0
  37. package/skills/opscompass-resources/scripts/summarize_resources_list.js +126 -0
  38. package/skills/opscompass-resources/scripts/tools.js +16 -0
@@ -0,0 +1,24 @@
1
+ import { asConfigList, summarize } from "./map_oracle_workloads_to_platform_cpus.js";
2
+ export const mapOracleWorkloadsToPlatformCpusTool = {
3
+ name: "opscompass_resources_map_oracle_workloads_to_platform_cpus",
4
+ label: "map Oracle workloads to platform CPUs",
5
+ description: "Map Oracle workloads to Nutanix or VMware VM vCPU counts.",
6
+ parameters: {
7
+ type: "object",
8
+ properties: {
9
+ payload: { type: "array", items: { type: "object", additionalProperties: true } },
10
+ top: { type: "integer", minimum: 1 },
11
+ },
12
+ required: ["payload"],
13
+ additionalProperties: false,
14
+ },
15
+ async execute(input) {
16
+ const value = input;
17
+ if (!Array.isArray(value?.payload))
18
+ throw new Error("expected a JSON object or array of objects");
19
+ const top = value.top ?? 50;
20
+ if (!Number.isInteger(top) || top <= 0)
21
+ throw new Error("--top must be greater than 0.");
22
+ return { content: [{ type: "text", text: summarize(asConfigList(value.payload), top) }] };
23
+ },
24
+ };
@@ -0,0 +1,273 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { isMainModule } from "../.opscompass-runtime/tools/cli-entrypoint.js";
3
+ function parseArgs(argv) {
4
+ const args = { input: null, top: 50 };
5
+ for (let i = 0; i < argv.length; i += 1) {
6
+ if (argv[i] === "--input") {
7
+ if (++i >= argv.length)
8
+ throw new Error("argument --input: expected one argument");
9
+ args.input = argv[i];
10
+ }
11
+ else if (argv[i] === "--top") {
12
+ if (++i >= argv.length)
13
+ throw new Error("argument --top: expected one argument");
14
+ args.top = Number(argv[i]);
15
+ if (!Number.isInteger(args.top))
16
+ throw new Error(`argument --top: invalid int value: '${argv[i]}'`);
17
+ }
18
+ else if (argv[i] === "--help" || argv[i] === "-h") {
19
+ console.log("Map Oracle workloads to Nutanix/VMware VM vCPU counts.");
20
+ process.exit(0);
21
+ }
22
+ else
23
+ throw new Error(`unrecognized arguments: ${argv[i]}`);
24
+ }
25
+ return args;
26
+ }
27
+ async function loadPayload(filePath) {
28
+ if (filePath)
29
+ return JSON.parse(readFileSync(filePath, "utf8"));
30
+ const chunks = [];
31
+ for await (const chunk of process.stdin)
32
+ chunks.push(chunk);
33
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
34
+ }
35
+ export function asConfigList(payload) {
36
+ if (payload !== null &&
37
+ typeof payload === "object" &&
38
+ !Array.isArray(payload))
39
+ return [payload];
40
+ if (Array.isArray(payload))
41
+ return payload.filter((item) => item !== null && typeof item === "object" && !Array.isArray(item));
42
+ throw new Error("expected a JSON object or array of objects");
43
+ }
44
+ export function getDocument(cfg) {
45
+ return cfg.document !== null &&
46
+ typeof cfg.document === "object" &&
47
+ !Array.isArray(cfg.document)
48
+ ? cfg.document
49
+ : {};
50
+ }
51
+ export function cfgType(cfg) {
52
+ return String(cfg.type || cfg._list_type || "").trim();
53
+ }
54
+ export function cfgName(cfg) {
55
+ return String(cfg.name || cfg._list_name || "").trim();
56
+ }
57
+ export function scalarInt(value) {
58
+ if (typeof value === "boolean" || value === null || value === undefined)
59
+ return null;
60
+ if (typeof value === "number" && Number.isFinite(value))
61
+ return Math.trunc(value);
62
+ if (typeof value === "string" && /^-?\d+$/.test(value.trim()))
63
+ return Number.parseInt(value.trim(), 10);
64
+ return null;
65
+ }
66
+ export function normalizeName(value) {
67
+ return String(value)
68
+ .toLowerCase()
69
+ .replace(/[^a-z0-9]+/g, "");
70
+ }
71
+ export function oracleHostnameCandidates(cfg) {
72
+ const doc = getDocument(cfg), candidates = [];
73
+ for (const key of ["hostname", "hostName", "serverName"])
74
+ if (typeof doc[key] === "string" && doc[key].trim())
75
+ candidates.push(doc[key].trim());
76
+ const name = cfgName(cfg);
77
+ if (name.includes("(") && name.includes(")")) {
78
+ const start = name.indexOf("(") + 1, end = name.lastIndexOf(")");
79
+ const inner = name.slice(start, end).trim();
80
+ if (inner)
81
+ candidates.push(inner);
82
+ }
83
+ if (name)
84
+ candidates.push(name);
85
+ return [...new Set(candidates.map(normalizeName).filter(Boolean))];
86
+ }
87
+ export function recursiveFindFirst(obj, keys) {
88
+ if (obj !== null && typeof obj === "object") {
89
+ if (Array.isArray(obj)) {
90
+ for (const value of obj) {
91
+ const found = recursiveFindFirst(value, keys);
92
+ if (found !== undefined)
93
+ return found;
94
+ }
95
+ }
96
+ else {
97
+ for (const [key, value] of Object.entries(obj))
98
+ if (keys.has(key.toLowerCase()))
99
+ return value;
100
+ for (const value of Object.values(obj)) {
101
+ const found = recursiveFindFirst(value, keys);
102
+ if (found !== undefined)
103
+ return found;
104
+ }
105
+ }
106
+ }
107
+ return undefined;
108
+ }
109
+ export function extractNutanixVcpu(doc) {
110
+ let sockets = scalarInt(doc.numSockets), cores = scalarInt(doc.numCoresPerSocket), threads = scalarInt(doc.numThreadsPerCore);
111
+ if (threads === null)
112
+ threads = 1;
113
+ if (sockets && cores && threads)
114
+ return [
115
+ sockets * cores * threads,
116
+ `numSockets=${sockets}, numCoresPerSocket=${cores}, numThreadsPerCore=${threads}`,
117
+ ];
118
+ const explicit = scalarInt(doc.numVcpus) ?? scalarInt(doc.numVcpusTotal);
119
+ return explicit !== null
120
+ ? [explicit, "explicit Nutanix VM vCPU field"]
121
+ : [null, "Nutanix CPU fields missing"];
122
+ }
123
+ export function extractVmwareVcpu(doc) {
124
+ for (const value of [
125
+ doc.numCpu,
126
+ doc.numCPU,
127
+ doc.cpuCount,
128
+ doc.numCpus,
129
+ doc.vcpuCount,
130
+ ]) {
131
+ const parsed = scalarInt(value);
132
+ if (parsed !== null)
133
+ return [parsed, "direct VMware VM CPU count field"];
134
+ }
135
+ const hardware = doc.hardware;
136
+ if (hardware !== null &&
137
+ typeof hardware === "object" &&
138
+ !Array.isArray(hardware)) {
139
+ const cpuHardware = hardware;
140
+ const numCpu = scalarInt(cpuHardware.numCPU) ?? scalarInt(cpuHardware.numCpu), cores = scalarInt(cpuHardware.numCoresPerSocket);
141
+ if (numCpu !== null)
142
+ return [
143
+ numCpu,
144
+ cores !== null
145
+ ? `hardware.numCPU=${numCpu}, hardware.numCoresPerSocket=${cores}`
146
+ : `hardware.numCPU=${numCpu}`,
147
+ ];
148
+ }
149
+ const numCpu = scalarInt(recursiveFindFirst(doc, new Set(["numcpu", "numcpus", "cpucount", "vcpucount"]))), cores = scalarInt(recursiveFindFirst(doc, new Set(["numcorespersocket"])));
150
+ if (numCpu !== null)
151
+ return [
152
+ numCpu,
153
+ cores !== null
154
+ ? `nested numCPU=${numCpu}, numCoresPerSocket=${cores}`
155
+ : "nested VMware VM CPU count field",
156
+ ];
157
+ return [null, "VMware CPU fields missing"];
158
+ }
159
+ export function vmIdentityCandidates(cfg) {
160
+ const doc = getDocument(cfg), candidates = [doc.name, cfgName(cfg), doc.guestHostname, doc.hostName]
161
+ .filter((value) => typeof value === "string" && Boolean(value.trim()))
162
+ .map((value) => normalizeName(value.trim()))
163
+ .filter(Boolean);
164
+ return [...new Set(candidates)];
165
+ }
166
+ export function extractVmCpu(cfg) {
167
+ const type = cfgType(cfg);
168
+ if (type === "Nutanix/Vm")
169
+ return extractNutanixVcpu(getDocument(cfg));
170
+ if (type === "vcenter/virtualMachine")
171
+ return extractVmwareVcpu(getDocument(cfg));
172
+ return [null, "unsupported VM type"];
173
+ }
174
+ export function platformLabel(cfg) {
175
+ const type = cfgType(cfg);
176
+ return type === "Nutanix/Vm"
177
+ ? "Nutanix"
178
+ : type === "vcenter/virtualMachine"
179
+ ? "VMware"
180
+ : type || "unknown";
181
+ }
182
+ export function summarize(configs, topN) {
183
+ const oracle = configs.filter((cfg) => cfgType(cfg) === "databases/oracle"), vms = configs.filter((cfg) => ["Nutanix/Vm", "vcenter/virtualMachine"].includes(cfgType(cfg)));
184
+ if (!oracle.length || !vms.length)
185
+ return "";
186
+ const index = new Map();
187
+ for (const vm of vms)
188
+ for (const candidate of vmIdentityCandidates(vm)) {
189
+ if (!index.has(candidate))
190
+ index.set(candidate, []);
191
+ index.get(candidate).push(vm);
192
+ }
193
+ const matched = [], unmatched = [];
194
+ for (const cfg of oracle) {
195
+ const doc = getDocument(cfg), name = cfgName(cfg) || String(doc.name || "(unnamed oracle db)"), host = String(doc.hostname || doc.hostName || "").trim();
196
+ let vm = null;
197
+ for (const candidate of oracleHostnameCandidates(cfg))
198
+ if (index.get(candidate)?.length) {
199
+ vm = index.get(candidate)[0];
200
+ break;
201
+ }
202
+ if (!vm) {
203
+ unmatched.push({
204
+ oracleName: name,
205
+ oracleHost: host || "(unknown host)",
206
+ reason: "no matching Nutanix/VMware VM config found",
207
+ });
208
+ continue;
209
+ }
210
+ const vmDoc = getDocument(vm), [count, basis] = extractVmCpu(vm);
211
+ matched.push({
212
+ oracleName: name,
213
+ oracleHost: host || "(unknown host)",
214
+ vmName: String(vmDoc.name || cfgName(vm) || "(unnamed vm)").trim(),
215
+ platform: platformLabel(vm),
216
+ cluster: vmDoc.cluster && typeof vmDoc.cluster === "object"
217
+ ? String(vmDoc.cluster.name || "").trim()
218
+ : "",
219
+ guestHostname: String(vmDoc.guestHostname || "").trim(),
220
+ vcpuCount: count === null ? "?" : String(count),
221
+ cpuBasis: basis,
222
+ });
223
+ }
224
+ const lines = [
225
+ "Oracle workload to platform vCPU summary",
226
+ `Oracle workload configurations collected: ${oracle.length}`,
227
+ `Platform VM configurations collected: ${vms.length}`,
228
+ `Oracle workloads matched to VMs: ${matched.length}`,
229
+ `Oracle workloads without VM match: ${unmatched.length}`,
230
+ "",
231
+ `Top ${topN} Oracle workload mappings:`,
232
+ ];
233
+ if (matched.length)
234
+ for (const item of matched.slice(0, topN)) {
235
+ const context = [`platform=${item.platform}`];
236
+ if (item.cluster)
237
+ context.push(`cluster=${item.cluster}`);
238
+ if (item.guestHostname &&
239
+ normalizeName(item.guestHostname) !== normalizeName(item.vmName))
240
+ context.push(`guest=${item.guestHostname}`);
241
+ context.push(`cpu_basis=${item.cpuBasis}`);
242
+ lines.push(`- ${item.oracleName} -> ${item.vmName} -> provisioned_vcpu=${item.vcpuCount} (${context.join(", ")})`);
243
+ }
244
+ else
245
+ lines.push("- none");
246
+ if (unmatched.length) {
247
+ lines.push("", `Top ${topN} Oracle workloads without VM match:`);
248
+ for (const item of unmatched.slice(0, topN))
249
+ lines.push(`- ${item.oracleName} (host=${item.oracleHost}): ${item.reason}`);
250
+ }
251
+ return lines.join("\n");
252
+ }
253
+ export async function main(argv = process.argv.slice(2)) {
254
+ try {
255
+ const args = parseArgs(argv);
256
+ if (args.top <= 0) {
257
+ console.error("Error: --top must be greater than 0.");
258
+ return 2;
259
+ }
260
+ const configs = asConfigList(await loadPayload(args.input));
261
+ console.log(summarize(configs, args.top));
262
+ return 0;
263
+ }
264
+ catch (error) {
265
+ console.error(error instanceof SyntaxError
266
+ ? `Error: invalid JSON: ${error.message}`
267
+ : `Error: ${error instanceof Error ? error.message : String(error)}`);
268
+ return 2;
269
+ }
270
+ }
271
+ if (isMainModule(import.meta.url)) {
272
+ process.exitCode = await main();
273
+ }
@@ -0,0 +1,80 @@
1
+ import { runCli } from "../.opscompass-runtime/tools/opsc-cli.js";
2
+ import { buildSetDefaultsCommand, buildSetupCommand, commandText, getDefaultCompany, getDefaultsPath, loadDefaults, } from "../.opscompass-runtime/tools/index.js";
3
+ export const opscompassDefaultsTool = {
4
+ name: "opscompass_resources_defaults",
5
+ label: "inspect Opscompass defaults",
6
+ description: "List or build safe Opscompass defaults commands.",
7
+ parameters: {
8
+ type: "object",
9
+ properties: {
10
+ command: { type: "string", enum: ["list", "set", "setup", "ignore"] },
11
+ company: { type: "string" },
12
+ account: { type: "string" },
13
+ monitoringConfigurationId: { type: "string" },
14
+ maskValues: { type: "array", items: { type: "string" } },
15
+ dryRun: { type: "boolean" },
16
+ cli: { type: "boolean" },
17
+ nonInteractive: { type: "boolean" },
18
+ },
19
+ required: ["command"],
20
+ additionalProperties: false,
21
+ },
22
+ async execute(input, context) {
23
+ const value = (input ?? {});
24
+ const command = value.command;
25
+ if (!command || !["list", "set", "setup", "ignore"].includes(command))
26
+ throw new Error(`invalid command: ${command}`);
27
+ if (command === "list") {
28
+ if (value.cli)
29
+ return {
30
+ content: [
31
+ {
32
+ type: "text",
33
+ text: await runCli(context, ["defaults", "list-defaults"]),
34
+ },
35
+ ],
36
+ };
37
+ const defaultsPath = getDefaultsPath(context);
38
+ return {
39
+ content: [
40
+ {
41
+ type: "text",
42
+ text: JSON.stringify({
43
+ company: getDefaultCompany(defaultsPath),
44
+ defaults: loadDefaults(defaultsPath),
45
+ }, null, 2),
46
+ },
47
+ ],
48
+ };
49
+ }
50
+ if (command === "set") {
51
+ const args = buildSetDefaultsCommand(value, context);
52
+ if (!value.dryRun)
53
+ await runCli(context, args.slice(1));
54
+ return { content: [{ type: "text", text: commandText(args) }] };
55
+ }
56
+ if (command === "setup") {
57
+ const args = buildSetupCommand(value, context);
58
+ if (!value.dryRun)
59
+ await runCli(context, args.slice(1));
60
+ return { content: [{ type: "text", text: commandText(args) }] };
61
+ }
62
+ const company = value.company ?? "<company>";
63
+ return {
64
+ content: [
65
+ {
66
+ type: "text",
67
+ text: commandText([
68
+ context.cliCommand,
69
+ "resources",
70
+ "list",
71
+ "--company",
72
+ company,
73
+ "--resource-types",
74
+ "databases/oracle",
75
+ ]),
76
+ },
77
+ ],
78
+ };
79
+ },
80
+ };
@@ -0,0 +1,51 @@
1
+ import { runCli } from "../.opscompass-runtime/tools/opsc-cli.js";
2
+ import { commandText } from "./build_resources_command.js";
3
+ const ACTIONS = [
4
+ "resources list",
5
+ "resources get-latest-configuration",
6
+ "resources get-compliance",
7
+ ];
8
+ export const opscompassResourcesTool = {
9
+ name: "opscompass_resources",
10
+ label: "opscompass resources",
11
+ description: "Run approved read-only Opscompass resource commands.",
12
+ promptSnippet: "Run approved Opscompass resource queries",
13
+ promptGuidelines: [
14
+ "Use numeric --id values from resources list for follow-up actions.",
15
+ "Do not use --resource-id or --resource-ids.",
16
+ "When reporting an executed command, copy the complete command at the start of the tool result, including its CLI identity and all flags. Do not reconstruct it from the generic tool label or production examples.",
17
+ ],
18
+ parameters: {
19
+ type: "object",
20
+ properties: {
21
+ action: { type: "string", enum: [...ACTIONS] },
22
+ args: { type: "array", items: { type: "string" } },
23
+ },
24
+ required: ["action", "args"],
25
+ additionalProperties: false,
26
+ },
27
+ async execute(input, context) {
28
+ if (!input || typeof input !== "object")
29
+ throw new Error("Opscompass action input must be an object.");
30
+ const value = input;
31
+ if (typeof value.action !== "string" ||
32
+ !ACTIONS.includes(value.action)) {
33
+ throw new Error("Only approved Opscompass resources actions are allowed.");
34
+ }
35
+ if (!Array.isArray(value.args) ||
36
+ value.args.length > 40 ||
37
+ value.args.some((arg) => typeof arg !== "string" || arg.length > 1_000 || arg.includes("\0"))) {
38
+ throw new Error("Opscompass action arguments are invalid.");
39
+ }
40
+ const args = value.args;
41
+ if (args.some((arg) => arg === "--resource-id" || arg === "--resource-ids")) {
42
+ throw new Error("Use numeric --id values from resources list; resource-id flags are not allowed.");
43
+ }
44
+ const command = [context.cliCommand, ...value.action.split(" "), ...args];
45
+ const output = await runCli(context, command.slice(1));
46
+ // WHY: Keep the executed command and output together, matching the builder's receipt.
47
+ return {
48
+ content: [{ type: "text", text: `${commandText(command)}\n\n${output}` }],
49
+ };
50
+ },
51
+ };
@@ -0,0 +1,24 @@
1
+ import { asConfigList, summarizeMany } from "./summarize_latest_configuration.js";
2
+ export const summarizeLatestConfigurationTool = {
3
+ name: "opscompass_resources_summarize_latest_configuration",
4
+ label: "summarize latest configuration",
5
+ description: "Summarize one or more latest-configuration JSON payloads.",
6
+ parameters: {
7
+ type: "object",
8
+ properties: {
9
+ payload: { type: "array", items: { type: "object", additionalProperties: true } },
10
+ top: { type: "integer", minimum: 1 },
11
+ },
12
+ required: ["payload"],
13
+ additionalProperties: false,
14
+ },
15
+ async execute(input) {
16
+ const value = input;
17
+ if (!Array.isArray(value?.payload))
18
+ throw new Error("expected a JSON object or array of objects");
19
+ const top = value.top ?? 5;
20
+ if (!Number.isInteger(top) || top <= 0)
21
+ throw new Error("--top must be greater than 0.");
22
+ return { content: [{ type: "text", text: summarizeMany(asConfigList(value.payload), top) }] };
23
+ },
24
+ };
@@ -0,0 +1,24 @@
1
+ import { summarize } from "./summarize_resources_list.js";
2
+ export const summarizeResourcesListTool = {
3
+ name: "opscompass_resources_summarize_resources_list",
4
+ label: "summarize resources list",
5
+ description: "Summarize a resources list JSON payload.",
6
+ parameters: {
7
+ type: "object",
8
+ properties: {
9
+ payload: { type: "array", items: { type: "object", additionalProperties: true } },
10
+ top: { type: "integer", minimum: 1 },
11
+ },
12
+ required: ["payload"],
13
+ additionalProperties: false,
14
+ },
15
+ async execute(input) {
16
+ const value = input;
17
+ if (!Array.isArray(value?.payload))
18
+ throw new Error("expected top-level JSON array.");
19
+ const top = value.top ?? 10;
20
+ if (!Number.isInteger(top) || top <= 0)
21
+ throw new Error("--top must be greater than 0.");
22
+ return { content: [{ type: "text", text: summarize(value.payload, top) }] };
23
+ },
24
+ };