@havesomecode/kibana-mcp-server 1.2.0 → 1.3.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 +6 -2
- package/dist/src/bin/kibana-mcp-server.js +54 -0
- package/dist/src/commands/context.js +1 -0
- package/dist/src/commands/serve.js +19 -0
- package/dist/src/commands/setup.js +132 -0
- package/dist/src/kibana_client.js +267 -90
- package/dist/src/schema_catalog.js +20 -6
- package/dist/src/server.js +63 -16
- package/dist/src/tools/configure_index.js +2 -2
- package/dist/src/tools/describe_fields.js +2 -2
- package/dist/src/tools/filter.js +1 -0
- package/dist/src/tools/query.js +5 -2
- package/dist/src/utils/cli_io.js +11 -0
- package/dist/src/utils/cli_prompts.js +67 -0
- package/dist/src/utils/cli_ui.js +19 -0
- package/package.json +6 -4
- package/dist/src/cli.js +0 -323
- package/dist/src/index.js +0 -10
|
@@ -43,7 +43,7 @@ export async function executeConfigureIndex(inputValue, options) {
|
|
|
43
43
|
JSON.stringify(existingSource.backend.index) !== JSON.stringify(proposedSource.backend.index)) {
|
|
44
44
|
throw new Error(`Source id '${proposedSource.id}' already refers to a different index. Re-run with replace=true only after the user explicitly approves replacement.`);
|
|
45
45
|
}
|
|
46
|
-
const discoveredFields = await options.kibanaClient.describeFields(proposedSource);
|
|
46
|
+
const discoveredFields = await options.kibanaClient.describeFields(proposedSource, options.callerSignal);
|
|
47
47
|
if (discoveredFields.length === 0) {
|
|
48
48
|
throw new Error(`Explicit index pattern '${input.index}' resolved to no fields. Check the index and Kibana permissions.`);
|
|
49
49
|
}
|
|
@@ -65,7 +65,7 @@ export async function executeConfigureIndex(inputValue, options) {
|
|
|
65
65
|
resolvedNestedFilters: [],
|
|
66
66
|
resolvedSortBy: verifiedSource.timeField,
|
|
67
67
|
advisories: [],
|
|
68
|
-
});
|
|
68
|
+
}, options.callerSignal);
|
|
69
69
|
const sources = existingSource
|
|
70
70
|
? existingCatalog.sources.map((source) => source.id === verifiedSource.id ? verifiedSource : source)
|
|
71
71
|
: [...existingCatalog.sources, verifiedSource];
|
|
@@ -21,9 +21,9 @@ export const describeFieldsOutputSchema = z.object({
|
|
|
21
21
|
preferred_exact_field: z.string().optional(),
|
|
22
22
|
})),
|
|
23
23
|
});
|
|
24
|
-
export async function executeDescribeFields(input, sourceCatalog, schemaCatalog) {
|
|
24
|
+
export async function executeDescribeFields(input, sourceCatalog, schemaCatalog, callerSignal) {
|
|
25
25
|
const [source] = sourceCatalog.getRequiredSources([input.source_id]);
|
|
26
|
-
const fields = await schemaCatalog.getFields(source);
|
|
26
|
+
const fields = await schemaCatalog.getFields(source, callerSignal);
|
|
27
27
|
const filteredFields = schemaCatalog.filterFields(fields, input.query, input.limit);
|
|
28
28
|
return {
|
|
29
29
|
source_id: source.id,
|
package/dist/src/tools/filter.js
CHANGED
|
@@ -111,6 +111,7 @@ export async function executeFilter(input, sourceCatalog, kibanaClient, options)
|
|
|
111
111
|
resolveFieldAliases: false,
|
|
112
112
|
resolvePreferredExactFields: true,
|
|
113
113
|
schemaCatalog: options?.schemaCatalog,
|
|
114
|
+
callerSignal: options?.callerSignal,
|
|
114
115
|
});
|
|
115
116
|
}
|
|
116
117
|
export function formatFilterResult(result) {
|
package/dist/src/tools/query.js
CHANGED
|
@@ -234,9 +234,12 @@ export async function executeQuery(input, sourceCatalog, kibanaClient, options)
|
|
|
234
234
|
if (schemaCatalog && needsSchemaResolution) {
|
|
235
235
|
for (const source of sources) {
|
|
236
236
|
try {
|
|
237
|
-
sourceSchemas.set(source.id, await schemaCatalog.getFields(source));
|
|
237
|
+
sourceSchemas.set(source.id, await schemaCatalog.getFields(source, options?.callerSignal));
|
|
238
238
|
}
|
|
239
239
|
catch (error) {
|
|
240
|
+
if (options?.callerSignal?.aborted) {
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
240
243
|
sourceSchemaErrors.set(source.id, error instanceof Error ? error.message : String(error));
|
|
241
244
|
}
|
|
242
245
|
}
|
|
@@ -246,7 +249,7 @@ export async function executeQuery(input, sourceCatalog, kibanaClient, options)
|
|
|
246
249
|
...(sourceSchemas.size > 0 ? { sourceSchemas } : {}),
|
|
247
250
|
...(sourceSchemaErrors.size > 0 ? { sourceSchemaErrors } : {}),
|
|
248
251
|
});
|
|
249
|
-
const executions = await kibanaClient.executeMany(plan.sourceQueries);
|
|
252
|
+
const executions = await kibanaClient.executeMany(plan.sourceQueries, options?.callerSignal);
|
|
250
253
|
return queryOutputSchema.parse(normalizeQueryResponse(plan, executions));
|
|
251
254
|
}
|
|
252
255
|
export function formatQueryResult(result) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function createCliIo(overrides = {}) {
|
|
2
|
+
return {
|
|
3
|
+
stdout: overrides.stdout ?? ((text) => process.stdout.write(`${text}\n`)),
|
|
4
|
+
stderr: overrides.stderr ?? ((text) => process.stderr.write(`${text}\n`)),
|
|
5
|
+
stdin: overrides.stdin ?? process.stdin,
|
|
6
|
+
stdoutStream: overrides.stdoutStream ?? process.stdout,
|
|
7
|
+
stdinIsTTY: overrides.stdinIsTTY,
|
|
8
|
+
env: overrides.env ?? process.env,
|
|
9
|
+
isStdoutOverridden: overrides.stdout !== undefined,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { cancel, confirm, isCancel, log, password, text } from "@clack/prompts";
|
|
2
|
+
export class PromptCancelledError extends Error {
|
|
3
|
+
constructor() {
|
|
4
|
+
super("Setup cancelled.");
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export async function createPromptIo(io) {
|
|
8
|
+
const stdinIsTTY = io.stdinIsTTY ?? Boolean(io.stdin.isTTY);
|
|
9
|
+
if (!stdinIsTTY)
|
|
10
|
+
return createQueuedPromptIo(io.stdout, await readQueuedAnswers(io.stdin));
|
|
11
|
+
return {
|
|
12
|
+
info: (message) => log.info(message, { output: io.stdoutStream }),
|
|
13
|
+
async prompt(message, options = {}) {
|
|
14
|
+
return unwrap(await (options.secret ? password : text)({
|
|
15
|
+
message,
|
|
16
|
+
defaultValue: options.defaultValue,
|
|
17
|
+
input: io.stdin,
|
|
18
|
+
output: io.stdoutStream,
|
|
19
|
+
}), io.stdoutStream);
|
|
20
|
+
},
|
|
21
|
+
async confirm(message, defaultValue = false) {
|
|
22
|
+
return unwrap(await confirm({
|
|
23
|
+
message,
|
|
24
|
+
initialValue: defaultValue,
|
|
25
|
+
input: io.stdin,
|
|
26
|
+
output: io.stdoutStream,
|
|
27
|
+
}), io.stdoutStream);
|
|
28
|
+
},
|
|
29
|
+
async close() { },
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function unwrap(answer, output) {
|
|
33
|
+
if (isCancel(answer)) {
|
|
34
|
+
cancel("Setup cancelled.", { output });
|
|
35
|
+
throw new PromptCancelledError();
|
|
36
|
+
}
|
|
37
|
+
return answer;
|
|
38
|
+
}
|
|
39
|
+
function createQueuedPromptIo(stdout, answers) {
|
|
40
|
+
let index = 0;
|
|
41
|
+
const consume = (label) => {
|
|
42
|
+
const answer = answers[index++];
|
|
43
|
+
if (answer === undefined)
|
|
44
|
+
throw new Error(`No stdin answer available for prompt '${label}'.`);
|
|
45
|
+
return answer;
|
|
46
|
+
};
|
|
47
|
+
return {
|
|
48
|
+
info: stdout,
|
|
49
|
+
async prompt(message, options = {}) {
|
|
50
|
+
const suffix = options.defaultValue ? ` [${options.defaultValue}]` : "";
|
|
51
|
+
stdout(`${message}${suffix}:`);
|
|
52
|
+
return consume(message) || options.defaultValue || "";
|
|
53
|
+
},
|
|
54
|
+
async confirm(message, defaultValue = false) {
|
|
55
|
+
stdout(`${message} ${defaultValue ? "[Y/n]" : "[y/N]"}:`);
|
|
56
|
+
const answer = consume(message).trim().toLowerCase();
|
|
57
|
+
return answer ? answer === "y" || answer === "yes" : defaultValue;
|
|
58
|
+
},
|
|
59
|
+
async close() { },
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
async function readQueuedAnswers(stdin) {
|
|
63
|
+
let raw = "";
|
|
64
|
+
for await (const chunk of stdin)
|
|
65
|
+
raw += typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
66
|
+
return raw.replaceAll("\r\n", "\n").split("\n");
|
|
67
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { log } from "@clack/prompts";
|
|
2
|
+
export function createCliUi(io) {
|
|
3
|
+
return {
|
|
4
|
+
success(message) {
|
|
5
|
+
if (io.isStdoutOverridden) {
|
|
6
|
+
io.stdout(message);
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
log.success(message, { output: io.stdoutStream });
|
|
10
|
+
},
|
|
11
|
+
error(message) {
|
|
12
|
+
if (io.isStdoutOverridden) {
|
|
13
|
+
io.stderr(message);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
log.error(message, { output: io.stdoutStream });
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@havesomecode/kibana-mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Read-only MCP server for Kibana-backed log investigation and schema-aware querying",
|
|
6
6
|
"homepage": "https://havesomecode.github.io/kibana-mcp-server/",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"type": "module",
|
|
12
12
|
"main": "dist/src/mcp_entry.js",
|
|
13
13
|
"bin": {
|
|
14
|
-
"kibana-mcp-server": "dist/src/
|
|
14
|
+
"kibana-mcp-server": "dist/src/bin/kibana-mcp-server.js"
|
|
15
15
|
},
|
|
16
16
|
"publishConfig": {
|
|
17
17
|
"access": "public",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"dev": "tsx src/mcp_entry.ts",
|
|
41
41
|
"release": "semantic-release",
|
|
42
42
|
"release:dry-run": "semantic-release --dry-run",
|
|
43
|
-
"setup": "node dist/src/
|
|
43
|
+
"setup": "node dist/src/bin/kibana-mcp-server.js setup",
|
|
44
44
|
"start": "node dist/src/mcp_entry.js",
|
|
45
45
|
"test": "vitest run",
|
|
46
46
|
"verify:release-contract": "node scripts/verify-release-contract.mjs",
|
|
@@ -50,7 +50,9 @@
|
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@cfworker/json-schema": "^4.1.1",
|
|
53
|
+
"@clack/prompts": "^1.8.0",
|
|
53
54
|
"@modelcontextprotocol/server": "2.0.0",
|
|
55
|
+
"commander": "^14.0.3",
|
|
54
56
|
"zod": "^4.0.0"
|
|
55
57
|
},
|
|
56
58
|
"devDependencies": {
|
|
@@ -60,7 +62,7 @@
|
|
|
60
62
|
"@types/node": "^25.5.2",
|
|
61
63
|
"semantic-release": "^25.0.1",
|
|
62
64
|
"tsx": "^4.0.0",
|
|
63
|
-
"typescript": "^
|
|
65
|
+
"typescript": "^7.0.2",
|
|
64
66
|
"vitest": "^4.1.3"
|
|
65
67
|
}
|
|
66
68
|
}
|
package/dist/src/cli.js
DELETED
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
import { createInterface } from "node:readline/promises";
|
|
2
|
-
import { Writable } from "node:stream";
|
|
3
|
-
import { parseArgs } from "node:util";
|
|
4
|
-
import { runBootstrap } from "./bootstrap.js";
|
|
5
|
-
import { PROFILE_NAME_ENV } from "./config.js";
|
|
6
|
-
import { startMcpServer } from "./mcp_runtime.js";
|
|
7
|
-
import { runSetupFlow } from "./setup_flow.js";
|
|
8
|
-
function renderHelp() {
|
|
9
|
-
return [
|
|
10
|
-
"Kibana Log Investigation",
|
|
11
|
-
"",
|
|
12
|
-
"Usage:",
|
|
13
|
-
" kibana-mcp-server [command]",
|
|
14
|
-
"",
|
|
15
|
-
"Commands:",
|
|
16
|
-
" setup Run guided machine setup",
|
|
17
|
-
" bootstrap Verify, save, and register a profile without prompts",
|
|
18
|
-
" serve Start the stdio MCP server",
|
|
19
|
-
" help Show this help output",
|
|
20
|
-
].join("\n");
|
|
21
|
-
}
|
|
22
|
-
function renderBootstrapHelp() {
|
|
23
|
-
return [
|
|
24
|
-
"Usage:",
|
|
25
|
-
" kibana-mcp-server bootstrap [options]",
|
|
26
|
-
" kibana-mcp-server setup [options] # compatible non-interactive alias",
|
|
27
|
-
"",
|
|
28
|
-
"Bootstrap verifies and saves only the Kibana connection; it does not inspect or configure indexes.",
|
|
29
|
-
"After installation, ask the agent to configure a specific index or index pattern.",
|
|
30
|
-
"",
|
|
31
|
-
"Connection options use KIBANA_BASE_URL, KIBANA_USERNAME, and KIBANA_PASSWORD as fallbacks.",
|
|
32
|
-
"Passwords are accepted only through --password-stdin, --password-env <NAME>, or KIBANA_PASSWORD.",
|
|
33
|
-
"",
|
|
34
|
-
"Options:",
|
|
35
|
-
" --profile <name> Saved profile name (default: KIBANA_PROFILE or default)",
|
|
36
|
-
" --url <url> Kibana base URL",
|
|
37
|
-
" --username <name> Kibana basic-auth username",
|
|
38
|
-
" --password-stdin Read the password from stdin",
|
|
39
|
-
" --password-env <name> Read the password from a named environment variable",
|
|
40
|
-
" --client <codex|none> Register Codex or skip client registration (default: codex)",
|
|
41
|
-
" --package <specifier> Exact package specifier used by the Codex registration",
|
|
42
|
-
" --mcp-name <name> Base name for the transport-hashed Codex registration",
|
|
43
|
-
" --replace Explicitly replace an existing catalog with an empty managed catalog",
|
|
44
|
-
" --no-default Do not make this profile the default",
|
|
45
|
-
].join("\n");
|
|
46
|
-
}
|
|
47
|
-
export async function runCli(argv = process.argv.slice(2), io = {}, dependencies = {}) {
|
|
48
|
-
const stdout = io.stdout ?? ((text) => process.stdout.write(`${text}\n`));
|
|
49
|
-
const stderr = io.stderr ?? ((text) => process.stderr.write(`${text}\n`));
|
|
50
|
-
const stdin = io.stdin ?? process.stdin;
|
|
51
|
-
const stdoutStream = io.stdoutStream ?? process.stdout;
|
|
52
|
-
const env = io.env ?? process.env;
|
|
53
|
-
const [command, ...rest] = argv;
|
|
54
|
-
switch (command) {
|
|
55
|
-
case undefined: {
|
|
56
|
-
try {
|
|
57
|
-
await (dependencies.startMcpServerFn ?? startMcpServer)(env);
|
|
58
|
-
return 0;
|
|
59
|
-
}
|
|
60
|
-
catch (error) {
|
|
61
|
-
stderr(error instanceof Error ? error.message : String(error));
|
|
62
|
-
return 1;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
case "setup": {
|
|
66
|
-
if (rest.length > 0) {
|
|
67
|
-
if (rest.includes("--help") || rest.includes("-h")) {
|
|
68
|
-
stdout(renderBootstrapHelp());
|
|
69
|
-
return 0;
|
|
70
|
-
}
|
|
71
|
-
try {
|
|
72
|
-
const options = await parseBootstrapOptions(rest, stdin, env);
|
|
73
|
-
const result = await (dependencies.runBootstrapFn ?? runBootstrap)(options);
|
|
74
|
-
stdout(renderBootstrapResult(result));
|
|
75
|
-
return 0;
|
|
76
|
-
}
|
|
77
|
-
catch (error) {
|
|
78
|
-
stderr(error instanceof Error ? error.message : String(error));
|
|
79
|
-
return 1;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
const promptIo = await createPromptIo(stdout, {
|
|
83
|
-
stdin,
|
|
84
|
-
stdoutStream,
|
|
85
|
-
stdinIsTTY: io.stdinIsTTY,
|
|
86
|
-
});
|
|
87
|
-
try {
|
|
88
|
-
const result = await (dependencies.runSetupFlowFn ?? runSetupFlow)(promptIo);
|
|
89
|
-
stdout(`Saved ${result.profiles.length} environment${result.profiles.length === 1 ? "" : "s"}. Default environment: ${result.defaultProfileName}.`);
|
|
90
|
-
return 0;
|
|
91
|
-
}
|
|
92
|
-
finally {
|
|
93
|
-
await promptIo.close();
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
case "help":
|
|
97
|
-
case "--help":
|
|
98
|
-
case "-h": {
|
|
99
|
-
stdout(renderHelp());
|
|
100
|
-
return 0;
|
|
101
|
-
}
|
|
102
|
-
case "bootstrap": {
|
|
103
|
-
if (rest.includes("--help") || rest.includes("-h")) {
|
|
104
|
-
stdout(renderBootstrapHelp());
|
|
105
|
-
return 0;
|
|
106
|
-
}
|
|
107
|
-
try {
|
|
108
|
-
const options = await parseBootstrapOptions(rest, stdin, env);
|
|
109
|
-
const result = await (dependencies.runBootstrapFn ?? runBootstrap)(options);
|
|
110
|
-
stdout(renderBootstrapResult(result));
|
|
111
|
-
return 0;
|
|
112
|
-
}
|
|
113
|
-
catch (error) {
|
|
114
|
-
stderr(error instanceof Error ? error.message : String(error));
|
|
115
|
-
return 1;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
case "serve": {
|
|
119
|
-
try {
|
|
120
|
-
const { values } = parseArgs({
|
|
121
|
-
args: rest,
|
|
122
|
-
allowPositionals: false,
|
|
123
|
-
strict: true,
|
|
124
|
-
options: {
|
|
125
|
-
profile: { type: "string" },
|
|
126
|
-
},
|
|
127
|
-
});
|
|
128
|
-
const serveEnv = values.profile ? { ...env, [PROFILE_NAME_ENV]: values.profile } : env;
|
|
129
|
-
await (dependencies.startMcpServerFn ?? startMcpServer)(serveEnv);
|
|
130
|
-
}
|
|
131
|
-
catch (error) {
|
|
132
|
-
stderr(error instanceof Error ? error.message : String(error));
|
|
133
|
-
return 1;
|
|
134
|
-
}
|
|
135
|
-
return 0;
|
|
136
|
-
}
|
|
137
|
-
default: {
|
|
138
|
-
stderr(`Unknown command: ${command}`);
|
|
139
|
-
stderr(renderHelp());
|
|
140
|
-
return 1;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
async function parseBootstrapOptions(args, stdin, env) {
|
|
145
|
-
const { values } = parseArgs({
|
|
146
|
-
args,
|
|
147
|
-
allowPositionals: false,
|
|
148
|
-
strict: true,
|
|
149
|
-
options: {
|
|
150
|
-
profile: { type: "string" },
|
|
151
|
-
url: { type: "string" },
|
|
152
|
-
username: { type: "string" },
|
|
153
|
-
"password-stdin": { type: "boolean" },
|
|
154
|
-
"password-env": { type: "string" },
|
|
155
|
-
timeout: { type: "string" },
|
|
156
|
-
client: { type: "string" },
|
|
157
|
-
package: { type: "string" },
|
|
158
|
-
"mcp-name": { type: "string" },
|
|
159
|
-
replace: { type: "boolean" },
|
|
160
|
-
"no-default": { type: "boolean" },
|
|
161
|
-
},
|
|
162
|
-
});
|
|
163
|
-
const baseUrl = values.url?.trim() || env.KIBANA_BASE_URL?.trim();
|
|
164
|
-
if (!baseUrl) {
|
|
165
|
-
throw new Error("Kibana base URL is required via --url or KIBANA_BASE_URL.");
|
|
166
|
-
}
|
|
167
|
-
const username = values.username?.trim() || env.KIBANA_USERNAME?.trim();
|
|
168
|
-
if (!username) {
|
|
169
|
-
throw new Error("Kibana username is required via --username or KIBANA_USERNAME.");
|
|
170
|
-
}
|
|
171
|
-
if (values["password-stdin"] && values["password-env"]) {
|
|
172
|
-
throw new Error("Use only one of --password-stdin or --password-env.");
|
|
173
|
-
}
|
|
174
|
-
let password;
|
|
175
|
-
if (values["password-stdin"]) {
|
|
176
|
-
password = (await readAllInput(stdin)).replace(/\r?\n$/, "");
|
|
177
|
-
}
|
|
178
|
-
else if (values["password-env"]) {
|
|
179
|
-
password = env[values["password-env"]];
|
|
180
|
-
if (password === undefined) {
|
|
181
|
-
throw new Error(`Password environment variable '${values["password-env"]}' is not set.`);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
password = env.KIBANA_PASSWORD;
|
|
186
|
-
}
|
|
187
|
-
if (!password) {
|
|
188
|
-
throw new Error("Kibana password is required via --password-stdin, --password-env, or KIBANA_PASSWORD.");
|
|
189
|
-
}
|
|
190
|
-
const client = values.client ?? "codex";
|
|
191
|
-
if (client !== "codex" && client !== "none") {
|
|
192
|
-
throw new Error("--client must be either 'codex' or 'none'.");
|
|
193
|
-
}
|
|
194
|
-
const timeoutMs = values.timeout === undefined ? undefined : Number(values.timeout);
|
|
195
|
-
if (timeoutMs !== undefined && !Number.isFinite(timeoutMs)) {
|
|
196
|
-
throw new Error("--timeout must be a number of milliseconds.");
|
|
197
|
-
}
|
|
198
|
-
return {
|
|
199
|
-
profileName: values.profile?.trim() || env[PROFILE_NAME_ENV]?.trim() || "default",
|
|
200
|
-
baseUrl,
|
|
201
|
-
username,
|
|
202
|
-
password,
|
|
203
|
-
client,
|
|
204
|
-
packageSpecifier: values.package,
|
|
205
|
-
mcpName: values["mcp-name"],
|
|
206
|
-
makeDefault: !values["no-default"],
|
|
207
|
-
replaceExisting: values.replace ?? false,
|
|
208
|
-
timeoutMs,
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
function renderBootstrapResult(result) {
|
|
212
|
-
return [
|
|
213
|
-
`Bootstrap verified for profile '${result.profileName}'.`,
|
|
214
|
-
result.sourceCount === 0
|
|
215
|
-
? "Source catalog is empty. No Kibana indexes were inspected or configured. Ask the user which index or index pattern to configure before querying logs."
|
|
216
|
-
: `Preserved ${result.sourceCount} explicitly configured source${result.sourceCount === 1 ? "" : "s"}; bootstrap did not inspect them.`,
|
|
217
|
-
result.registered
|
|
218
|
-
? `Codex MCP registration is installed and verified for profile '${result.profileName}'.`
|
|
219
|
-
: "Client registration was skipped by request.",
|
|
220
|
-
].join("\n");
|
|
221
|
-
}
|
|
222
|
-
async function readAllInput(stdin) {
|
|
223
|
-
let rawInput = "";
|
|
224
|
-
for await (const chunk of stdin) {
|
|
225
|
-
rawInput += typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
226
|
-
}
|
|
227
|
-
return rawInput;
|
|
228
|
-
}
|
|
229
|
-
export async function createPromptIo(stdout, options = {}) {
|
|
230
|
-
const stdin = options.stdin ?? process.stdin;
|
|
231
|
-
const stdoutStream = options.stdoutStream ?? process.stdout;
|
|
232
|
-
const stdinIsTTY = options.stdinIsTTY ?? Boolean(stdin.isTTY);
|
|
233
|
-
if (!stdinIsTTY) {
|
|
234
|
-
return createQueuedPromptIo(stdout, await readQueuedAnswers(stdin));
|
|
235
|
-
}
|
|
236
|
-
const maskingOutput = new MaskingWritable(stdoutStream);
|
|
237
|
-
const readline = createInterface({
|
|
238
|
-
input: stdin,
|
|
239
|
-
output: maskingOutput,
|
|
240
|
-
});
|
|
241
|
-
return {
|
|
242
|
-
info(message) {
|
|
243
|
-
stdout(message);
|
|
244
|
-
},
|
|
245
|
-
async prompt(message, options = {}) {
|
|
246
|
-
const suffix = options.defaultValue ? ` [${options.defaultValue}]` : "";
|
|
247
|
-
if (options.secret) {
|
|
248
|
-
maskingOutput.muted = false;
|
|
249
|
-
stdoutStream.write(`${message}: `);
|
|
250
|
-
maskingOutput.muted = true;
|
|
251
|
-
const answer = await readline.question("");
|
|
252
|
-
maskingOutput.muted = false;
|
|
253
|
-
stdoutStream.write("\n");
|
|
254
|
-
return answer || options.defaultValue || "";
|
|
255
|
-
}
|
|
256
|
-
const answer = await readline.question(`${message}${suffix}: `);
|
|
257
|
-
return answer || options.defaultValue || "";
|
|
258
|
-
},
|
|
259
|
-
async confirm(message, defaultValue = false) {
|
|
260
|
-
const answer = await readline.question(`${message} ${defaultValue ? "[Y/n]" : "[y/N]"}: `);
|
|
261
|
-
const normalized = answer.trim().toLowerCase();
|
|
262
|
-
if (!normalized) {
|
|
263
|
-
return defaultValue;
|
|
264
|
-
}
|
|
265
|
-
return normalized === "y" || normalized === "yes";
|
|
266
|
-
},
|
|
267
|
-
async close() {
|
|
268
|
-
await readline.close();
|
|
269
|
-
},
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
function createQueuedPromptIo(stdout, answers) {
|
|
273
|
-
let answerIndex = 0;
|
|
274
|
-
function consumeAnswer(promptLabel) {
|
|
275
|
-
const answer = answers[answerIndex];
|
|
276
|
-
answerIndex += 1;
|
|
277
|
-
if (answer === undefined) {
|
|
278
|
-
throw new Error(`No stdin answer available for prompt '${promptLabel}'.`);
|
|
279
|
-
}
|
|
280
|
-
return answer;
|
|
281
|
-
}
|
|
282
|
-
return {
|
|
283
|
-
info(message) {
|
|
284
|
-
stdout(message);
|
|
285
|
-
},
|
|
286
|
-
async prompt(message, options = {}) {
|
|
287
|
-
const suffix = options.defaultValue ? ` [${options.defaultValue}]` : "";
|
|
288
|
-
stdout(`${message}${suffix}:`);
|
|
289
|
-
const answer = consumeAnswer(message);
|
|
290
|
-
return answer || options.defaultValue || "";
|
|
291
|
-
},
|
|
292
|
-
async confirm(message, defaultValue = false) {
|
|
293
|
-
stdout(`${message} ${defaultValue ? "[Y/n]" : "[y/N]"}:`);
|
|
294
|
-
const normalized = consumeAnswer(message).trim().toLowerCase();
|
|
295
|
-
if (!normalized) {
|
|
296
|
-
return defaultValue;
|
|
297
|
-
}
|
|
298
|
-
return normalized === "y" || normalized === "yes";
|
|
299
|
-
},
|
|
300
|
-
async close() { },
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
async function readQueuedAnswers(stdin) {
|
|
304
|
-
let rawInput = "";
|
|
305
|
-
for await (const chunk of stdin) {
|
|
306
|
-
rawInput += typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
307
|
-
}
|
|
308
|
-
return rawInput.replaceAll("\r\n", "\n").split("\n");
|
|
309
|
-
}
|
|
310
|
-
class MaskingWritable extends Writable {
|
|
311
|
-
target;
|
|
312
|
-
muted = false;
|
|
313
|
-
constructor(target) {
|
|
314
|
-
super();
|
|
315
|
-
this.target = target;
|
|
316
|
-
}
|
|
317
|
-
_write(chunk, encoding, callback) {
|
|
318
|
-
if (!this.muted) {
|
|
319
|
-
this.target.write(chunk, encoding);
|
|
320
|
-
}
|
|
321
|
-
callback();
|
|
322
|
-
}
|
|
323
|
-
}
|