@kinetica/admin-agent 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.
@@ -0,0 +1,4961 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // src/cli/index.ts
32
+ var cli_exports = {};
33
+ __export(cli_exports, {
34
+ getSession: () => getSession,
35
+ main: () => main,
36
+ verbose: () => verbose
37
+ });
38
+ module.exports = __toCommonJS(cli_exports);
39
+ var import_picocolors14 = __toESM(require("picocolors"));
40
+
41
+ // src/cli/banner.ts
42
+ var import_picocolors = __toESM(require("picocolors"));
43
+
44
+ // src/cli/version.ts
45
+ var import_fs = require("fs");
46
+ var import_path = require("path");
47
+ function getVersion() {
48
+ try {
49
+ let dir = __dirname;
50
+ while (dir !== (0, import_path.dirname)(dir)) {
51
+ const candidate = (0, import_path.join)(dir, "package.json");
52
+ if ((0, import_fs.existsSync)(candidate)) {
53
+ const pkg = JSON.parse((0, import_fs.readFileSync)(candidate, "utf8"));
54
+ return pkg.version;
55
+ }
56
+ dir = (0, import_path.dirname)(dir);
57
+ }
58
+ return "0.0.0";
59
+ } catch {
60
+ return "0.0.0";
61
+ }
62
+ }
63
+
64
+ // src/cli/banner.ts
65
+ var LOGO = ` \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557
66
+ \u2588\u2588\u2551 \u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
67
+ \u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551
68
+ \u2588\u2588\u2554\u2550\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551
69
+ \u2588\u2588\u2551 \u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551
70
+ \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D`;
71
+ function lerpRgb(from, to, t) {
72
+ return [
73
+ Math.round(from[0] + (to[0] - from[0]) * t),
74
+ Math.round(from[1] + (to[1] - from[1]) * t),
75
+ Math.round(from[2] + (to[2] - from[2]) * t)
76
+ ];
77
+ }
78
+ function dimRgb(color, factor) {
79
+ return [
80
+ Math.round(color[0] * factor),
81
+ Math.round(color[1] * factor),
82
+ Math.round(color[2] * factor)
83
+ ];
84
+ }
85
+ var SHADOW_CHARS = /* @__PURE__ */ new Set(["\u2557", "\u2554", "\u2551", "\u255D", "\u255A", "\u2550"]);
86
+ var DIM_FACTOR = 0.4;
87
+ function gradientize(text) {
88
+ const PURPLE = [147, 51, 234];
89
+ const HOT_PINK = [236, 72, 153];
90
+ const RESET = "\x1B[0m";
91
+ const lines = text.split("\n");
92
+ const maxIdx = Math.max(lines.length - 1, 1);
93
+ return lines.map((line, i) => {
94
+ const bright = lerpRgb(PURPLE, HOT_PINK, i / maxIdx);
95
+ const dim = dimRgb(bright, DIM_FACTOR);
96
+ let result = "";
97
+ let currentMode = null;
98
+ for (const ch of line) {
99
+ const mode = SHADOW_CHARS.has(ch) ? "dim" : "bright";
100
+ if (mode !== currentMode) {
101
+ const [r, g, b] = mode === "dim" ? dim : bright;
102
+ result += `\x1B[38;2;${r};${g};${b}m`;
103
+ currentMode = mode;
104
+ }
105
+ result += ch;
106
+ }
107
+ return result + RESET;
108
+ }).join("\n");
109
+ }
110
+ function printBanner(model) {
111
+ const version = getVersion();
112
+ const subtitle = `admin-agent ${import_picocolors.default.dim(`v${version}`)}`;
113
+ const header = model ? `${subtitle}
114
+ ${import_picocolors.default.dim(`model: ${model}`)}` : subtitle;
115
+ process.stderr.write("\n\n" + gradientize(LOGO) + "\n\n" + header + "\n");
116
+ return subtitle;
117
+ }
118
+
119
+ // src/auth/preflight.ts
120
+ var import_claude_agent_sdk = require("@anthropic-ai/claude-agent-sdk");
121
+
122
+ // src/auth/oauth-flow.ts
123
+ var import_picocolors2 = __toESM(require("picocolors"));
124
+
125
+ // src/auth/open-browser.ts
126
+ var import_child_process = require("child_process");
127
+ function openBrowser(url) {
128
+ try {
129
+ const platform = process.platform;
130
+ const { command, args } = platform === "darwin" ? { command: "open", args: [url] } : platform === "win32" ? { command: "cmd", args: ["/c", "start", "", url] } : { command: "xdg-open", args: [url] };
131
+ const child = (0, import_child_process.spawn)(command, args, { detached: true, stdio: "ignore" });
132
+ child.unref();
133
+ return true;
134
+ } catch {
135
+ return false;
136
+ }
137
+ }
138
+
139
+ // src/auth/oauth-flow.ts
140
+ async function resolveAuthentication(agentQuery, options) {
141
+ if (options.hasApiKey && !options.forceLogin) {
142
+ return { method: "api_key" };
143
+ }
144
+ const query3 = agentQuery;
145
+ try {
146
+ const { manualUrl, automaticUrl } = await query3.claudeAuthenticate(options.loginWithClaudeAi);
147
+ const opened = openBrowser(automaticUrl);
148
+ if (opened) {
149
+ process.stderr.write(import_picocolors2.default.dim("Browser opened for login. Waiting for authentication...\n"));
150
+ } else {
151
+ process.stderr.write(`
152
+ Open this URL in your browser to log in:
153
+ ${import_picocolors2.default.bold(manualUrl)}
154
+
155
+ `);
156
+ process.stderr.write(import_picocolors2.default.dim("Waiting for browser login to complete...\n"));
157
+ }
158
+ await query3.claudeOAuthWaitForCompletion();
159
+ return { method: "oauth" };
160
+ } catch (err) {
161
+ const message = err instanceof Error ? err.message : String(err);
162
+ process.stderr.write(
163
+ import_picocolors2.default.yellow(`
164
+ Warning: OAuth login failed (${message}). SDK may retry automatically.
165
+ `)
166
+ );
167
+ return { method: "oauth" };
168
+ }
169
+ }
170
+
171
+ // src/auth/preflight.ts
172
+ var PROBE_TIMEOUT_MS = 1e4;
173
+ async function probeCachedCredentials(authQuery) {
174
+ try {
175
+ const info = await Promise.race([
176
+ authQuery.accountInfo(),
177
+ new Promise(
178
+ (_, reject) => setTimeout(() => reject(new Error("Probe timed out")), PROBE_TIMEOUT_MS)
179
+ )
180
+ ]);
181
+ if (info.email || info.apiKeySource) {
182
+ return info;
183
+ }
184
+ return null;
185
+ } catch {
186
+ return null;
187
+ }
188
+ }
189
+ async function authenticateAnthropic(options) {
190
+ const hasApiKey = Boolean(process.env.ANTHROPIC_API_KEY);
191
+ if (hasApiKey && !options.forceLogin) {
192
+ return { method: "api_key" };
193
+ }
194
+ if (!process.stdin.isTTY) {
195
+ throw new Error(
196
+ "No ANTHROPIC_API_KEY set and terminal is non-interactive. Set ANTHROPIC_API_KEY or run in an interactive terminal with --login."
197
+ );
198
+ }
199
+ const env = options.forceLogin ? (() => {
200
+ const { ANTHROPIC_API_KEY: _stripped, ...rest } = process.env;
201
+ return { ...rest, CLAUDE_AGENT_SDK_CLIENT_APP: "admin-agent" };
202
+ })() : { ...process.env, CLAUDE_AGENT_SDK_CLIENT_APP: "admin-agent" };
203
+ const abortController = new AbortController();
204
+ async function* hangingPrompt() {
205
+ await new Promise(() => {
206
+ });
207
+ }
208
+ const authQuery = (0, import_claude_agent_sdk.query)({
209
+ prompt: hangingPrompt(),
210
+ options: {
211
+ persistSession: false,
212
+ abortController,
213
+ env,
214
+ ...options.loginMethod ? { forceLoginMethod: options.loginMethod } : {},
215
+ ...options.loginOrgUUID ? { forceLoginOrgUUID: options.loginOrgUUID } : {}
216
+ }
217
+ });
218
+ try {
219
+ if (!options.forceLogin) {
220
+ const cached = await probeCachedCredentials(authQuery);
221
+ if (cached) {
222
+ return { method: "oauth", email: cached.email };
223
+ }
224
+ }
225
+ return await resolveAuthentication(authQuery, {
226
+ forceLogin: options.forceLogin,
227
+ loginWithClaudeAi: (options.loginMethod ?? "claudeai") === "claudeai",
228
+ hasApiKey
229
+ });
230
+ } finally {
231
+ abortController.abort();
232
+ await authQuery.return().catch(() => {
233
+ });
234
+ }
235
+ }
236
+
237
+ // src/auth/logout.ts
238
+ var import_child_process2 = require("child_process");
239
+ var import_node_module = require("module");
240
+ var import_node_path = __toESM(require("path"));
241
+ var import_util = require("util");
242
+ var execFileAsync = (0, import_util.promisify)(import_child_process2.execFile);
243
+ function resolveSdkCliPath() {
244
+ const require_ = (0, import_node_module.createRequire)(__filename);
245
+ return import_node_path.default.join(import_node_path.default.dirname(require_.resolve("@anthropic-ai/claude-agent-sdk")), "cli.js");
246
+ }
247
+ async function logout() {
248
+ try {
249
+ const sdkCliPath = resolveSdkCliPath();
250
+ const { stdout, stderr } = await execFileAsync(process.execPath, [
251
+ sdkCliPath,
252
+ "auth",
253
+ "logout"
254
+ ]);
255
+ const output = (stdout || stderr || "").trim();
256
+ return { success: true, message: output || "Logged out successfully." };
257
+ } catch (err) {
258
+ const message = err instanceof Error ? err.message : String(err);
259
+ return { success: false, message: `Logout failed: ${message}` };
260
+ }
261
+ }
262
+
263
+ // src/session/env-file.ts
264
+ var import_fs2 = require("fs");
265
+ var import_promises = require("fs/promises");
266
+ var import_path2 = require("path");
267
+ var import_prompts = require("@inquirer/prompts");
268
+ var import_picocolors3 = __toESM(require("picocolors"));
269
+ function parseEnvContent(content) {
270
+ const entries = [];
271
+ for (const line of content.split("\n")) {
272
+ const trimmed = line.trim();
273
+ if (trimmed === "" || trimmed.startsWith("#")) continue;
274
+ const match = /^([A-Za-z_]\w*)=(.*)/.exec(trimmed);
275
+ if (!match) continue;
276
+ const key = match[1];
277
+ let value = match[2];
278
+ if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
279
+ value = value.slice(1, -1).replace(/\\(["\\])/g, "$1");
280
+ } else if (value.startsWith("'") && value.endsWith("'") && value.length >= 2) {
281
+ value = value.slice(1, -1);
282
+ }
283
+ entries.push([key, value]);
284
+ }
285
+ return new Map(entries);
286
+ }
287
+ var FORBIDDEN_VALUE_CHARS = /[\n\r\0]/;
288
+ var REQUIRES_QUOTING = /[\s"'#]/;
289
+ function escapeEnvValue(value) {
290
+ if (FORBIDDEN_VALUE_CHARS.test(value)) {
291
+ throw new Error(
292
+ "Env value contains a forbidden character (newline or null byte); refusing to write"
293
+ );
294
+ }
295
+ if (!REQUIRES_QUOTING.test(value)) {
296
+ return value;
297
+ }
298
+ const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
299
+ return `"${escaped}"`;
300
+ }
301
+ var ENV_TEMPLATE = `# Anthropic API key (optional \u2014 if not set, OAuth login via browser is used)
302
+ ANTHROPIC_API_KEY=
303
+
304
+ # Kinetica connection details (prompted interactively if not set)
305
+ # If password is omitted, the agent will prompt for it at startup
306
+ KINETICA_URL={url}
307
+ KINETICA_USER={user}
308
+ KINETICA_PASS=
309
+ `;
310
+ function buildEnvContent(url, user, existingContent) {
311
+ const safeUrl = escapeEnvValue(url);
312
+ const safeUser = escapeEnvValue(user);
313
+ if (!existingContent?.trim()) {
314
+ return ENV_TEMPLATE.replace("{url}", safeUrl).replace("{user}", safeUser);
315
+ }
316
+ const lines = existingContent.split("\n");
317
+ let urlReplaced = false;
318
+ let userReplaced = false;
319
+ const updated = lines.map((line) => {
320
+ if (/^KINETICA_URL=/.exec(line)) {
321
+ urlReplaced = true;
322
+ return `KINETICA_URL=${safeUrl}`;
323
+ }
324
+ if (/^KINETICA_USER=/.exec(line)) {
325
+ userReplaced = true;
326
+ return `KINETICA_USER=${safeUser}`;
327
+ }
328
+ return line;
329
+ });
330
+ if (!urlReplaced) updated.push(`KINETICA_URL=${safeUrl}`);
331
+ if (!userReplaced) updated.push(`KINETICA_USER=${safeUser}`);
332
+ return updated.join("\n");
333
+ }
334
+ function loadEnvFile(dir, env = process.env) {
335
+ try {
336
+ const filePath = (0, import_path2.join)(dir ?? process.cwd(), ".env");
337
+ const content = (0, import_fs2.readFileSync)(filePath, "utf8");
338
+ const parsed = parseEnvContent(content);
339
+ for (const [key, value] of parsed) {
340
+ if (env[key] === void 0 && value !== "") {
341
+ env[key] = value;
342
+ }
343
+ }
344
+ } catch {
345
+ }
346
+ }
347
+ async function offerSaveCredentials(url, user, dir) {
348
+ if (!process.stdin.isTTY) return;
349
+ try {
350
+ const shouldSave = await (0, import_prompts.confirm)({
351
+ message: "Save KINETICA_URL and KINETICA_USER to .env? (password is never saved)",
352
+ default: true
353
+ });
354
+ if (!shouldSave) return;
355
+ const filePath = (0, import_path2.join)(dir ?? process.cwd(), ".env");
356
+ let existing;
357
+ try {
358
+ existing = await (0, import_promises.readFile)(filePath, "utf8");
359
+ } catch {
360
+ }
361
+ const content = buildEnvContent(url, user, existing);
362
+ await (0, import_promises.writeFile)(filePath, content, "utf8");
363
+ console.error(import_picocolors3.default.dim("Saved to .env"));
364
+ } catch (err) {
365
+ const message = err instanceof Error ? err.message : String(err);
366
+ console.error(import_picocolors3.default.yellow(`Could not save .env file: ${message}`));
367
+ }
368
+ }
369
+
370
+ // src/session/verify.ts
371
+ var import_picocolors6 = __toESM(require("picocolors"));
372
+ var import_prompts4 = require("@inquirer/prompts");
373
+
374
+ // src/session/collect.ts
375
+ var import_prompts2 = require("@inquirer/prompts");
376
+ var import_picocolors4 = __toESM(require("picocolors"));
377
+ async function collectCredentials() {
378
+ const prompted = /* @__PURE__ */ new Set();
379
+ const envUrl = process.env.KINETICA_URL;
380
+ const envUser = process.env.KINETICA_USER;
381
+ if (envUrl && envUser && process.stdin.isTTY) {
382
+ console.error(import_picocolors4.default.dim(`Saved connection: ${envUrl} (${envUser})`));
383
+ const useSaved = await (0, import_prompts2.confirm)({
384
+ message: "Use saved connection?",
385
+ default: true
386
+ });
387
+ if (!useSaved) {
388
+ prompted.add("url");
389
+ prompted.add("user");
390
+ const url2 = await (0, import_prompts2.input)({ message: "Kinetica endpoint URL:" });
391
+ const user2 = await (0, import_prompts2.input)({ message: "Admin username:" });
392
+ const pass2 = await (0, import_prompts2.password)({ message: "Admin password:", mask: "*" });
393
+ return { credentials: { url: url2, user: user2, pass: pass2 }, prompted };
394
+ }
395
+ }
396
+ const url = envUrl ?? (prompted.add("url"), await (0, import_prompts2.input)({ message: "Kinetica endpoint URL:" }));
397
+ const user = envUser ?? (prompted.add("user"), await (0, import_prompts2.input)({ message: "Admin username:" }));
398
+ const pass = process.env.KINETICA_PASS ?? await (0, import_prompts2.password)({ message: "Admin password:", mask: "*" });
399
+ return { credentials: { url, user, pass }, prompted };
400
+ }
401
+ async function repromptCredentials() {
402
+ const user = await (0, import_prompts2.input)({ message: "Admin username:" });
403
+ const pass = await (0, import_prompts2.password)({ message: "Admin password:", mask: "*" });
404
+ return { user, pass };
405
+ }
406
+
407
+ // src/session/KineticaSession.ts
408
+ var REQUEST_TIMEOUT_MS = 3e4;
409
+ function replacePort(baseUrl, port) {
410
+ const parsed = new URL(baseUrl);
411
+ parsed.port = String(port);
412
+ return parsed.origin;
413
+ }
414
+ function createSession(url, user, pass) {
415
+ const authHeader = "Basic " + Buffer.from(`${user}:${pass}`).toString("base64");
416
+ const doFetch = async (fullUrl, body) => {
417
+ if (process.env.DEBUG) {
418
+ console.error(`[DEBUG] POST ${fullUrl}`);
419
+ }
420
+ return fetch(fullUrl, {
421
+ method: "POST",
422
+ headers: {
423
+ Authorization: authHeader,
424
+ "Content-Type": "application/json"
425
+ },
426
+ body: body !== void 0 ? JSON.stringify(body) : void 0,
427
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
428
+ });
429
+ };
430
+ return {
431
+ baseUrl: url,
432
+ makeRequest: (endpoint, body) => doFetch(`${url}${endpoint}`, body),
433
+ makeRequestToPort: (port, endpoint, body) => doFetch(`${replacePort(url, port)}${endpoint}`, body)
434
+ };
435
+ }
436
+
437
+ // src/session/resolve-url.ts
438
+ var import_picocolors5 = __toESM(require("picocolors"));
439
+ var import_prompts3 = require("@inquirer/prompts");
440
+ var PROBE_TIMEOUT_MS2 = 3e3;
441
+ var HTTP_PROTOCOL_RE = /^https?:\/\//i;
442
+ var ANY_PROTOCOL_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
443
+ function hasProtocol(input5) {
444
+ return HTTP_PROTOCOL_RE.test(input5);
445
+ }
446
+ function stripTrailingSlashes(url) {
447
+ return url.replace(/\/+$/, "");
448
+ }
449
+ async function probeProtocol(url) {
450
+ try {
451
+ await fetch(url, {
452
+ method: "HEAD",
453
+ signal: AbortSignal.timeout(PROBE_TIMEOUT_MS2)
454
+ });
455
+ return true;
456
+ } catch {
457
+ return false;
458
+ }
459
+ }
460
+ function isHttpsOnly() {
461
+ return process.env.KINETICA_HTTPS_ONLY === "1";
462
+ }
463
+ function isInteractive() {
464
+ return Boolean(process.stdin.isTTY);
465
+ }
466
+ async function confirmHttpFallback(host) {
467
+ process.stderr.write(
468
+ "\n" + import_picocolors5.default.red(
469
+ import_picocolors5.default.bold(
470
+ ` WARNING: HTTPS unavailable at ${host}.
471
+ Falling back to plaintext HTTP will transmit your Kinetica credentials in the clear.
472
+ `
473
+ )
474
+ ) + import_picocolors5.default.dim(
475
+ ` Set KINETICA_HTTPS_ONLY=1 to refuse this fallback automatically, or pass an explicit http:// prefix to silence this prompt.
476
+
477
+ `
478
+ )
479
+ );
480
+ try {
481
+ return await (0, import_prompts3.confirm)({
482
+ message: "Continue over plaintext HTTP?",
483
+ default: false
484
+ });
485
+ } catch {
486
+ return false;
487
+ }
488
+ }
489
+ async function resolveUrl(input5) {
490
+ const trimmed = input5.trim();
491
+ if (trimmed === "") {
492
+ return { ok: false, error: "URL is empty" };
493
+ }
494
+ const normalized = stripTrailingSlashes(trimmed);
495
+ if (hasProtocol(normalized)) {
496
+ return { ok: true, url: normalized };
497
+ }
498
+ if (ANY_PROTOCOL_RE.test(normalized)) {
499
+ const scheme = normalized.split("://")[0];
500
+ return {
501
+ ok: false,
502
+ error: `Unsupported protocol: ${scheme}. Use http:// or https://`
503
+ };
504
+ }
505
+ const httpsUrl = `https://${normalized}`;
506
+ const httpUrl = `http://${normalized}`;
507
+ console.error(import_picocolors5.default.dim("Detecting protocol..."));
508
+ if (await probeProtocol(httpsUrl)) {
509
+ return { ok: true, url: httpsUrl };
510
+ }
511
+ if (isHttpsOnly()) {
512
+ return {
513
+ ok: false,
514
+ error: `HTTPS probe to ${httpsUrl} failed and KINETICA_HTTPS_ONLY=1; refusing to fall back to plaintext HTTP.`
515
+ };
516
+ }
517
+ if (!await probeProtocol(httpUrl)) {
518
+ return {
519
+ ok: false,
520
+ error: `Could not connect to ${normalized} via https:// or http://`
521
+ };
522
+ }
523
+ if (!isInteractive()) {
524
+ return {
525
+ ok: false,
526
+ error: `HTTPS unavailable at ${normalized} and terminal is non-interactive. Pass an explicit http:// prefix to allow plaintext HTTP, or point the URL at an HTTPS endpoint.`
527
+ };
528
+ }
529
+ const approved = await confirmHttpFallback(normalized);
530
+ if (!approved) {
531
+ return {
532
+ ok: false,
533
+ error: "User declined plaintext HTTP fallback"
534
+ };
535
+ }
536
+ return { ok: true, url: httpUrl };
537
+ }
538
+
539
+ // src/session/verify.ts
540
+ var MAX_RETRIES = 3;
541
+ var MAX_REPROMPTS = 2;
542
+ var DEFAULT_HM_PORT = 9300;
543
+ function extractVersion(responseBody) {
544
+ try {
545
+ const outer = JSON.parse(responseBody);
546
+ if (typeof outer.data_str !== "string") return void 0;
547
+ const inner = JSON.parse(outer.data_str);
548
+ const systemStr = inner.status_map?.system;
549
+ if (typeof systemStr !== "string") return void 0;
550
+ const system = JSON.parse(systemStr);
551
+ return typeof system.version === "string" ? system.version : void 0;
552
+ } catch {
553
+ return void 0;
554
+ }
555
+ }
556
+ function extractVersionFromHostManager(responseBody) {
557
+ try {
558
+ const parsed = JSON.parse(responseBody);
559
+ return typeof parsed.version === "string" ? parsed.version : void 0;
560
+ } catch {
561
+ return void 0;
562
+ }
563
+ }
564
+ async function probeHostManager(session2) {
565
+ if (!session2.makeRequestToPort) {
566
+ return { ok: false };
567
+ }
568
+ try {
569
+ const response = await session2.makeRequestToPort(DEFAULT_HM_PORT, "/", void 0);
570
+ if (!response.ok) return { ok: false };
571
+ const body = await response.text();
572
+ return { ok: true, version: extractVersionFromHostManager(body) };
573
+ } catch {
574
+ return { ok: false };
575
+ }
576
+ }
577
+ async function verifyConnectivity(session2) {
578
+ const response = await session2.makeRequest("/show/system/status", {});
579
+ if (!response.ok) {
580
+ const body2 = await response.text();
581
+ throw new Error(`HTTP ${response.status}: ${body2}`);
582
+ }
583
+ const body = await response.text();
584
+ return extractVersion(body);
585
+ }
586
+ function isCredentialError(errorMessage) {
587
+ return errorMessage.startsWith("HTTP 401") || errorMessage.startsWith("HTTP 403");
588
+ }
589
+ async function connectWithRetry() {
590
+ const { credentials, prompted } = await collectCredentials();
591
+ const resolved = await resolveUrl(credentials.url);
592
+ if (!resolved.ok) {
593
+ console.error(import_picocolors6.default.red(resolved.error));
594
+ process.exit(1);
595
+ }
596
+ const resolvedUrl = resolved.url;
597
+ let currentUser = credentials.user;
598
+ let currentPass = credentials.pass;
599
+ let wasReprompted = false;
600
+ let repromptCount = 0;
601
+ let session2 = createSession(resolvedUrl, currentUser, currentPass);
602
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
603
+ try {
604
+ const kineticaVersion = await verifyConnectivity(session2);
605
+ console.error(import_picocolors6.default.green("Connected to Kinetica successfully."));
606
+ if (prompted.size > 0 || wasReprompted) {
607
+ await offerSaveCredentials(resolvedUrl, currentUser);
608
+ }
609
+ return { session: session2, kineticaVersion, degraded: false };
610
+ } catch (err) {
611
+ const msg = err instanceof Error ? err.message : String(err);
612
+ console.error(import_picocolors6.default.red(`Connection failed (attempt ${attempt}/${MAX_RETRIES}): ${msg}`));
613
+ if (isCredentialError(msg)) {
614
+ if (process.stdin.isTTY && repromptCount < MAX_REPROMPTS) {
615
+ const shouldRetry = await (0, import_prompts4.confirm)({
616
+ message: "Credentials may be incorrect. Re-enter?",
617
+ default: true
618
+ });
619
+ if (shouldRetry) {
620
+ const fresh = await repromptCredentials();
621
+ currentUser = fresh.user;
622
+ currentPass = fresh.pass;
623
+ wasReprompted = true;
624
+ repromptCount++;
625
+ session2 = createSession(resolvedUrl, currentUser, currentPass);
626
+ attempt = 0;
627
+ continue;
628
+ }
629
+ }
630
+ console.error(import_picocolors6.default.red("Authentication failed. Exiting."));
631
+ process.exit(1);
632
+ }
633
+ if (attempt === MAX_RETRIES) {
634
+ console.error(import_picocolors6.default.yellow("DB engine unreachable. Probing host manager on port 9300..."));
635
+ const hmResult = await probeHostManager(session2);
636
+ if (hmResult.ok) {
637
+ console.error(
638
+ import_picocolors6.default.yellow(
639
+ "Connected in DEGRADED MODE (host manager only). Most diagnostic tools will be unavailable."
640
+ )
641
+ );
642
+ if (prompted.size > 0 || wasReprompted) {
643
+ await offerSaveCredentials(resolvedUrl, currentUser);
644
+ }
645
+ return { session: session2, kineticaVersion: hmResult.version, degraded: true };
646
+ }
647
+ console.error(import_picocolors6.default.red("Host manager also unreachable. Exiting."));
648
+ process.exit(1);
649
+ }
650
+ }
651
+ }
652
+ throw new Error("unreachable");
653
+ }
654
+
655
+ // src/agent/run-agent.ts
656
+ var import_claude_agent_sdk5 = require("@anthropic-ai/claude-agent-sdk");
657
+ var import_prompts8 = require("@inquirer/prompts");
658
+ var import_picocolors13 = __toESM(require("picocolors"));
659
+
660
+ // src/agent/diagnostic-sql.ts
661
+ function has(columns, name) {
662
+ return columns.includes(name);
663
+ }
664
+ var FALLBACK_QUERY_HISTORY_SQL = `-- Slow queries in the last hour
665
+ SELECT query_id, user_name, query_text, start_time, stop_time,
666
+ TIMESTAMPDIFF(SECOND, start_time, stop_time) AS elapsed_sec
667
+ FROM ki_catalog.ki_query_history
668
+ WHERE start_time > NOW() - INTERVAL '1' HOUR
669
+ ORDER BY elapsed_sec DESC
670
+ LIMIT 20;`;
671
+ var FALLBACK_ACTIVE_QUERIES_SQL = `-- Currently active queries
672
+ SELECT query_id, user_name, query_text, start_time, execution_status
673
+ FROM ki_catalog.ki_query_active_all
674
+ ORDER BY start_time ASC;`;
675
+ var FALLBACK_TIERED_OBJECTS_SQL = `-- Objects in disk tier (potential memory pressure)
676
+ -- NOTE: id is a string (e.g. @schema@oid[col][0]), NOT a numeric OID. Filter by table: WHERE id LIKE '%table_name%'
677
+ -- For per-table tier placement, prefer kinetica_resource_objects with table_names filter.
678
+ SELECT id, tier, size, source_rank, owner_resource_group
679
+ FROM ki_catalog.ki_tiered_objects
680
+ WHERE tier != 'VRAM'
681
+ ORDER BY size DESC
682
+ LIMIT 20;`;
683
+ var FALLBACK_OBJ_STAT_SQL = `-- Table sizes and row counts
684
+ SELECT object_name, total_bytes, row_count
685
+ FROM ki_catalog.ki_obj_stat
686
+ ORDER BY total_bytes DESC
687
+ LIMIT 30;`;
688
+ var FALLBACK_COLUMNS_SQL = `-- Structural column metadata (use kinetica_show_table for Kinetica-native types)
689
+ SELECT c.table_name, c.column_name, c.column_position
690
+ FROM ki_catalog.ki_columns c
691
+ WHERE c.table_name = '<TABLE_NAME>'
692
+ ORDER BY c.column_position;`;
693
+ var FALLBACK_DATATYPES_SQL = `-- Resolve column_type_oid to human-readable type name
694
+ SELECT oid, name, sql_typename
695
+ FROM ki_catalog.ki_datatypes
696
+ ORDER BY oid;`;
697
+ var FALLBACK_QUERY_SPAN_METRICS_SQL = `-- Query span metrics for a specific query
698
+ SELECT query_id, span_id, parent_span_id, operator, sql_step,
699
+ metric_data, start_time, stop_time, source_rank
700
+ FROM ki_catalog.ki_query_span_metrics_all
701
+ WHERE query_id = '<QUERY_ID>'
702
+ ORDER BY start_time;`;
703
+ var FALLBACK_QUERY_WORKERS_SQL = `-- Active query workers (non-idle)
704
+ SELECT job_id, worker_id, type, status, elapsed_time_ms, source_rank
705
+ FROM ki_catalog.ki_query_workers
706
+ WHERE status != 'IDLE'
707
+ ORDER BY elapsed_time_ms DESC;`;
708
+ var FALLBACK_OBJECTS_SQL = `-- Object registry and metadata
709
+ SELECT oid, object_name, schema_name, type_id, persistence, obj_kind,
710
+ creation_time, last_read_time, read_count, write_count
711
+ FROM ki_catalog.ki_objects
712
+ ORDER BY last_read_time DESC
713
+ LIMIT 30;`;
714
+ var FALLBACK_PARTITIONS_SQL = `-- Partition sizes and tier distribution
715
+ SELECT oid, object_name, schema_name, rank_num, partition_type,
716
+ partition_id, num_rows, actual_bytes, tier
717
+ FROM ki_catalog.ki_partitions
718
+ ORDER BY actual_bytes DESC
719
+ LIMIT 30;`;
720
+ var FALLBACK_INDEXES_SQL = `-- Index definitions
721
+ SELECT oid, object_name, schema_name, index_type, index_columns
722
+ FROM ki_catalog.ki_indexes
723
+ ORDER BY object_name;`;
724
+ var FALLBACK_PERIODIC_OBJECTS_SQL = `-- Periodic refresh schedules
725
+ SELECT oid, object_name, schema_name, last_refresh_time,
726
+ next_refresh_time, additional_info
727
+ FROM ki_catalog.ki_periodic_objects
728
+ ORDER BY next_refresh_time;`;
729
+ var FALLBACK_USERS_AND_ROLES_SQL = `-- Users and roles
730
+ SELECT oid, name, can_login, is_superuser, resource_group
731
+ FROM ki_catalog.ki_users_and_roles
732
+ ORDER BY name;`;
733
+ var FALLBACK_OBJECT_PERMISSIONS_SQL = `-- Object permissions
734
+ SELECT role_name, permission_type, object_type, object_name, with_grant_option
735
+ FROM ki_catalog.ki_object_permissions
736
+ ORDER BY object_name, role_name;`;
737
+ var FALLBACK_DEPEND_SQL = `-- Object dependency graph
738
+ SELECT src_obj_oid, src_obj_kind, dep_obj_oid, dep_obj_kind, mv_oid, dep_kind
739
+ FROM ki_catalog.ki_depend;`;
740
+ var FALLBACK_LOAD_HISTORY_SQL = `-- Recent data load history
741
+ SELECT table_oid, datasource_oid, user_name, load_kind,
742
+ start_time, end_time, rows_inserted, event_message
743
+ FROM ki_catalog.ki_load_history
744
+ WHERE start_time > NOW() - INTERVAL '1' HOUR
745
+ ORDER BY start_time DESC
746
+ LIMIT 20;`;
747
+ var FALLBACK_BACKUP_HISTORY_SQL = `-- Backup history
748
+ SELECT backup_name, operation, status, start_time, end_time,
749
+ num_files, num_bytes, num_records
750
+ FROM ki_catalog.ki_backup_history
751
+ ORDER BY start_time DESC
752
+ LIMIT 20;`;
753
+ var FALLBACK_KAFKA_LAG_INFO_SQL = `-- Kafka consumer lag
754
+ SELECT datasource_oid, table_oid, schema_name, table_name,
755
+ partition_id, highest_offset, last_committed_offset
756
+ FROM ki_catalog.ki_kafka_lag_info
757
+ ORDER BY datasource_oid, partition_id;`;
758
+ function buildQueryHistorySql(columns) {
759
+ const select = columns.join(", ");
760
+ const elapsed = has(columns, "start_time") && has(columns, "stop_time") ? ",\n TIMESTAMPDIFF(SECOND, start_time, stop_time) AS elapsed_sec" : "";
761
+ const where = has(columns, "start_time") ? "\nWHERE start_time > NOW() - INTERVAL '1' HOUR" : "";
762
+ const orderBy = has(columns, "start_time") && has(columns, "stop_time") ? (
763
+ // Kinetica does not support timestamp arithmetic in ORDER BY; use the elapsed_sec alias instead
764
+ "\nORDER BY elapsed_sec DESC"
765
+ ) : "";
766
+ return `-- Slow queries in the last hour
767
+ SELECT ${select}${elapsed}
768
+ FROM ki_catalog.ki_query_history${where}${orderBy}
769
+ LIMIT 20;`;
770
+ }
771
+ function buildActiveQueriesSql(columns) {
772
+ const select = columns.join(", ");
773
+ const orderBy = has(columns, "start_time") ? "\nORDER BY start_time ASC" : "";
774
+ return `-- Currently active queries
775
+ SELECT ${select}
776
+ FROM ki_catalog.ki_query_active_all${orderBy};`;
777
+ }
778
+ function buildTieredObjectsSql(columns) {
779
+ const select = columns.join(", ");
780
+ const where = has(columns, "tier") ? "\nWHERE tier != 'VRAM'" : "";
781
+ const orderBy = has(columns, "size") ? "\nORDER BY size DESC" : "";
782
+ const idNote = has(columns, "id") ? "\n-- NOTE: id is a string (e.g. @schema@oid[col][0]), NOT a numeric OID. Filter by table: WHERE id LIKE '%table_name%'" : "";
783
+ return `-- Objects in disk tier (potential memory pressure)${idNote}
784
+ SELECT ${select}
785
+ FROM ki_catalog.ki_tiered_objects${where}${orderBy}
786
+ LIMIT 20;`;
787
+ }
788
+ function buildObjStatSql(columns) {
789
+ const select = columns.join(", ");
790
+ const orderBy = has(columns, "total_bytes") ? "\nORDER BY total_bytes DESC" : "";
791
+ return `-- Table sizes and row counts
792
+ SELECT ${select}
793
+ FROM ki_catalog.ki_obj_stat${orderBy}
794
+ LIMIT 30;`;
795
+ }
796
+ var STRUCTURAL_COLUMNS = /* @__PURE__ */ new Set([
797
+ "table_name",
798
+ "column_name",
799
+ "column_position",
800
+ "is_nullable",
801
+ "is_shard_key",
802
+ "is_primary_key",
803
+ "is_dict_encoded",
804
+ "default_value",
805
+ "bytes_on_disk_uncompressed",
806
+ "bytes_on_disk_compressed"
807
+ ]);
808
+ function buildColumnsSql(columns) {
809
+ const filtered = columns.filter((c) => STRUCTURAL_COLUMNS.has(c));
810
+ const selectCols = filtered.length > 0 ? filtered : columns;
811
+ const select = selectCols.map((c) => `c.${c}`).join(", ");
812
+ const where = has(selectCols, "table_name") ? "\nWHERE c.table_name = '<TABLE_NAME>'" : "";
813
+ const orderBy = has(selectCols, "column_position") ? "\nORDER BY c.column_position" : has(selectCols, "column_name") ? "\nORDER BY c.column_name" : "";
814
+ return `-- Structural column metadata (use kinetica_show_table for Kinetica-native types)
815
+ SELECT ${select}
816
+ FROM ki_catalog.ki_columns c${where}${orderBy};`;
817
+ }
818
+ function buildDatatypesSql(columns) {
819
+ const select = columns.join(", ");
820
+ const orderBy = has(columns, "oid") ? "\nORDER BY oid" : "";
821
+ return `-- Resolve column_type_oid to human-readable type name
822
+ SELECT ${select}
823
+ FROM ki_catalog.ki_datatypes${orderBy};`;
824
+ }
825
+ function buildQuerySpanMetricsSql(columns) {
826
+ const select = columns.join(", ");
827
+ const where = has(columns, "query_id") ? "\nWHERE query_id = '<QUERY_ID>'" : "";
828
+ const orderBy = has(columns, "start_time") ? "\nORDER BY start_time" : "";
829
+ return `-- Query span metrics for a specific query
830
+ SELECT ${select}
831
+ FROM ki_catalog.ki_query_span_metrics_all${where}${orderBy};`;
832
+ }
833
+ function buildQueryWorkersSql(columns) {
834
+ const select = columns.join(", ");
835
+ const where = has(columns, "status") ? "\nWHERE status != 'IDLE'" : "";
836
+ const orderBy = has(columns, "elapsed_time_ms") ? "\nORDER BY elapsed_time_ms DESC" : "";
837
+ return `-- Active query workers (non-idle)
838
+ SELECT ${select}
839
+ FROM ki_catalog.ki_query_workers${where}${orderBy};`;
840
+ }
841
+ function buildObjectsSql(columns) {
842
+ const select = columns.join(", ");
843
+ const orderBy = has(columns, "last_read_time") ? "\nORDER BY last_read_time DESC" : has(columns, "creation_time") ? "\nORDER BY creation_time DESC" : "";
844
+ return `-- Object registry and metadata
845
+ SELECT ${select}
846
+ FROM ki_catalog.ki_objects${orderBy}
847
+ LIMIT 30;`;
848
+ }
849
+ function buildPartitionsSql(columns) {
850
+ const select = columns.join(", ");
851
+ const orderBy = has(columns, "actual_bytes") ? "\nORDER BY actual_bytes DESC" : "";
852
+ return `-- Partition sizes and tier distribution
853
+ SELECT ${select}
854
+ FROM ki_catalog.ki_partitions${orderBy}
855
+ LIMIT 30;`;
856
+ }
857
+ function buildIndexesSql(columns) {
858
+ const select = columns.join(", ");
859
+ const orderBy = has(columns, "object_name") ? "\nORDER BY object_name" : "";
860
+ return `-- Index definitions
861
+ SELECT ${select}
862
+ FROM ki_catalog.ki_indexes${orderBy};`;
863
+ }
864
+ function buildPeriodicObjectsSql(columns) {
865
+ const select = columns.join(", ");
866
+ const orderBy = has(columns, "next_refresh_time") ? "\nORDER BY next_refresh_time" : "";
867
+ return `-- Periodic refresh schedules
868
+ SELECT ${select}
869
+ FROM ki_catalog.ki_periodic_objects${orderBy};`;
870
+ }
871
+ function buildUsersAndRolesSql(columns) {
872
+ const select = columns.join(", ");
873
+ const orderBy = has(columns, "name") ? "\nORDER BY name" : "";
874
+ return `-- Users and roles
875
+ SELECT ${select}
876
+ FROM ki_catalog.ki_users_and_roles${orderBy};`;
877
+ }
878
+ function buildObjectPermissionsSql(columns) {
879
+ const select = columns.join(", ");
880
+ const orderBy = has(columns, "object_name") && has(columns, "role_name") ? "\nORDER BY object_name, role_name" : has(columns, "object_name") ? "\nORDER BY object_name" : "";
881
+ return `-- Object permissions
882
+ SELECT ${select}
883
+ FROM ki_catalog.ki_object_permissions${orderBy};`;
884
+ }
885
+ function buildDependSql(columns) {
886
+ const select = columns.join(", ");
887
+ return `-- Object dependency graph
888
+ SELECT ${select}
889
+ FROM ki_catalog.ki_depend;`;
890
+ }
891
+ function buildLoadHistorySql(columns) {
892
+ const select = columns.join(", ");
893
+ const where = has(columns, "start_time") ? "\nWHERE start_time > NOW() - INTERVAL '1' HOUR" : "";
894
+ const orderBy = has(columns, "start_time") ? "\nORDER BY start_time DESC" : "";
895
+ return `-- Recent data load history
896
+ SELECT ${select}
897
+ FROM ki_catalog.ki_load_history${where}${orderBy}
898
+ LIMIT 20;`;
899
+ }
900
+ function buildBackupHistorySql(columns) {
901
+ const select = columns.join(", ");
902
+ const orderBy = has(columns, "start_time") ? "\nORDER BY start_time DESC" : "";
903
+ return `-- Backup history
904
+ SELECT ${select}
905
+ FROM ki_catalog.ki_backup_history${orderBy}
906
+ LIMIT 20;`;
907
+ }
908
+ function buildKafkaLagInfoSql(columns) {
909
+ const select = columns.join(", ");
910
+ const orderBy = has(columns, "datasource_oid") && has(columns, "partition_id") ? "\nORDER BY datasource_oid, partition_id" : has(columns, "datasource_oid") ? "\nORDER BY datasource_oid" : "";
911
+ return `-- Kafka consumer lag
912
+ SELECT ${select}
913
+ FROM ki_catalog.ki_kafka_lag_info${orderBy};`;
914
+ }
915
+ var BUILDER_REGISTRY = [
916
+ // Query History and Performance
917
+ {
918
+ table: "ki_query_history",
919
+ section: "Query History and Performance",
920
+ build: buildQueryHistorySql,
921
+ fallback: FALLBACK_QUERY_HISTORY_SQL
922
+ },
923
+ {
924
+ table: "ki_query_active_all",
925
+ section: "Query History and Performance",
926
+ build: buildActiveQueriesSql,
927
+ fallback: FALLBACK_ACTIVE_QUERIES_SQL
928
+ },
929
+ {
930
+ table: "ki_query_span_metrics_all",
931
+ section: "Query History and Performance",
932
+ build: buildQuerySpanMetricsSql,
933
+ fallback: FALLBACK_QUERY_SPAN_METRICS_SQL
934
+ },
935
+ {
936
+ table: "ki_query_workers",
937
+ section: "Query History and Performance",
938
+ build: buildQueryWorkersSql,
939
+ fallback: FALLBACK_QUERY_WORKERS_SQL
940
+ },
941
+ // Memory and Storage Tiers
942
+ {
943
+ table: "ki_tiered_objects",
944
+ section: "Memory and Storage Tiers",
945
+ build: buildTieredObjectsSql,
946
+ fallback: FALLBACK_TIERED_OBJECTS_SQL
947
+ },
948
+ {
949
+ table: "ki_obj_stat",
950
+ section: "Memory and Storage Tiers",
951
+ build: buildObjStatSql,
952
+ fallback: FALLBACK_OBJ_STAT_SQL
953
+ },
954
+ {
955
+ table: "ki_partitions",
956
+ section: "Memory and Storage Tiers",
957
+ build: buildPartitionsSql,
958
+ fallback: FALLBACK_PARTITIONS_SQL
959
+ },
960
+ // Object Registry and Metadata
961
+ {
962
+ table: "ki_objects",
963
+ section: "Object Registry and Metadata",
964
+ build: buildObjectsSql,
965
+ fallback: FALLBACK_OBJECTS_SQL
966
+ },
967
+ {
968
+ table: "ki_indexes",
969
+ section: "Object Registry and Metadata",
970
+ build: buildIndexesSql,
971
+ fallback: FALLBACK_INDEXES_SQL
972
+ },
973
+ {
974
+ table: "ki_periodic_objects",
975
+ section: "Object Registry and Metadata",
976
+ build: buildPeriodicObjectsSql,
977
+ fallback: FALLBACK_PERIODIC_OBJECTS_SQL
978
+ },
979
+ {
980
+ table: "ki_depend",
981
+ section: "Object Registry and Metadata",
982
+ build: buildDependSql,
983
+ fallback: FALLBACK_DEPEND_SQL
984
+ },
985
+ // Security and Access Control
986
+ {
987
+ table: "ki_users_and_roles",
988
+ section: "Security and Access Control",
989
+ build: buildUsersAndRolesSql,
990
+ fallback: FALLBACK_USERS_AND_ROLES_SQL
991
+ },
992
+ {
993
+ table: "ki_object_permissions",
994
+ section: "Security and Access Control",
995
+ build: buildObjectPermissionsSql,
996
+ fallback: FALLBACK_OBJECT_PERMISSIONS_SQL
997
+ },
998
+ // Data Ingestion and Operations
999
+ {
1000
+ table: "ki_load_history",
1001
+ section: "Data Ingestion and Operations",
1002
+ build: buildLoadHistorySql,
1003
+ fallback: FALLBACK_LOAD_HISTORY_SQL
1004
+ },
1005
+ {
1006
+ table: "ki_backup_history",
1007
+ section: "Data Ingestion and Operations",
1008
+ build: buildBackupHistorySql,
1009
+ fallback: FALLBACK_BACKUP_HISTORY_SQL
1010
+ },
1011
+ {
1012
+ table: "ki_kafka_lag_info",
1013
+ section: "Data Ingestion and Operations",
1014
+ build: buildKafkaLagInfoSql,
1015
+ fallback: FALLBACK_KAFKA_LAG_INFO_SQL
1016
+ },
1017
+ // Schema Inspection
1018
+ {
1019
+ table: "ki_columns",
1020
+ section: "Schema Inspection",
1021
+ build: buildColumnsSql,
1022
+ fallback: FALLBACK_COLUMNS_SQL
1023
+ },
1024
+ {
1025
+ table: "ki_datatypes",
1026
+ section: "Schema Inspection",
1027
+ build: buildDatatypesSql,
1028
+ fallback: FALLBACK_DATATYPES_SQL
1029
+ }
1030
+ ];
1031
+
1032
+ // src/tools/index.ts
1033
+ var import_claude_agent_sdk3 = require("@anthropic-ai/claude-agent-sdk");
1034
+ var import_zod16 = require("zod");
1035
+ var import_picocolors9 = __toESM(require("picocolors"));
1036
+
1037
+ // src/approval/registry.ts
1038
+ var DEFAULT_READ_ONLY_TOOLS = /* @__PURE__ */ new Set();
1039
+ function createRegistry(tools = DEFAULT_READ_ONLY_TOOLS) {
1040
+ return {
1041
+ isReadOnlyTool: (toolName) => tools.has(toolName),
1042
+ // Returns a NEW registry — never mutates the current one
1043
+ registerReadOnlyTool: (toolName) => createRegistry(/* @__PURE__ */ new Set([...tools, toolName])),
1044
+ tools
1045
+ };
1046
+ }
1047
+ var defaultRegistry = createRegistry();
1048
+ var isReadOnlyTool = defaultRegistry.isReadOnlyTool;
1049
+ var READ_ONLY_TOOLS = defaultRegistry.tools;
1050
+
1051
+ // src/output/stringify.ts
1052
+ function stringifyValue(v) {
1053
+ if (v === void 0 || v === null) return "";
1054
+ if (typeof v === "string") return v;
1055
+ if (typeof v === "number" || typeof v === "boolean" || typeof v === "bigint") {
1056
+ return v.toString();
1057
+ }
1058
+ if (typeof v === "object") return JSON.stringify(v);
1059
+ if (typeof v === "symbol") return v.toString();
1060
+ return "";
1061
+ }
1062
+
1063
+ // src/output/format.ts
1064
+ function formatOutput(json) {
1065
+ if (json === null || json === void 0) {
1066
+ return "(empty)";
1067
+ }
1068
+ if (Array.isArray(json)) {
1069
+ return formatArray(json);
1070
+ }
1071
+ if (typeof json === "object") {
1072
+ return formatObject(json);
1073
+ }
1074
+ return stringifyValue(json);
1075
+ }
1076
+ function formatArray(arr) {
1077
+ if (arr.length === 0) {
1078
+ return "(no results)";
1079
+ }
1080
+ const first = arr[0];
1081
+ if (typeof first === "object" && first !== null && !Array.isArray(first)) {
1082
+ return formatTableArray(arr);
1083
+ }
1084
+ return arr.map(stringifyValue).join("\n");
1085
+ }
1086
+ function formatTableArray(rows) {
1087
+ const headers = Object.keys(rows[0]);
1088
+ const cells = rows.map((row) => headers.map((h) => stringifyValue(row[h])));
1089
+ const colWidths = headers.map(
1090
+ (h, i) => Math.max(h.length, ...cells.map((row) => row[i].length), 3)
1091
+ );
1092
+ const pad = (text, col) => text.padEnd(colWidths[col]);
1093
+ const headerRow = `| ${headers.map((h, i) => pad(h, i)).join(" | ")} |`;
1094
+ const separatorRow = `| ${colWidths.map((w) => "-".repeat(w)).join(" | ")} |`;
1095
+ const dataRows = cells.map((row) => `| ${row.map((cell, i) => pad(cell, i)).join(" | ")} |`);
1096
+ return [headerRow, separatorRow, ...dataRows].join("\n");
1097
+ }
1098
+ function formatObject(obj) {
1099
+ return Object.entries(obj).map(([key, value]) => {
1100
+ if (typeof value === "object" && value !== null) {
1101
+ return `**${key}:**
1102
+ ${formatOutput(value)}`;
1103
+ }
1104
+ return `**${key}:** ${stringifyValue(value)}`;
1105
+ }).join("\n");
1106
+ }
1107
+
1108
+ // src/output/format-tool-name.ts
1109
+ function formatToolName(toolName) {
1110
+ const stripped = toolName.replace(/^mcp__[^_]+__/, "").replace(/^kinetica_/, "");
1111
+ return stripped.replace(/_/g, " ");
1112
+ }
1113
+
1114
+ // src/types/index.ts
1115
+ var DEFAULT_TRUNCATION = {
1116
+ headLines: 150,
1117
+ tailLines: 50
1118
+ };
1119
+
1120
+ // src/output/truncate.ts
1121
+ function truncateOutput(text, options = DEFAULT_TRUNCATION) {
1122
+ if (text === "") return "";
1123
+ const lines = text.split("\n");
1124
+ const { headLines, tailLines } = options;
1125
+ const threshold = headLines + tailLines;
1126
+ if (lines.length <= threshold) {
1127
+ return text;
1128
+ }
1129
+ const truncatedCount = lines.length - headLines - tailLines;
1130
+ const head = lines.slice(0, headLines);
1131
+ const tail = lines.slice(lines.length - tailLines);
1132
+ return [...head, "", `[... ${truncatedCount} lines truncated ...]`, "", ...tail].join("\n");
1133
+ }
1134
+
1135
+ // src/output/reshape.ts
1136
+ function safeString(v) {
1137
+ if (typeof v === "object" && v !== null) {
1138
+ return JSON.stringify(v);
1139
+ }
1140
+ return String(v);
1141
+ }
1142
+ function flatObjectToRows(obj, keyLabel = "key", valueLabel = "value") {
1143
+ return Object.entries(obj).map(([k, v]) => ({
1144
+ [keyLabel]: k,
1145
+ [valueLabel]: safeString(v)
1146
+ }));
1147
+ }
1148
+
1149
+ // src/tools/rest/parse-data-str.ts
1150
+ function parseDataStr(outerDataStr, raw) {
1151
+ if (typeof outerDataStr !== "string") {
1152
+ return { ok: true, data: void 0 };
1153
+ }
1154
+ try {
1155
+ return { ok: true, data: JSON.parse(outerDataStr) };
1156
+ } catch (err) {
1157
+ const message = err instanceof Error ? err.message : String(err);
1158
+ return { ok: false, status: 200, error: `data_str parse error: ${message}`, raw };
1159
+ }
1160
+ }
1161
+
1162
+ // src/tools/rest/health.ts
1163
+ async function healthCheck(session2) {
1164
+ let response;
1165
+ let rawText;
1166
+ try {
1167
+ response = await session2.makeRequest("/show/system/status", {});
1168
+ rawText = await response.text();
1169
+ } catch (err) {
1170
+ const message = err instanceof Error ? err.message : String(err);
1171
+ return {
1172
+ ok: false,
1173
+ status: 0,
1174
+ error: message,
1175
+ raw: ""
1176
+ };
1177
+ }
1178
+ if (!response.ok) {
1179
+ return {
1180
+ ok: false,
1181
+ status: response.status,
1182
+ error: `HTTP ${response.status}`,
1183
+ raw: rawText
1184
+ };
1185
+ }
1186
+ let parsed;
1187
+ try {
1188
+ parsed = JSON.parse(rawText);
1189
+ } catch (err) {
1190
+ const message = err instanceof Error ? err.message : String(err);
1191
+ return {
1192
+ ok: false,
1193
+ status: 200,
1194
+ error: `JSON parse error: ${message}`,
1195
+ raw: rawText
1196
+ };
1197
+ }
1198
+ const inner = parseDataStr(parsed.data_str, rawText);
1199
+ if (!inner.ok) return inner;
1200
+ const statusMap = inner.data?.status_map ?? {};
1201
+ return {
1202
+ ok: true,
1203
+ data: flatObjectToRows(statusMap, "component", "status")
1204
+ };
1205
+ }
1206
+
1207
+ // src/tools/rest/decode-nested-json.ts
1208
+ function decodeNestedJsonStrings(obj) {
1209
+ return Object.fromEntries(
1210
+ Object.entries(obj).map(([k, v]) => {
1211
+ if (typeof v !== "string") return [k, v];
1212
+ try {
1213
+ const parsed = JSON.parse(v);
1214
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
1215
+ return [k, decodeNestedJsonStrings(parsed)];
1216
+ }
1217
+ return [k, parsed];
1218
+ } catch {
1219
+ return [k, v];
1220
+ }
1221
+ })
1222
+ );
1223
+ }
1224
+
1225
+ // src/tools/rest/flatten-rank-stats.ts
1226
+ function findTier(tiers, prefix) {
1227
+ for (const [name, data] of Object.entries(tiers)) {
1228
+ if (name === prefix || name.startsWith(`${prefix}.`) || name.startsWith(`${prefix}_`) || name.startsWith(prefix) && /^\d/.test(name.slice(prefix.length))) {
1229
+ if (typeof data === "object" && data !== null && !Array.isArray(data)) {
1230
+ return data;
1231
+ }
1232
+ }
1233
+ }
1234
+ return void 0;
1235
+ }
1236
+ function overallField(tier, field) {
1237
+ if (!tier) return "";
1238
+ const overall = tier.overall;
1239
+ if (typeof overall !== "object" || overall === null) return "";
1240
+ const value = overall[field];
1241
+ return stringifyValue(value);
1242
+ }
1243
+ function computePercent(used, limit) {
1244
+ if (!used || !limit) return "";
1245
+ const usedN = Number(used);
1246
+ const limitN = Number(limit);
1247
+ if (!Number.isFinite(usedN) || !Number.isFinite(limitN) || limitN === 0) return "";
1248
+ return `${(usedN / limitN * 100).toFixed(1)}%`;
1249
+ }
1250
+ function flattenRanksSummary(decoded) {
1251
+ const ranksRaw = typeof decoded.ranks === "object" && decoded.ranks !== null ? decoded.ranks : decoded;
1252
+ return Object.entries(ranksRaw).filter(([, v]) => typeof v === "object" && v !== null && !Array.isArray(v)).map(([rankId, rankData]) => {
1253
+ const rank = rankData;
1254
+ const tiers = typeof rank.tiers === "object" && rank.tiers !== null ? rank.tiers : {};
1255
+ const ramUsed = overallField(findTier(tiers, "RAM"), "used");
1256
+ const ramLimit = overallField(findTier(tiers, "RAM"), "limit");
1257
+ return {
1258
+ rank: rankId,
1259
+ ram_used: ramUsed,
1260
+ ram_limit: ramLimit,
1261
+ ram_percent: computePercent(ramUsed, ramLimit),
1262
+ persist_used: overallField(findTier(tiers, "PERSIST"), "used"),
1263
+ disk_used: overallField(findTier(tiers, "DISK"), "used"),
1264
+ vram_used: overallField(findTier(tiers, "VRAM"), "used")
1265
+ };
1266
+ });
1267
+ }
1268
+ function flattenRankDetail(rankData) {
1269
+ const tiersRaw = typeof rankData.tiers === "object" && rankData.tiers !== null ? rankData.tiers : {};
1270
+ const tiers = Object.entries(tiersRaw).filter(([, v]) => typeof v === "object" && v !== null && !Array.isArray(v)).map(([tierName, tierData]) => {
1271
+ const tier = tierData;
1272
+ const overall = typeof tier.overall === "object" && tier.overall !== null ? tier.overall : {};
1273
+ const stats = typeof tier.stats === "object" && tier.stats !== null ? tier.stats : {};
1274
+ const row = { tier: tierName };
1275
+ for (const [k, v] of Object.entries(overall)) {
1276
+ row[k] = stringifyValue(v);
1277
+ }
1278
+ for (const [k, v] of Object.entries(stats)) {
1279
+ row[k] = stringifyValue(v);
1280
+ }
1281
+ return row;
1282
+ });
1283
+ const rgRaw = typeof rankData.resource_groups === "object" && rankData.resource_groups !== null ? rankData.resource_groups : {};
1284
+ const resource_groups = Object.entries(rgRaw).filter(([, v]) => typeof v === "object" && v !== null && !Array.isArray(v)).map(([rgName, rgData]) => {
1285
+ const rg = rgData;
1286
+ const row = { name: rgName };
1287
+ for (const [k, v] of Object.entries(rg)) {
1288
+ row[k] = stringifyValue(v);
1289
+ }
1290
+ return row;
1291
+ });
1292
+ return { tiers, resource_groups };
1293
+ }
1294
+
1295
+ // src/tools/rest/metrics.ts
1296
+ async function getMetrics(session2, nodeId) {
1297
+ let response;
1298
+ let rawText;
1299
+ try {
1300
+ response = await session2.makeRequest("/show/resource/statistics", {
1301
+ options: {}
1302
+ });
1303
+ rawText = await response.text();
1304
+ } catch (err) {
1305
+ const message = err instanceof Error ? err.message : String(err);
1306
+ return { ok: false, status: 0, error: message, raw: "" };
1307
+ }
1308
+ if (!response.ok) {
1309
+ return {
1310
+ ok: false,
1311
+ status: response.status,
1312
+ error: `HTTP ${response.status}`,
1313
+ raw: rawText
1314
+ };
1315
+ }
1316
+ let parsed;
1317
+ try {
1318
+ parsed = JSON.parse(rawText);
1319
+ } catch (err) {
1320
+ const message = err instanceof Error ? err.message : String(err);
1321
+ return {
1322
+ ok: false,
1323
+ status: 200,
1324
+ error: `JSON parse error: ${message}`,
1325
+ raw: rawText
1326
+ };
1327
+ }
1328
+ const inner = parseDataStr(parsed.data_str, rawText);
1329
+ if (!inner.ok) return inner;
1330
+ const statisticsMap = inner.data?.statistics_map ?? {};
1331
+ const decoded = decodeNestedJsonStrings(statisticsMap);
1332
+ const rows = flattenRanksSummary(decoded);
1333
+ if (nodeId !== void 0) {
1334
+ return { ok: true, data: rows, note: `Filtered for node: ${nodeId}` };
1335
+ }
1336
+ return { ok: true, data: rows };
1337
+ }
1338
+
1339
+ // src/tools/rest/summarize-shards.ts
1340
+ var EMPTY_SUMMARY = {
1341
+ shard_array_version: void 0,
1342
+ total_shards: 0,
1343
+ rank_count: 0,
1344
+ distribution: [],
1345
+ balanced: true
1346
+ };
1347
+ function summarizeShards(raw) {
1348
+ if (raw === null || raw === void 0 || typeof raw !== "object") {
1349
+ return EMPTY_SUMMARY;
1350
+ }
1351
+ const obj = raw;
1352
+ const version = typeof obj.shard_array_version === "number" ? obj.shard_array_version : void 0;
1353
+ const shardMap = obj.shard_map;
1354
+ if (shardMap === null || shardMap === void 0 || typeof shardMap !== "object" || Array.isArray(shardMap)) {
1355
+ return { ...EMPTY_SUMMARY, shard_array_version: version };
1356
+ }
1357
+ const entries = Object.values(shardMap);
1358
+ if (entries.length === 0) {
1359
+ return { ...EMPTY_SUMMARY, shard_array_version: version };
1360
+ }
1361
+ const counts = entries.reduce((acc, rank) => {
1362
+ const map = new Map(acc);
1363
+ map.set(rank, (acc.get(rank) ?? 0) + 1);
1364
+ return map;
1365
+ }, /* @__PURE__ */ new Map());
1366
+ const totalShards = entries.length;
1367
+ const distribution = [...counts.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([rank, count]) => ({
1368
+ rank,
1369
+ shard_count: count,
1370
+ percent: `${(count / totalShards * 100).toFixed(1)}%`
1371
+ }));
1372
+ const shardCounts = distribution.map((d) => d.shard_count);
1373
+ const balanced = Math.max(...shardCounts) - Math.min(...shardCounts) <= 1;
1374
+ return {
1375
+ shard_array_version: version,
1376
+ total_shards: totalShards,
1377
+ rank_count: counts.size,
1378
+ distribution,
1379
+ balanced
1380
+ };
1381
+ }
1382
+
1383
+ // src/tools/rest/system-properties.ts
1384
+ var import_zod = require("zod");
1385
+ var GetSystemPropertiesSchema = import_zod.z.object({
1386
+ category: import_zod.z.string().optional(),
1387
+ key_pattern: import_zod.z.string().optional()
1388
+ });
1389
+ async function getSystemProperties(session2, input5) {
1390
+ try {
1391
+ const response = await session2.makeRequest("/show/system/properties", {
1392
+ options: {}
1393
+ });
1394
+ if (!response.ok) {
1395
+ const raw2 = await response.text();
1396
+ return {
1397
+ ok: false,
1398
+ status: response.status,
1399
+ error: `HTTP ${response.status}`,
1400
+ raw: raw2
1401
+ };
1402
+ }
1403
+ const raw = await response.text();
1404
+ let parsed;
1405
+ try {
1406
+ parsed = JSON.parse(raw);
1407
+ } catch (parseError) {
1408
+ const message = parseError instanceof Error ? parseError.message : String(parseError);
1409
+ return {
1410
+ ok: false,
1411
+ status: 200,
1412
+ error: `JSON parse error: ${message}`,
1413
+ raw
1414
+ };
1415
+ }
1416
+ const inner = parseDataStr(parsed.data_str, raw);
1417
+ if (!inner.ok) return inner;
1418
+ const propertyMap = inner.data?.property_map ?? {};
1419
+ const filteredMap = applyFilters(propertyMap, input5);
1420
+ return {
1421
+ ok: true,
1422
+ data: flatObjectToRows(filteredMap, "property", "value"),
1423
+ rowCount: Object.keys(filteredMap).length
1424
+ };
1425
+ } catch (error) {
1426
+ const message = error instanceof Error ? error.message : String(error);
1427
+ return {
1428
+ ok: false,
1429
+ status: 0,
1430
+ error: message,
1431
+ raw: ""
1432
+ };
1433
+ }
1434
+ }
1435
+ function applyFilters(propertyMap, input5) {
1436
+ const { category, key_pattern } = input5;
1437
+ if (category === void 0 && key_pattern === void 0) {
1438
+ return propertyMap;
1439
+ }
1440
+ const patternLower = key_pattern?.toLowerCase();
1441
+ return Object.fromEntries(
1442
+ Object.entries(propertyMap).filter(([key]) => {
1443
+ const matchesCategory = category === void 0 || key.startsWith(category);
1444
+ const matchesPattern = patternLower === void 0 || key.toLowerCase().includes(patternLower);
1445
+ return matchesCategory && matchesPattern;
1446
+ })
1447
+ );
1448
+ }
1449
+
1450
+ // src/tools/rest/discover-hm-port.ts
1451
+ var DEFAULT_HM_PORT2 = 9300;
1452
+ async function discoverHmPort(session2) {
1453
+ const result = await getSystemProperties(session2, { key_pattern: "hm_http_port" });
1454
+ if (!result.ok) return DEFAULT_HM_PORT2;
1455
+ const rows = result.data;
1456
+ const entry = rows.find((r) => r.property?.includes("hm_http_port"));
1457
+ if (!entry?.value) return DEFAULT_HM_PORT2;
1458
+ const port = parseInt(entry.value, 10);
1459
+ return Number.isFinite(port) ? port : DEFAULT_HM_PORT2;
1460
+ }
1461
+
1462
+ // src/tools/rest/cluster.ts
1463
+ async function fetchJson(session2, endpoint, body) {
1464
+ let response;
1465
+ let rawText;
1466
+ try {
1467
+ response = await session2.makeRequest(endpoint, body);
1468
+ rawText = await response.text();
1469
+ } catch (err) {
1470
+ const message = err instanceof Error ? err.message : String(err);
1471
+ return { ok: false, status: 0, error: message, raw: "" };
1472
+ }
1473
+ if (!response.ok) {
1474
+ return {
1475
+ ok: false,
1476
+ status: response.status,
1477
+ error: `HTTP ${response.status}`,
1478
+ raw: rawText
1479
+ };
1480
+ }
1481
+ try {
1482
+ const parsed = JSON.parse(rawText);
1483
+ return { ok: true, data: parsed };
1484
+ } catch (err) {
1485
+ const message = err instanceof Error ? err.message : String(err);
1486
+ return {
1487
+ ok: false,
1488
+ status: 200,
1489
+ error: `JSON parse error: ${message}`,
1490
+ raw: rawText
1491
+ };
1492
+ }
1493
+ }
1494
+ async function fetchJsonOnPort(session2, port, endpoint, body) {
1495
+ if (!session2.makeRequestToPort) {
1496
+ return {
1497
+ ok: false,
1498
+ status: 0,
1499
+ error: "makeRequestToPort not available on this session",
1500
+ raw: ""
1501
+ };
1502
+ }
1503
+ let response;
1504
+ let rawText;
1505
+ try {
1506
+ response = await session2.makeRequestToPort(port, endpoint, body);
1507
+ rawText = await response.text();
1508
+ } catch (err) {
1509
+ const message = err instanceof Error ? err.message : String(err);
1510
+ return { ok: false, status: 0, error: message, raw: "" };
1511
+ }
1512
+ if (!response.ok) {
1513
+ return {
1514
+ ok: false,
1515
+ status: response.status,
1516
+ error: `HTTP ${response.status}`,
1517
+ raw: rawText
1518
+ };
1519
+ }
1520
+ try {
1521
+ const parsed = JSON.parse(rawText);
1522
+ return { ok: true, data: parsed };
1523
+ } catch (err) {
1524
+ const message = err instanceof Error ? err.message : String(err);
1525
+ return {
1526
+ ok: false,
1527
+ status: 200,
1528
+ error: `JSON parse error: ${message}`,
1529
+ raw: rawText
1530
+ };
1531
+ }
1532
+ }
1533
+ async function clusterStatus(session2) {
1534
+ const opsResult = await fetchJson(session2, "/admin/show/cluster/operations", { options: {} });
1535
+ if (!opsResult.ok) return opsResult;
1536
+ const shardsResult = await fetchJson(session2, "/admin/show/shards", {
1537
+ options: {}
1538
+ });
1539
+ if (!shardsResult.ok) return shardsResult;
1540
+ const hmPort = await discoverHmPort(session2);
1541
+ const alertsResult = await fetchJsonOnPort(session2, hmPort, "/admin/show/alerts", {
1542
+ num_alerts: 50,
1543
+ options: {}
1544
+ });
1545
+ const alertsAvailable = alertsResult.ok;
1546
+ const jobsResult = await fetchJson(session2, "/admin/show/jobs", {
1547
+ options: { show_async_jobs: "true", show_worker_info: "true" }
1548
+ });
1549
+ if (!jobsResult.ok) return jobsResult;
1550
+ let alerts = [];
1551
+ if (alertsAvailable) {
1552
+ const alertsOuter = alertsResult.data;
1553
+ const alertsInner = parseDataStr(
1554
+ alertsOuter.data_str,
1555
+ JSON.stringify(alertsResult.data)
1556
+ );
1557
+ if (!alertsInner.ok) return alertsInner;
1558
+ const timestamps = alertsInner.data?.timestamps ?? [];
1559
+ const alertTypes = alertsInner.data?.types ?? [];
1560
+ const alertParams = alertsInner.data?.params ?? [];
1561
+ alerts = timestamps.map((ts, i) => ({
1562
+ timestamp: ts,
1563
+ type: alertTypes[i] ?? "",
1564
+ params: alertParams[i] ?? ""
1565
+ }));
1566
+ }
1567
+ const jobsOuter = jobsResult.data;
1568
+ const jobsInner = parseDataStr(jobsOuter.data_str, JSON.stringify(jobsResult.data));
1569
+ if (!jobsInner.ok) return jobsInner;
1570
+ const jobIds = jobsInner.data?.job_id ?? [];
1571
+ const jobStatuses = jobsInner.data?.status ?? [];
1572
+ const jobEndpoints = jobsInner.data?.endpoint_name ?? [];
1573
+ const jobs = jobIds.map((id, i) => ({
1574
+ job_id: id,
1575
+ status: jobStatuses[i] ?? "",
1576
+ endpoint: jobEndpoints[i] ?? ""
1577
+ }));
1578
+ return {
1579
+ ok: true,
1580
+ data: {
1581
+ operations: opsResult.data,
1582
+ shards: summarizeShards(shardsResult.data),
1583
+ alerts,
1584
+ jobs
1585
+ },
1586
+ ...alertsAvailable ? {} : { note: "/admin/show/alerts not available on this version \u2014 alerts array is empty" }
1587
+ };
1588
+ }
1589
+
1590
+ // src/tools/rest/node.ts
1591
+ async function nodeDetails(session2, nodeId) {
1592
+ let response;
1593
+ let rawText;
1594
+ try {
1595
+ response = await session2.makeRequest("/show/resource/statistics", {
1596
+ options: {}
1597
+ });
1598
+ rawText = await response.text();
1599
+ } catch (err) {
1600
+ const message = err instanceof Error ? err.message : String(err);
1601
+ return { ok: false, status: 0, error: message, raw: "" };
1602
+ }
1603
+ if (!response.ok) {
1604
+ return {
1605
+ ok: false,
1606
+ status: response.status,
1607
+ error: `HTTP ${response.status}`,
1608
+ raw: rawText
1609
+ };
1610
+ }
1611
+ let parsed;
1612
+ try {
1613
+ parsed = JSON.parse(rawText);
1614
+ } catch (err) {
1615
+ const message = err instanceof Error ? err.message : String(err);
1616
+ return {
1617
+ ok: false,
1618
+ status: 200,
1619
+ error: `JSON parse error: ${message}`,
1620
+ raw: rawText
1621
+ };
1622
+ }
1623
+ const inner = parseDataStr(
1624
+ parsed.data_str,
1625
+ rawText
1626
+ );
1627
+ if (!inner.ok) return inner;
1628
+ const rawMap = inner.data?.statistics_map ?? {};
1629
+ const decoded = decodeNestedJsonStrings(rawMap);
1630
+ const ranks = typeof decoded.ranks === "object" && decoded.ranks !== null ? decoded.ranks : decoded;
1631
+ if (nodeId !== void 0) {
1632
+ if (Object.prototype.hasOwnProperty.call(ranks, nodeId)) {
1633
+ return {
1634
+ ok: true,
1635
+ data: flattenRankDetail(ranks[nodeId])
1636
+ };
1637
+ }
1638
+ return {
1639
+ ok: true,
1640
+ data: flattenRanksSummary(decoded),
1641
+ note: `node_id '${nodeId}' not found in statistics_map \u2014 returning all nodes`
1642
+ };
1643
+ }
1644
+ return { ok: true, data: flattenRanksSummary(decoded) };
1645
+ }
1646
+
1647
+ // src/tools/rest/logs.ts
1648
+ var import_zod2 = require("zod");
1649
+ var LOG_SOURCES = ["kinetica", "rank", "syslog", "gadmin", "reveal", "workbench"];
1650
+ var LOG_SEVERITIES = ["DEBUG", "INFO", "WARN", "ERROR", "FATAL"];
1651
+ var GetLogsSchema = import_zod2.z.object({
1652
+ source: import_zod2.z.enum(LOG_SOURCES),
1653
+ min_severity: import_zod2.z.enum(LOG_SEVERITIES).default("INFO"),
1654
+ duration: import_zod2.z.string().regex(
1655
+ /^\d+[mhd]$/,
1656
+ "Duration must match pattern: digits followed by m, h, or d (e.g. '1h', '30m', '7d')"
1657
+ ).optional(),
1658
+ start_time: import_zod2.z.string().datetime().optional(),
1659
+ end_time: import_zod2.z.string().datetime().optional(),
1660
+ node_id: import_zod2.z.string().optional(),
1661
+ limit: import_zod2.z.number().int().min(1).max(5e3).default(500)
1662
+ }).refine(
1663
+ (data) => {
1664
+ const hasDuration = data.duration !== void 0;
1665
+ const hasAbsoluteTime = data.start_time !== void 0 || data.end_time !== void 0;
1666
+ return !(hasDuration && hasAbsoluteTime);
1667
+ },
1668
+ {
1669
+ message: "duration and start_time/end_time are mutually exclusive -- use one or the other, not both"
1670
+ }
1671
+ );
1672
+ function buildStubResponse(input5) {
1673
+ return {
1674
+ ok: true,
1675
+ data: {
1676
+ note: "The /admin/show/logs endpoint is not yet implemented on this Kinetica server. Use kinetica_execute_sql to query ki_catalog system tables for log-like diagnostic data.",
1677
+ endpoint: "/admin/show/logs",
1678
+ status: "stub",
1679
+ requested_params: {
1680
+ source: input5.source,
1681
+ min_severity: input5.min_severity,
1682
+ duration: input5.duration,
1683
+ start_time: input5.start_time,
1684
+ end_time: input5.end_time,
1685
+ node_id: input5.node_id,
1686
+ limit: input5.limit
1687
+ }
1688
+ }
1689
+ };
1690
+ }
1691
+ async function getLogs(session2, input5) {
1692
+ const params = {
1693
+ source: input5.source,
1694
+ severity: input5.min_severity,
1695
+ limit: input5.limit
1696
+ };
1697
+ if (input5.duration !== void 0) {
1698
+ params.duration = input5.duration;
1699
+ }
1700
+ if (input5.start_time !== void 0) {
1701
+ params.start_time = input5.start_time;
1702
+ }
1703
+ if (input5.end_time !== void 0) {
1704
+ params.end_time = input5.end_time;
1705
+ }
1706
+ if (input5.node_id !== void 0) {
1707
+ params.node_id = input5.node_id;
1708
+ }
1709
+ try {
1710
+ const response = await session2.makeRequest("/admin/show/logs", params);
1711
+ if (!response.ok) {
1712
+ return buildStubResponse(input5);
1713
+ }
1714
+ const raw = await response.text();
1715
+ try {
1716
+ const data = JSON.parse(raw);
1717
+ return {
1718
+ ok: true,
1719
+ data
1720
+ };
1721
+ } catch {
1722
+ return buildStubResponse(input5);
1723
+ }
1724
+ } catch {
1725
+ return buildStubResponse(input5);
1726
+ }
1727
+ }
1728
+
1729
+ // src/tools/rest/show-configuration.ts
1730
+ var import_zod3 = require("zod");
1731
+
1732
+ // src/report/scrub.ts
1733
+ var CONFIG_SECRET_PATTERN = /([^\r\n=:]*(?:password|passwd|passphrase|license[_-]?key|private[_-]?key|secret)[^\r\n=:]*[:=][ \t]*)[^\r\n]+/gi;
1734
+ function redactConfigSecrets(content) {
1735
+ return content.replace(CONFIG_SECRET_PATTERN, "$1[REDACTED]");
1736
+ }
1737
+ var DEFAULT_SCRUB_PATTERNS = [
1738
+ /https?:\/\/[^\s"'`)\]]+/gi,
1739
+ // HTTP/HTTPS URLs
1740
+ /Basic\s+[A-Za-z0-9+/=]+/gi,
1741
+ // Basic auth headers
1742
+ /Bearer\s+[A-Za-z0-9._-]+/gi,
1743
+ // Bearer tokens
1744
+ /password[:\s]+[^\s"'`)\]]+/gi,
1745
+ // Password values (bare form: password: value)
1746
+ /"password"\s*:\s*"[^"]*"/gi,
1747
+ // JSON form: "password":"..."
1748
+ /(api[_-]?key|access[_-]?token|secret)["']?\s*[:=]\s*['"]?[^\s"'`)\]&,;]+/gi,
1749
+ // api_key / access_token / secret
1750
+ /(set-)?cookie\s*:\s*[^\r\n]+/gi,
1751
+ // Cookie / Set-Cookie headers
1752
+ /Authorization[:\s]+[^\s"'`)\]]+/gi
1753
+ // Authorization header values
1754
+ ];
1755
+ function scrubCredentials(content, patterns = DEFAULT_SCRUB_PATTERNS) {
1756
+ const configRedacted = redactConfigSecrets(content);
1757
+ return patterns.reduce((text, pattern) => text.replace(pattern, "[REDACTED]"), configRedacted);
1758
+ }
1759
+
1760
+ // src/tools/rest/show-configuration.ts
1761
+ var ShowConfigurationSchema = import_zod3.z.object({});
1762
+ async function showConfiguration(session2, _input) {
1763
+ if (!session2.makeRequestToPort) {
1764
+ return {
1765
+ ok: false,
1766
+ status: 0,
1767
+ error: "makeRequestToPort not available on this session",
1768
+ raw: ""
1769
+ };
1770
+ }
1771
+ const hmPort = await discoverHmPort(session2);
1772
+ let response;
1773
+ let rawText;
1774
+ try {
1775
+ response = await session2.makeRequestToPort(hmPort, "/admin/show/configuration", {});
1776
+ rawText = await response.text();
1777
+ } catch (err) {
1778
+ const message = err instanceof Error ? err.message : String(err);
1779
+ return { ok: false, status: 0, error: message, raw: "" };
1780
+ }
1781
+ if (!response.ok) {
1782
+ return {
1783
+ ok: false,
1784
+ status: response.status,
1785
+ error: `HTTP ${response.status}`,
1786
+ raw: rawText
1787
+ };
1788
+ }
1789
+ let outer;
1790
+ try {
1791
+ outer = JSON.parse(rawText);
1792
+ } catch (err) {
1793
+ const message = err instanceof Error ? err.message : String(err);
1794
+ return {
1795
+ ok: false,
1796
+ status: 200,
1797
+ error: `JSON parse error: ${message}`,
1798
+ raw: rawText
1799
+ };
1800
+ }
1801
+ const inner = parseDataStr(outer.data_str, rawText);
1802
+ if (!inner.ok) return inner;
1803
+ return {
1804
+ ok: true,
1805
+ data: {
1806
+ config_string: redactConfigSecrets(inner.data?.config_string ?? ""),
1807
+ info: inner.data?.info ?? {}
1808
+ }
1809
+ };
1810
+ }
1811
+
1812
+ // src/tools/rest/system-timing.ts
1813
+ async function systemTiming(session2) {
1814
+ let response;
1815
+ let rawText;
1816
+ try {
1817
+ response = await session2.makeRequest("/show/system/timing", { options: {} });
1818
+ rawText = await response.text();
1819
+ } catch (err) {
1820
+ const message = err instanceof Error ? err.message : String(err);
1821
+ return {
1822
+ ok: false,
1823
+ status: 0,
1824
+ error: message,
1825
+ raw: ""
1826
+ };
1827
+ }
1828
+ if (!response.ok) {
1829
+ return {
1830
+ ok: false,
1831
+ status: response.status,
1832
+ error: `HTTP ${response.status}`,
1833
+ raw: rawText
1834
+ };
1835
+ }
1836
+ let parsed;
1837
+ try {
1838
+ parsed = JSON.parse(rawText);
1839
+ } catch (err) {
1840
+ const message = err instanceof Error ? err.message : String(err);
1841
+ return {
1842
+ ok: false,
1843
+ status: 200,
1844
+ error: `JSON parse error: ${message}`,
1845
+ raw: rawText
1846
+ };
1847
+ }
1848
+ const inner = parseDataStr(parsed.data_str, rawText);
1849
+ if (!inner.ok) return inner;
1850
+ const endpoints = inner.data?.endpoints ?? [];
1851
+ const timings = inner.data?.time_in_ms ?? [];
1852
+ const jobIds = inner.data?.jobIds ?? [];
1853
+ const data = endpoints.map((endpoint, i) => ({
1854
+ endpoint,
1855
+ time_in_ms: timings[i] ?? 0,
1856
+ job_id: jobIds[i] ?? ""
1857
+ }));
1858
+ return {
1859
+ ok: true,
1860
+ data
1861
+ };
1862
+ }
1863
+
1864
+ // src/tools/rest/resource-groups.ts
1865
+ var import_zod4 = require("zod");
1866
+ var ResourceGroupsSchema = import_zod4.z.object({
1867
+ names: import_zod4.z.array(import_zod4.z.string()).optional().default([""]),
1868
+ show_tier_usage: import_zod4.z.boolean().optional()
1869
+ });
1870
+ async function getResourceGroups(session2, input5) {
1871
+ try {
1872
+ const response = await session2.makeRequest("/show/resourcegroups", {
1873
+ names: input5.names,
1874
+ options: {
1875
+ show_tier_usage: String(input5.show_tier_usage ?? false),
1876
+ show_default_values: "true",
1877
+ show_default_group: "true"
1878
+ }
1879
+ });
1880
+ if (!response.ok) {
1881
+ const raw2 = await response.text();
1882
+ return {
1883
+ ok: false,
1884
+ status: response.status,
1885
+ error: `HTTP ${response.status}`,
1886
+ raw: raw2
1887
+ };
1888
+ }
1889
+ const raw = await response.text();
1890
+ let parsed;
1891
+ try {
1892
+ parsed = JSON.parse(raw);
1893
+ } catch (parseError) {
1894
+ const message = parseError instanceof Error ? parseError.message : String(parseError);
1895
+ return {
1896
+ ok: false,
1897
+ status: 200,
1898
+ error: `JSON parse error: ${message}`,
1899
+ raw
1900
+ };
1901
+ }
1902
+ const inner = parseDataStr(parsed.data_str, raw);
1903
+ if (!inner.ok) return inner;
1904
+ return {
1905
+ ok: true,
1906
+ data: inner.data ?? {}
1907
+ };
1908
+ } catch (error) {
1909
+ const message = error instanceof Error ? error.message : String(error);
1910
+ return {
1911
+ ok: false,
1912
+ status: 0,
1913
+ error: message,
1914
+ raw: ""
1915
+ };
1916
+ }
1917
+ }
1918
+
1919
+ // src/tools/rest/verify-db.ts
1920
+ var import_zod5 = require("zod");
1921
+ var VerifyDbSchema = import_zod5.z.object({
1922
+ verify_nulls: import_zod5.z.boolean().optional(),
1923
+ verify_persist: import_zod5.z.boolean().optional(),
1924
+ verify_rank0: import_zod5.z.boolean().optional()
1925
+ });
1926
+ async function verifyDb(session2, input5) {
1927
+ const options = {
1928
+ concurrent_safe: "true"
1929
+ };
1930
+ if (input5.verify_nulls !== void 0) {
1931
+ options.verify_nulls = String(input5.verify_nulls);
1932
+ }
1933
+ if (input5.verify_persist !== void 0) {
1934
+ options.verify_persist = String(input5.verify_persist);
1935
+ }
1936
+ if (input5.verify_rank0 !== void 0) {
1937
+ options.verify_rank0 = String(input5.verify_rank0);
1938
+ }
1939
+ try {
1940
+ const response = await session2.makeRequest("/admin/verifydb", { options });
1941
+ if (!response.ok) {
1942
+ const raw2 = await response.text();
1943
+ return {
1944
+ ok: false,
1945
+ status: response.status,
1946
+ error: `HTTP ${response.status}`,
1947
+ raw: raw2
1948
+ };
1949
+ }
1950
+ const raw = await response.text();
1951
+ let parsed;
1952
+ try {
1953
+ parsed = JSON.parse(raw);
1954
+ } catch (parseError) {
1955
+ const message = parseError instanceof Error ? parseError.message : String(parseError);
1956
+ return {
1957
+ ok: false,
1958
+ status: 200,
1959
+ error: `JSON parse error: ${message}`,
1960
+ raw
1961
+ };
1962
+ }
1963
+ const inner = parseDataStr(parsed.data_str, raw);
1964
+ if (!inner.ok) return inner;
1965
+ const data = {
1966
+ verified_ok: inner.data?.verified_ok ?? false,
1967
+ error_list: inner.data?.error_list ?? [],
1968
+ orphaned_tables_total_size: inner.data?.orphaned_tables_total_size ?? 0
1969
+ };
1970
+ return {
1971
+ ok: true,
1972
+ data
1973
+ };
1974
+ } catch (error) {
1975
+ const message = error instanceof Error ? error.message : String(error);
1976
+ return {
1977
+ ok: false,
1978
+ status: 0,
1979
+ error: message,
1980
+ raw: ""
1981
+ };
1982
+ }
1983
+ }
1984
+
1985
+ // src/tools/rest/security.ts
1986
+ var import_zod6 = require("zod");
1987
+ var ShowSecuritySchema = import_zod6.z.object({
1988
+ names: import_zod6.z.array(import_zod6.z.string()).optional().default([""])
1989
+ });
1990
+ async function showSecurity(session2, input5) {
1991
+ try {
1992
+ const response = await session2.makeRequest("/show/security", {
1993
+ names: input5.names,
1994
+ options: {}
1995
+ });
1996
+ if (!response.ok) {
1997
+ const raw2 = await response.text();
1998
+ return {
1999
+ ok: false,
2000
+ status: response.status,
2001
+ error: `HTTP ${response.status}`,
2002
+ raw: raw2
2003
+ };
2004
+ }
2005
+ const raw = await response.text();
2006
+ let parsed;
2007
+ try {
2008
+ parsed = JSON.parse(raw);
2009
+ } catch (parseError) {
2010
+ const message = parseError instanceof Error ? parseError.message : String(parseError);
2011
+ return {
2012
+ ok: false,
2013
+ status: 200,
2014
+ error: `JSON parse error: ${message}`,
2015
+ raw
2016
+ };
2017
+ }
2018
+ const inner = parseDataStr(parsed.data_str, raw);
2019
+ if (!inner.ok) return inner;
2020
+ return {
2021
+ ok: true,
2022
+ data: inner.data ?? {}
2023
+ };
2024
+ } catch (error) {
2025
+ const message = error instanceof Error ? error.message : String(error);
2026
+ return {
2027
+ ok: false,
2028
+ status: 0,
2029
+ error: message,
2030
+ raw: ""
2031
+ };
2032
+ }
2033
+ }
2034
+
2035
+ // src/tools/rest/show-table.ts
2036
+ var import_zod7 = require("zod");
2037
+
2038
+ // src/tools/rest/parse-type-schema.ts
2039
+ function resolveUnionType(union) {
2040
+ for (const entry of union) {
2041
+ if (typeof entry === "string" && entry !== "null") {
2042
+ return entry;
2043
+ }
2044
+ }
2045
+ return "null";
2046
+ }
2047
+ function resolveFieldType(fieldType) {
2048
+ if (typeof fieldType === "string") {
2049
+ return fieldType;
2050
+ }
2051
+ if (Array.isArray(fieldType)) {
2052
+ return resolveUnionType(fieldType);
2053
+ }
2054
+ return void 0;
2055
+ }
2056
+ function parseTypeSchema(schemaJson) {
2057
+ let parsed;
2058
+ try {
2059
+ parsed = JSON.parse(schemaJson);
2060
+ } catch {
2061
+ return [];
2062
+ }
2063
+ if (typeof parsed !== "object" || parsed === null || !("type" in parsed) || !("fields" in parsed)) {
2064
+ return [];
2065
+ }
2066
+ const record = parsed;
2067
+ if (record.type !== "record" || !Array.isArray(record.fields)) {
2068
+ return [];
2069
+ }
2070
+ const columns = [];
2071
+ for (const field of record.fields) {
2072
+ if (typeof field !== "object" || field === null) continue;
2073
+ const f = field;
2074
+ if (typeof f.name !== "string" || f.type === void 0) continue;
2075
+ const resolvedType = resolveFieldType(f.type);
2076
+ if (resolvedType === void 0) continue;
2077
+ columns.push({ name: f.name, type: resolvedType });
2078
+ }
2079
+ return columns;
2080
+ }
2081
+
2082
+ // src/tools/rest/show-table.ts
2083
+ var ShowTableSchema = import_zod7.z.object({
2084
+ table_name: import_zod7.z.string().optional().default(""),
2085
+ get_sizes: import_zod7.z.boolean().optional(),
2086
+ get_access_data: import_zod7.z.boolean().optional(),
2087
+ get_column_info: import_zod7.z.boolean().optional()
2088
+ });
2089
+ function resolveColumnInfoOption(input5) {
2090
+ if (input5.get_column_info === true) return "true";
2091
+ if (input5.get_column_info === false) return "false";
2092
+ return input5.table_name !== "" ? "true" : "false";
2093
+ }
2094
+ function parseColumnProperties(propertiesJson) {
2095
+ try {
2096
+ const parsed = JSON.parse(propertiesJson);
2097
+ const result = /* @__PURE__ */ new Map();
2098
+ for (const [colName, props] of Object.entries(parsed)) {
2099
+ if (Array.isArray(props)) {
2100
+ result.set(colName, props.join(", "));
2101
+ }
2102
+ }
2103
+ return result;
2104
+ } catch {
2105
+ return /* @__PURE__ */ new Map();
2106
+ }
2107
+ }
2108
+ function buildColumnEntries(typeSchemaJson, propertiesStr) {
2109
+ if (!typeSchemaJson) return [];
2110
+ const columnInfos = parseTypeSchema(typeSchemaJson);
2111
+ if (columnInfos.length === 0) return [];
2112
+ const propsMap = propertiesStr !== void 0 ? parseColumnProperties(propertiesStr) : /* @__PURE__ */ new Map();
2113
+ return columnInfos.map((col) => ({
2114
+ name: col.name,
2115
+ type: col.type,
2116
+ properties: propsMap.get(col.name) ?? ""
2117
+ }));
2118
+ }
2119
+ function escapeSqlString(value) {
2120
+ return value.replace(/'/g, "''");
2121
+ }
2122
+ function splitSchemaTable(tableName) {
2123
+ const dot = tableName.indexOf(".");
2124
+ if (dot === -1) return null;
2125
+ return { schema: tableName.slice(0, dot), table: tableName.slice(dot + 1) };
2126
+ }
2127
+ async function fetchIndexes(session2, tableName) {
2128
+ const parts = splitSchemaTable(tableName);
2129
+ if (!parts) return [];
2130
+ const schema = escapeSqlString(parts.schema);
2131
+ const table = escapeSqlString(parts.table);
2132
+ const statement = `SELECT index_type, index_columns FROM ki_catalog.ki_indexes WHERE schema_name = '${schema}' AND object_name = '${table}'`;
2133
+ try {
2134
+ const response = await session2.makeRequest("/execute/sql", {
2135
+ statement,
2136
+ offset: 0,
2137
+ limit: 100,
2138
+ encoding: "json",
2139
+ options: {}
2140
+ });
2141
+ if (!response.ok) return [];
2142
+ const rawText = await response.text();
2143
+ const outer = JSON.parse(rawText);
2144
+ if (outer.status === "ERROR") return [];
2145
+ const dataStr = JSON.parse(outer.data_str);
2146
+ if (dataStr.total_number_of_records === 0) return [];
2147
+ const cols = JSON.parse(dataStr.json_encoded_response);
2148
+ const types = cols.column_1 ?? [];
2149
+ const indexCols = cols.column_2 ?? [];
2150
+ return types.map((t, i) => ({
2151
+ index_type: t,
2152
+ index_columns: indexCols[i] ?? ""
2153
+ }));
2154
+ } catch {
2155
+ return [];
2156
+ }
2157
+ }
2158
+ async function showTable(session2, input5) {
2159
+ const options = {
2160
+ get_sizes: String(input5.get_sizes ?? true),
2161
+ show_children: "false",
2162
+ no_error_if_not_exists: "true",
2163
+ get_column_info: resolveColumnInfoOption(input5)
2164
+ };
2165
+ if (input5.get_access_data !== void 0) {
2166
+ options.get_access_data = String(input5.get_access_data);
2167
+ }
2168
+ try {
2169
+ const response = await session2.makeRequest("/show/table", {
2170
+ table_name: input5.table_name,
2171
+ options
2172
+ });
2173
+ if (!response.ok) {
2174
+ const raw2 = await response.text();
2175
+ return {
2176
+ ok: false,
2177
+ status: response.status,
2178
+ error: `HTTP ${response.status}`,
2179
+ raw: raw2
2180
+ };
2181
+ }
2182
+ const raw = await response.text();
2183
+ let outer;
2184
+ try {
2185
+ outer = JSON.parse(raw);
2186
+ } catch (parseError) {
2187
+ const message = parseError instanceof Error ? parseError.message : String(parseError);
2188
+ return {
2189
+ ok: false,
2190
+ status: 200,
2191
+ error: `JSON parse error: ${message}`,
2192
+ raw
2193
+ };
2194
+ }
2195
+ let inner = {};
2196
+ if (typeof outer.data_str === "string") {
2197
+ try {
2198
+ inner = JSON.parse(outer.data_str);
2199
+ } catch (parseError) {
2200
+ const message = parseError instanceof Error ? parseError.message : String(parseError);
2201
+ return {
2202
+ ok: false,
2203
+ status: 200,
2204
+ error: `data_str parse error: ${message}`,
2205
+ raw
2206
+ };
2207
+ }
2208
+ }
2209
+ const tableNames = inner.table_names ?? [];
2210
+ const descriptions = inner.table_descriptions ?? [];
2211
+ const sizes = inner.sizes ?? [];
2212
+ const properties = inner.properties ?? [];
2213
+ const typeSchemas = inner.type_schemas;
2214
+ if (options.get_column_info === "true" && tableNames.length > 0) {
2215
+ const table = {
2216
+ table_name: tableNames[0],
2217
+ description: descriptions[0] ?? "",
2218
+ size: sizes[0] ?? "",
2219
+ properties: properties[0] ?? ""
2220
+ };
2221
+ const columns = buildColumnEntries(typeSchemas?.[0], typeSchemas ? properties[0] : void 0);
2222
+ const indexes = await fetchIndexes(session2, input5.table_name);
2223
+ return {
2224
+ ok: true,
2225
+ data: { table, columns, indexes }
2226
+ };
2227
+ }
2228
+ const data = tableNames.map((name, i) => ({
2229
+ table_name: name,
2230
+ description: descriptions[i] ?? "",
2231
+ size: sizes[i] ?? "",
2232
+ properties: properties[i] ?? ""
2233
+ }));
2234
+ return {
2235
+ ok: true,
2236
+ data
2237
+ };
2238
+ } catch (error) {
2239
+ const message = error instanceof Error ? error.message : String(error);
2240
+ return {
2241
+ ok: false,
2242
+ status: 0,
2243
+ error: message,
2244
+ raw: ""
2245
+ };
2246
+ }
2247
+ }
2248
+
2249
+ // src/tools/rest/resource-objects.ts
2250
+ var import_zod8 = require("zod");
2251
+ var ResourceObjectsSchema = import_zod8.z.object({
2252
+ table_names: import_zod8.z.string().optional().default("*"),
2253
+ tiers: import_zod8.z.string().optional(),
2254
+ order_by: import_zod8.z.string().optional(),
2255
+ limit: import_zod8.z.number().int().min(1).max(1e4).optional().default(100)
2256
+ });
2257
+ async function getResourceObjects(session2, input5) {
2258
+ const options = {
2259
+ table_names: input5.table_names ?? "*",
2260
+ limit: String(input5.limit ?? 100)
2261
+ };
2262
+ if (input5.tiers !== void 0) {
2263
+ options.tiers = input5.tiers;
2264
+ }
2265
+ if (input5.order_by !== void 0) {
2266
+ options.order_by = input5.order_by;
2267
+ }
2268
+ try {
2269
+ const response = await session2.makeRequest("/show/resource/objects", {
2270
+ options
2271
+ });
2272
+ if (!response.ok) {
2273
+ const raw2 = await response.text();
2274
+ return {
2275
+ ok: false,
2276
+ status: response.status,
2277
+ error: `HTTP ${response.status}`,
2278
+ raw: raw2
2279
+ };
2280
+ }
2281
+ const raw = await response.text();
2282
+ let parsed;
2283
+ try {
2284
+ parsed = JSON.parse(raw);
2285
+ } catch (parseError) {
2286
+ const message = parseError instanceof Error ? parseError.message : String(parseError);
2287
+ return {
2288
+ ok: false,
2289
+ status: 200,
2290
+ error: `JSON parse error: ${message}`,
2291
+ raw
2292
+ };
2293
+ }
2294
+ const inner = parseDataStr(parsed.data_str, raw);
2295
+ if (!inner.ok) return inner;
2296
+ return {
2297
+ ok: true,
2298
+ data: inner.data ?? {}
2299
+ };
2300
+ } catch (error) {
2301
+ const message = error instanceof Error ? error.message : String(error);
2302
+ return {
2303
+ ok: false,
2304
+ status: 0,
2305
+ error: message,
2306
+ raw: ""
2307
+ };
2308
+ }
2309
+ }
2310
+
2311
+ // src/tools/rest/host-manager.ts
2312
+ async function hostManagerStatus(session2) {
2313
+ if (!session2.makeRequestToPort) {
2314
+ return {
2315
+ ok: false,
2316
+ status: 0,
2317
+ error: "makeRequestToPort not available on this session",
2318
+ raw: ""
2319
+ };
2320
+ }
2321
+ const hmPort = await discoverHmPort(session2);
2322
+ let response;
2323
+ let rawText;
2324
+ try {
2325
+ response = await session2.makeRequestToPort(hmPort, "/", void 0);
2326
+ rawText = await response.text();
2327
+ } catch (err) {
2328
+ const message = err instanceof Error ? err.message : String(err);
2329
+ return {
2330
+ ok: false,
2331
+ status: 0,
2332
+ error: message,
2333
+ raw: ""
2334
+ };
2335
+ }
2336
+ if (!response.ok) {
2337
+ return {
2338
+ ok: false,
2339
+ status: response.status,
2340
+ error: `HTTP ${response.status}`,
2341
+ raw: rawText
2342
+ };
2343
+ }
2344
+ let parsed;
2345
+ try {
2346
+ parsed = JSON.parse(rawText);
2347
+ } catch (err) {
2348
+ const message = err instanceof Error ? err.message : String(err);
2349
+ return {
2350
+ ok: false,
2351
+ status: 200,
2352
+ error: `JSON parse error: ${message}`,
2353
+ raw: rawText
2354
+ };
2355
+ }
2356
+ return {
2357
+ ok: true,
2358
+ data: flatObjectToRows(parsed, "key", "value")
2359
+ };
2360
+ }
2361
+ async function hostManagerAlerts(session2) {
2362
+ if (!session2.makeRequestToPort) {
2363
+ return {
2364
+ ok: false,
2365
+ status: 0,
2366
+ error: "makeRequestToPort not available on this session",
2367
+ raw: ""
2368
+ };
2369
+ }
2370
+ const hmPort = await discoverHmPort(session2);
2371
+ let response;
2372
+ let rawText;
2373
+ try {
2374
+ response = await session2.makeRequestToPort(hmPort, "/admin/show/alerts", {
2375
+ num_alerts: 50,
2376
+ options: {}
2377
+ });
2378
+ rawText = await response.text();
2379
+ } catch (err) {
2380
+ const message = err instanceof Error ? err.message : String(err);
2381
+ return { ok: false, status: 0, error: message, raw: "" };
2382
+ }
2383
+ if (!response.ok) {
2384
+ return {
2385
+ ok: false,
2386
+ status: response.status,
2387
+ error: `HTTP ${response.status}`,
2388
+ raw: rawText
2389
+ };
2390
+ }
2391
+ let outer;
2392
+ try {
2393
+ outer = JSON.parse(rawText);
2394
+ } catch (err) {
2395
+ const message = err instanceof Error ? err.message : String(err);
2396
+ return {
2397
+ ok: false,
2398
+ status: 200,
2399
+ error: `JSON parse error: ${message}`,
2400
+ raw: rawText
2401
+ };
2402
+ }
2403
+ const inner = parseDataStr(outer.data_str, rawText);
2404
+ if (!inner.ok) return inner;
2405
+ const timestamps = inner.data?.timestamps ?? [];
2406
+ const alertTypes = inner.data?.types ?? [];
2407
+ const alertParams = inner.data?.params ?? [];
2408
+ const alerts = timestamps.map((ts, i) => ({
2409
+ timestamp: ts,
2410
+ type: alertTypes[i] ?? "",
2411
+ params: alertParams[i] ?? ""
2412
+ }));
2413
+ return { ok: true, data: alerts };
2414
+ }
2415
+
2416
+ // src/tools/sql/execute.ts
2417
+ var import_zod9 = require("zod");
2418
+ var ExecuteSqlSchema = import_zod9.z.object({
2419
+ statement: import_zod9.z.string().min(1),
2420
+ limit: import_zod9.z.number().int().min(1).max(1e4).default(100)
2421
+ });
2422
+ var READ_ONLY_PREFIXES = /^\s*(SELECT|EXPLAIN|DESCRIBE|DESC)\b/i;
2423
+ var SQL_COMMENTS = /\/\*[\s\S]*?\*\/|--[^\n]*/g;
2424
+ function isCteReadOnly(stripped) {
2425
+ let depth = 0;
2426
+ let pastFirstParen = false;
2427
+ let finalStart = -1;
2428
+ for (let i = 0; i < stripped.length; i++) {
2429
+ const ch = stripped[i];
2430
+ if (ch === "(") {
2431
+ depth++;
2432
+ pastFirstParen = true;
2433
+ } else if (ch === ")") {
2434
+ depth--;
2435
+ if (depth === 0 && pastFirstParen) {
2436
+ finalStart = i + 1;
2437
+ }
2438
+ }
2439
+ }
2440
+ if (finalStart === -1) {
2441
+ return false;
2442
+ }
2443
+ const tail = stripped.slice(finalStart).trim();
2444
+ return READ_ONLY_PREFIXES.test(tail);
2445
+ }
2446
+ function isReadOnlySql(statement) {
2447
+ const stripped = statement.replace(SQL_COMMENTS, " ").trim();
2448
+ if (READ_ONLY_PREFIXES.test(stripped)) {
2449
+ return true;
2450
+ }
2451
+ if (/^\s*WITH\b/i.test(stripped)) {
2452
+ return isCteReadOnly(stripped);
2453
+ }
2454
+ return false;
2455
+ }
2456
+ async function executeSql(session2, statement, limit = 100) {
2457
+ if (!isReadOnlySql(statement)) {
2458
+ return {
2459
+ ok: false,
2460
+ status: 400,
2461
+ error: "SQL rejected: only SELECT, WITH, and EXPLAIN statements are permitted",
2462
+ raw: statement
2463
+ };
2464
+ }
2465
+ let response;
2466
+ let rawText;
2467
+ try {
2468
+ response = await session2.makeRequest("/execute/sql", {
2469
+ statement,
2470
+ offset: 0,
2471
+ limit,
2472
+ encoding: "json",
2473
+ options: {}
2474
+ });
2475
+ rawText = await response.text();
2476
+ } catch (err) {
2477
+ const message = err instanceof Error ? err.message : String(err);
2478
+ return { ok: false, status: 0, error: message, raw: "" };
2479
+ }
2480
+ if (!response.ok) {
2481
+ return {
2482
+ ok: false,
2483
+ status: response.status,
2484
+ error: `HTTP ${response.status}`,
2485
+ raw: rawText
2486
+ };
2487
+ }
2488
+ let outer;
2489
+ try {
2490
+ outer = JSON.parse(rawText);
2491
+ } catch (err) {
2492
+ const message = err instanceof Error ? err.message : String(err);
2493
+ return {
2494
+ ok: false,
2495
+ status: 200,
2496
+ error: `JSON parse error: ${message}`,
2497
+ raw: rawText
2498
+ };
2499
+ }
2500
+ if (outer.status === "ERROR") {
2501
+ return {
2502
+ ok: false,
2503
+ status: 400,
2504
+ error: outer.message,
2505
+ raw: rawText
2506
+ };
2507
+ }
2508
+ let dataStr;
2509
+ try {
2510
+ dataStr = JSON.parse(outer.data_str);
2511
+ } catch (err) {
2512
+ const message = err instanceof Error ? err.message : String(err);
2513
+ return {
2514
+ ok: false,
2515
+ status: 200,
2516
+ error: `data_str parse error: ${message}`,
2517
+ raw: rawText
2518
+ };
2519
+ }
2520
+ let rows;
2521
+ try {
2522
+ rows = JSON.parse(dataStr.json_encoded_response);
2523
+ } catch (err) {
2524
+ const message = err instanceof Error ? err.message : String(err);
2525
+ return {
2526
+ ok: false,
2527
+ status: 200,
2528
+ error: `JSON parse error: ${message}`,
2529
+ raw: rawText
2530
+ };
2531
+ }
2532
+ const totalRecords = dataStr.total_number_of_records;
2533
+ if (totalRecords === 0) {
2534
+ return {
2535
+ ok: true,
2536
+ data: [],
2537
+ rowCount: 0,
2538
+ note: "Query returned 0 rows"
2539
+ };
2540
+ }
2541
+ return {
2542
+ ok: true,
2543
+ data: rows,
2544
+ rowCount: totalRecords
2545
+ };
2546
+ }
2547
+
2548
+ // src/tools/sql/explain.ts
2549
+ var import_zod10 = require("zod");
2550
+ var ExplainQuerySchema = import_zod10.z.object({
2551
+ statement: import_zod10.z.string().min(1),
2552
+ limit: import_zod10.z.number().int().min(1).max(1e4).default(100)
2553
+ });
2554
+ async function explainQuery(session2, statement, limit = 100) {
2555
+ return executeSql(session2, "EXPLAIN " + statement.trimStart(), limit);
2556
+ }
2557
+
2558
+ // src/tools/sql/enrich-error.ts
2559
+ var KI_CATALOG_TABLE_RE = /(?:FROM|JOIN)\s+ki_catalog\.(\w+)/i;
2560
+ function enrichSqlError(error, statement, schemas) {
2561
+ if (!schemas) return error;
2562
+ const match = KI_CATALOG_TABLE_RE.exec(statement);
2563
+ if (!match) return error;
2564
+ const tableName = match[1];
2565
+ const columns = schemas.tables.get(tableName);
2566
+ if (!columns) return error;
2567
+ return `${error}
2568
+
2569
+ Verified columns for ${tableName}: ${columns.join(", ")}`;
2570
+ }
2571
+
2572
+ // src/tools/mutation/alter-system-properties.ts
2573
+ var import_zod11 = require("zod");
2574
+ var AlterSystemPropertiesSchema = import_zod11.z.object({
2575
+ /**
2576
+ * Map of property key -> new value to apply at runtime.
2577
+ * At least one entry is required.
2578
+ */
2579
+ property_updates_map: import_zod11.z.record(import_zod11.z.string(), import_zod11.z.string()).refine((map) => Object.keys(map).length >= 1, {
2580
+ message: "property_updates_map must have at least one entry"
2581
+ })
2582
+ });
2583
+ var ALTERABLE_PROPERTIES = /* @__PURE__ */ new Set([
2584
+ "concurrent_kernel_execution",
2585
+ "subtask_concurrency_limit",
2586
+ "chunk_size",
2587
+ "chunk_column_max_memory",
2588
+ "chunk_max_memory",
2589
+ "execution_mode",
2590
+ "external_files_directory",
2591
+ "request_timeout",
2592
+ "max_get_records_size",
2593
+ "enable_audit",
2594
+ "audit_headers",
2595
+ "audit_body",
2596
+ "audit_data",
2597
+ "audit_response",
2598
+ "shadow_agg_size",
2599
+ "shadow_filter_size",
2600
+ "enable_overlapped_equi_join",
2601
+ "enable_one_step_compound_equi_join",
2602
+ "kafka_batch_size",
2603
+ "kafka_poll_timeout",
2604
+ "kafka_wait_time",
2605
+ "egress_parquet_compression",
2606
+ "egress_single_file_max_size",
2607
+ "max_concurrent_kernels",
2608
+ "system_metadata_retention_period",
2609
+ "tcs_per_tom",
2610
+ "tps_per_tom",
2611
+ "background_worker_threads",
2612
+ "log_debug_job_info",
2613
+ "enable_thread_hang_logging",
2614
+ "ai_enable_rag",
2615
+ "ai_api_provider",
2616
+ "ai_api_url",
2617
+ "ai_api_key",
2618
+ "ai_api_connection_timeout",
2619
+ "ai_api_embeddings_model",
2620
+ "telm_persist_query_metrics",
2621
+ "postgres_proxy_idle_connection_timeout",
2622
+ "postgres_proxy_keep_alive",
2623
+ "kifs_directory_data_limit",
2624
+ "compression_codec",
2625
+ "disk_auto_optimize_timeout",
2626
+ "ha_consumer_replay_offset"
2627
+ ]);
2628
+ var BLOCKED_PROPERTIES = /* @__PURE__ */ new Set([
2629
+ "ai_api_key",
2630
+ // credential — would appear in audit logs
2631
+ "external_files_directory"
2632
+ // filesystem path — potential path traversal
2633
+ ]);
2634
+ function findDisallowedProperties(requestedKeys) {
2635
+ return requestedKeys.filter(
2636
+ (key) => !ALTERABLE_PROPERTIES.has(key) || BLOCKED_PROPERTIES.has(key)
2637
+ );
2638
+ }
2639
+ async function readRequestedProperties(session2, requestedKeys) {
2640
+ try {
2641
+ const response = await session2.makeRequest("/show/system/properties", {
2642
+ options: {}
2643
+ });
2644
+ if (!response.ok) return {};
2645
+ const raw = await response.text();
2646
+ let parsed;
2647
+ try {
2648
+ parsed = JSON.parse(raw);
2649
+ } catch {
2650
+ return {};
2651
+ }
2652
+ const inner = parseDataStr(parsed.data_str, raw);
2653
+ if (!inner.ok) return {};
2654
+ const propertyMap = inner.data?.property_map ?? {};
2655
+ return Object.fromEntries(
2656
+ requestedKeys.filter((key) => Object.prototype.hasOwnProperty.call(propertyMap, key)).map((key) => [key, propertyMap[key]])
2657
+ );
2658
+ } catch {
2659
+ return {};
2660
+ }
2661
+ }
2662
+ function computeVerification(requestedMap, afterState) {
2663
+ for (const [key, expectedValue] of Object.entries(requestedMap)) {
2664
+ if (afterState[key] !== expectedValue) {
2665
+ return "failed";
2666
+ }
2667
+ }
2668
+ return "confirmed";
2669
+ }
2670
+ async function alterSystemProperties(session2, input5) {
2671
+ const requestedKeys = Object.keys(input5.property_updates_map);
2672
+ const disallowed = findDisallowedProperties(requestedKeys);
2673
+ if (disallowed.length > 0) {
2674
+ return {
2675
+ ok: false,
2676
+ status: 400,
2677
+ error: `Property rejected: ${disallowed.map((k) => `'${k}'`).join(", ")} not supported by /alter/system/properties`,
2678
+ raw: ""
2679
+ };
2680
+ }
2681
+ const beforeState = await readRequestedProperties(session2, requestedKeys);
2682
+ let mutationResponse;
2683
+ let rawText;
2684
+ try {
2685
+ mutationResponse = await session2.makeRequest("/alter/system/properties", {
2686
+ property_updates_map: input5.property_updates_map
2687
+ });
2688
+ rawText = await mutationResponse.text();
2689
+ } catch (error) {
2690
+ const message = error instanceof Error ? error.message : String(error);
2691
+ return { ok: false, status: 0, error: message, raw: "" };
2692
+ }
2693
+ if (!mutationResponse.ok) {
2694
+ return {
2695
+ ok: false,
2696
+ status: mutationResponse.status,
2697
+ error: `HTTP ${mutationResponse.status}`,
2698
+ raw: rawText
2699
+ };
2700
+ }
2701
+ let parsedMutation;
2702
+ try {
2703
+ parsedMutation = JSON.parse(rawText);
2704
+ } catch (parseError) {
2705
+ const message = parseError instanceof Error ? parseError.message : String(parseError);
2706
+ return {
2707
+ ok: false,
2708
+ status: 200,
2709
+ error: `JSON parse error: ${message}`,
2710
+ raw: rawText
2711
+ };
2712
+ }
2713
+ const innerMutation = parseDataStr(parsedMutation.data_str, rawText);
2714
+ if (!innerMutation.ok) return innerMutation;
2715
+ const updatedPropertiesMap = innerMutation.data?.updated_properties_map ?? {};
2716
+ let afterState;
2717
+ let verification;
2718
+ try {
2719
+ const verifyResponse = await session2.makeRequest("/show/system/properties", { options: {} });
2720
+ if (!verifyResponse.ok) {
2721
+ afterState = {};
2722
+ verification = "unavailable";
2723
+ } else {
2724
+ const verifyRaw = await verifyResponse.text();
2725
+ let parsedVerify;
2726
+ try {
2727
+ parsedVerify = JSON.parse(verifyRaw);
2728
+ } catch {
2729
+ afterState = {};
2730
+ verification = "unavailable";
2731
+ const data2 = {
2732
+ updated_properties_map: updatedPropertiesMap,
2733
+ before_state: beforeState,
2734
+ after_state: afterState,
2735
+ verification
2736
+ };
2737
+ return { ok: true, data: data2 };
2738
+ }
2739
+ const innerVerify = parseDataStr(parsedVerify.data_str, verifyRaw);
2740
+ if (!innerVerify.ok) {
2741
+ afterState = {};
2742
+ verification = "unavailable";
2743
+ } else {
2744
+ const verifyPropertyMap = innerVerify.data?.property_map ?? {};
2745
+ afterState = Object.fromEntries(
2746
+ requestedKeys.filter((key) => Object.prototype.hasOwnProperty.call(verifyPropertyMap, key)).map((key) => [key, verifyPropertyMap[key]])
2747
+ );
2748
+ verification = computeVerification(input5.property_updates_map, afterState);
2749
+ }
2750
+ }
2751
+ } catch {
2752
+ afterState = {};
2753
+ verification = "unavailable";
2754
+ }
2755
+ const data = {
2756
+ updated_properties_map: updatedPropertiesMap,
2757
+ before_state: beforeState,
2758
+ after_state: afterState,
2759
+ verification
2760
+ };
2761
+ return { ok: true, data };
2762
+ }
2763
+
2764
+ // src/tools/mutation/execute-mutation-sql.ts
2765
+ var import_zod12 = require("zod");
2766
+ var ExecuteMutationSqlSchema = import_zod12.z.object({
2767
+ statement: import_zod12.z.string().min(1),
2768
+ limit: import_zod12.z.number().int().min(1).max(1e4).default(100)
2769
+ });
2770
+ var SQL_COMMENTS2 = /\/\*[\s\S]*?\*\/|--[^\n]*/g;
2771
+ var DENY_LIST_PATTERN = /\b(DROP\s+(TABLE|SCHEMA|DATABASE|INDEX|VIEW|MATERIALIZED\s+VIEW|PROCEDURE|FUNCTION|SEQUENCE|TYPE)\b|TRUNCATE(\s+TABLE)?\b|DELETE\s+FROM\b|DELETE\b(?!\s+INDEX)|(?<!FOR\s)UPDATE\s+\w)/i;
2772
+ function isDeniedMutationSql(statement) {
2773
+ const stripped = statement.replace(SQL_COMMENTS2, " ").trim();
2774
+ return DENY_LIST_PATTERN.test(stripped);
2775
+ }
2776
+ async function executeMutationSql(session2, statement, limit = 100) {
2777
+ if (isDeniedMutationSql(statement)) {
2778
+ return {
2779
+ ok: false,
2780
+ status: 400,
2781
+ error: "SQL rejected: destructive statements (DROP, TRUNCATE, DELETE, UPDATE) are not permitted, even inside a CTE",
2782
+ raw: statement
2783
+ };
2784
+ }
2785
+ let response;
2786
+ let rawText;
2787
+ try {
2788
+ response = await session2.makeRequest("/execute/sql", {
2789
+ statement,
2790
+ offset: 0,
2791
+ limit,
2792
+ encoding: "json",
2793
+ options: {}
2794
+ });
2795
+ rawText = await response.text();
2796
+ } catch (err) {
2797
+ const message = err instanceof Error ? err.message : String(err);
2798
+ return { ok: false, status: 0, error: message, raw: "" };
2799
+ }
2800
+ if (!response.ok) {
2801
+ return {
2802
+ ok: false,
2803
+ status: response.status,
2804
+ error: `HTTP ${response.status}`,
2805
+ raw: rawText
2806
+ };
2807
+ }
2808
+ let outer;
2809
+ try {
2810
+ outer = JSON.parse(rawText);
2811
+ } catch (err) {
2812
+ const message = err instanceof Error ? err.message : String(err);
2813
+ return {
2814
+ ok: false,
2815
+ status: 200,
2816
+ error: `JSON parse error: ${message}`,
2817
+ raw: rawText
2818
+ };
2819
+ }
2820
+ if (outer.status === "ERROR") {
2821
+ return {
2822
+ ok: false,
2823
+ status: 400,
2824
+ error: outer.message,
2825
+ raw: rawText
2826
+ };
2827
+ }
2828
+ let dataStr;
2829
+ try {
2830
+ dataStr = JSON.parse(outer.data_str);
2831
+ } catch (err) {
2832
+ const message = err instanceof Error ? err.message : String(err);
2833
+ return {
2834
+ ok: false,
2835
+ status: 200,
2836
+ error: `data_str parse error: ${message}`,
2837
+ raw: rawText
2838
+ };
2839
+ }
2840
+ let rows;
2841
+ try {
2842
+ rows = JSON.parse(dataStr.json_encoded_response);
2843
+ } catch (err) {
2844
+ const message = err instanceof Error ? err.message : String(err);
2845
+ return {
2846
+ ok: false,
2847
+ status: 200,
2848
+ error: `JSON parse error: ${message}`,
2849
+ raw: rawText
2850
+ };
2851
+ }
2852
+ const data = {
2853
+ rows,
2854
+ total_records: dataStr.total_number_of_records
2855
+ };
2856
+ return { ok: true, data };
2857
+ }
2858
+
2859
+ // src/tools/mutation/admin-rebalance.ts
2860
+ var import_zod13 = require("zod");
2861
+ var AdminRebalanceSchema = import_zod13.z.object({
2862
+ rebalance_sharded_data: import_zod13.z.boolean().optional(),
2863
+ rebalance_unsharded_data: import_zod13.z.boolean().optional(),
2864
+ table_includes: import_zod13.z.string().optional(),
2865
+ table_excludes: import_zod13.z.string().optional(),
2866
+ aggressiveness: import_zod13.z.number().int().min(1).max(5).optional(),
2867
+ compact_after_rebalance: import_zod13.z.boolean().optional(),
2868
+ compact_only: import_zod13.z.boolean().optional()
2869
+ });
2870
+ async function readShardState(session2) {
2871
+ try {
2872
+ const response = await session2.makeRequest("/show/system/status", {});
2873
+ if (!response.ok) return {};
2874
+ const raw = await response.text();
2875
+ let parsed;
2876
+ try {
2877
+ parsed = JSON.parse(raw);
2878
+ } catch {
2879
+ return {};
2880
+ }
2881
+ const inner = parseDataStr(parsed.data_str, raw);
2882
+ if (!inner.ok || !inner.data) return {};
2883
+ const data = inner.data;
2884
+ return {
2885
+ shard_map: data.shard_map ?? {},
2886
+ db_status: data.db_status ?? "unknown"
2887
+ };
2888
+ } catch {
2889
+ return {};
2890
+ }
2891
+ }
2892
+ async function adminRebalance(session2, input5) {
2893
+ const beforeState = await readShardState(session2);
2894
+ const options = {};
2895
+ if (input5.rebalance_sharded_data !== void 0) {
2896
+ options.rebalance_sharded_data = String(input5.rebalance_sharded_data);
2897
+ }
2898
+ if (input5.rebalance_unsharded_data !== void 0) {
2899
+ options.rebalance_unsharded_data = String(input5.rebalance_unsharded_data);
2900
+ }
2901
+ if (input5.table_includes !== void 0) {
2902
+ options.table_includes = input5.table_includes;
2903
+ }
2904
+ if (input5.table_excludes !== void 0) {
2905
+ options.table_excludes = input5.table_excludes;
2906
+ }
2907
+ if (input5.aggressiveness !== void 0) {
2908
+ options.aggressiveness = String(input5.aggressiveness);
2909
+ }
2910
+ if (input5.compact_after_rebalance !== void 0) {
2911
+ options.compact_after_rebalance = String(input5.compact_after_rebalance);
2912
+ }
2913
+ if (input5.compact_only !== void 0) {
2914
+ options.compact_only = String(input5.compact_only);
2915
+ }
2916
+ try {
2917
+ const response = await session2.makeRequest("/admin/rebalance", { options });
2918
+ if (!response.ok) {
2919
+ const raw2 = await response.text();
2920
+ return {
2921
+ ok: false,
2922
+ status: response.status,
2923
+ error: `HTTP ${response.status}`,
2924
+ raw: raw2
2925
+ };
2926
+ }
2927
+ const raw = await response.text();
2928
+ let parsed;
2929
+ try {
2930
+ parsed = JSON.parse(raw);
2931
+ } catch (parseError) {
2932
+ const message = parseError instanceof Error ? parseError.message : String(parseError);
2933
+ return {
2934
+ ok: false,
2935
+ status: 200,
2936
+ error: `JSON parse error: ${message}`,
2937
+ raw
2938
+ };
2939
+ }
2940
+ const inner = parseDataStr(parsed.data_str, raw);
2941
+ if (!inner.ok) return inner;
2942
+ const info = inner.data?.info ?? {};
2943
+ const afterState = await readShardState(session2);
2944
+ const verification = Object.keys(afterState).length > 0 ? "confirmed" : "unavailable";
2945
+ const data = {
2946
+ info,
2947
+ before_state: beforeState,
2948
+ after_state: afterState,
2949
+ verification
2950
+ };
2951
+ return { ok: true, data };
2952
+ } catch (error) {
2953
+ const message = error instanceof Error ? error.message : String(error);
2954
+ return {
2955
+ ok: false,
2956
+ status: 0,
2957
+ error: message,
2958
+ raw: ""
2959
+ };
2960
+ }
2961
+ }
2962
+
2963
+ // src/tools/mutation/alter-configuration.ts
2964
+ var import_zod14 = require("zod");
2965
+ var AlterConfigurationSchema = import_zod14.z.object({
2966
+ /**
2967
+ * The full replacement content for the gpudb.conf configuration file.
2968
+ * Must be non-empty.
2969
+ */
2970
+ config_string: import_zod14.z.string().min(1, { message: "config_string must not be empty" })
2971
+ });
2972
+ var PREVIEW_LINES = 20;
2973
+ var EMPTY_SUMMARY2 = { line_count: 0, preview: "" };
2974
+ function summarizeConfig(configString) {
2975
+ const lines = configString.split("\n");
2976
+ const preview = lines.slice(0, PREVIEW_LINES).join("\n");
2977
+ return { line_count: lines.length, preview };
2978
+ }
2979
+ async function readCurrentConfig(session2) {
2980
+ try {
2981
+ const result = await showConfiguration(session2, {});
2982
+ return result.ok ? result.data.config_string : void 0;
2983
+ } catch {
2984
+ return void 0;
2985
+ }
2986
+ }
2987
+ async function alterConfiguration(session2, input5) {
2988
+ if (!session2.makeRequestToPort) {
2989
+ return {
2990
+ ok: false,
2991
+ status: 0,
2992
+ error: "makeRequestToPort not available on this session",
2993
+ raw: ""
2994
+ };
2995
+ }
2996
+ const beforeConfig = await readCurrentConfig(session2);
2997
+ const beforeSummary = beforeConfig !== void 0 ? summarizeConfig(beforeConfig) : EMPTY_SUMMARY2;
2998
+ const hmPort = await discoverHmPort(session2);
2999
+ let mutationResponse;
3000
+ let rawText;
3001
+ try {
3002
+ mutationResponse = await session2.makeRequestToPort(hmPort, "/admin/alter/configuration", {
3003
+ config_string: input5.config_string
3004
+ });
3005
+ rawText = await mutationResponse.text();
3006
+ } catch (error) {
3007
+ const message = error instanceof Error ? error.message : String(error);
3008
+ return { ok: false, status: 0, error: message, raw: "" };
3009
+ }
3010
+ if (!mutationResponse.ok) {
3011
+ return {
3012
+ ok: false,
3013
+ status: mutationResponse.status,
3014
+ error: `HTTP ${mutationResponse.status}`,
3015
+ raw: rawText
3016
+ };
3017
+ }
3018
+ let parsedMutation;
3019
+ try {
3020
+ parsedMutation = JSON.parse(rawText);
3021
+ } catch (parseError) {
3022
+ const message = parseError instanceof Error ? parseError.message : String(parseError);
3023
+ return {
3024
+ ok: false,
3025
+ status: 200,
3026
+ error: `JSON parse error: ${message}`,
3027
+ raw: rawText
3028
+ };
3029
+ }
3030
+ const innerMutation = parseDataStr(parsedMutation.data_str, rawText);
3031
+ if (!innerMutation.ok) return innerMutation;
3032
+ const responseInfo = innerMutation.data?.info ?? {};
3033
+ const afterConfig = await readCurrentConfig(session2);
3034
+ let afterSummary;
3035
+ let verification;
3036
+ if (afterConfig === void 0) {
3037
+ afterSummary = EMPTY_SUMMARY2;
3038
+ verification = "unavailable";
3039
+ } else {
3040
+ afterSummary = summarizeConfig(afterConfig);
3041
+ if (beforeConfig === void 0) {
3042
+ verification = "unavailable";
3043
+ } else {
3044
+ verification = afterConfig !== beforeConfig ? "confirmed" : "failed";
3045
+ }
3046
+ }
3047
+ const data = {
3048
+ before_summary: beforeSummary,
3049
+ after_summary: afterSummary,
3050
+ verification,
3051
+ info: responseInfo
3052
+ };
3053
+ return { ok: true, data };
3054
+ }
3055
+
3056
+ // src/tools/mutation/alter-table-columns.ts
3057
+ var import_zod15 = require("zod");
3058
+ var import_prompts6 = require("@inquirer/prompts");
3059
+ var import_picocolors8 = __toESM(require("picocolors"));
3060
+ var import_claude_agent_sdk2 = require("@anthropic-ai/claude-agent-sdk");
3061
+
3062
+ // src/approval/checklist.ts
3063
+ var import_prompts5 = require("@inquirer/prompts");
3064
+ var import_picocolors7 = __toESM(require("picocolors"));
3065
+ var DIVIDER = import_picocolors7.default.dim("\u2500".repeat(60));
3066
+ function renderChecklist(header, summary, items) {
3067
+ const lines = [
3068
+ "",
3069
+ DIVIDER,
3070
+ ` ${import_picocolors7.default.bold(import_picocolors7.default.yellow(header))}`,
3071
+ "",
3072
+ ` ${import_picocolors7.default.dim("Summary:")} ${summary}`,
3073
+ "",
3074
+ ` ${import_picocolors7.default.bold(`${items.length} proposed column change(s):`)}`,
3075
+ ""
3076
+ ];
3077
+ for (let i = 0; i < items.length; i++) {
3078
+ const item = items[i];
3079
+ lines.push(` ${import_picocolors7.default.bold(`${i + 1}.`)} ${item.label}`, ` ${import_picocolors7.default.dim(item.description)}`);
3080
+ }
3081
+ lines.push("", DIVIDER, "");
3082
+ return lines.join("\n");
3083
+ }
3084
+ async function showChecklist(header, summary, items) {
3085
+ const panel = renderChecklist(header, summary, items);
3086
+ process.stderr.write(panel);
3087
+ try {
3088
+ const selected = await (0, import_prompts5.checkbox)({
3089
+ message: "Select columns to alter (space to toggle, enter to confirm):",
3090
+ choices: items.map((item, i) => ({
3091
+ value: i,
3092
+ name: item.label,
3093
+ description: item.description,
3094
+ checked: true
3095
+ })),
3096
+ loop: false
3097
+ });
3098
+ if (selected.length === 0) {
3099
+ return { action: "cancelled" };
3100
+ }
3101
+ return { action: "confirmed", selectedIndices: selected };
3102
+ } catch {
3103
+ return { action: "cancelled" };
3104
+ }
3105
+ }
3106
+
3107
+ // src/tools/mutation/alter-table-columns.ts
3108
+ var ColumnChangeSchema = import_zod15.z.object({
3109
+ column_name: import_zod15.z.string().min(1),
3110
+ new_definition: import_zod15.z.string().min(1),
3111
+ description: import_zod15.z.string().min(1)
3112
+ });
3113
+ var AlterTableColumnsSchema = import_zod15.z.object({
3114
+ table_name: import_zod15.z.string().min(1),
3115
+ rationale: import_zod15.z.string().min(1),
3116
+ columns: import_zod15.z.array(ColumnChangeSchema).min(2).max(50)
3117
+ });
3118
+ function buildAlterTableSql(tableName, columns) {
3119
+ const clauses = columns.map((c) => ` ALTER COLUMN ${c.column_name} ${c.new_definition}`);
3120
+ return `ALTER TABLE ${tableName}
3121
+ ${clauses.join(",\n")}`;
3122
+ }
3123
+ var SQL_DIVIDER = import_picocolors8.default.dim("\u2500".repeat(60));
3124
+ async function confirmSqlExecution(sql) {
3125
+ const panel = [
3126
+ "",
3127
+ SQL_DIVIDER,
3128
+ ` ${import_picocolors8.default.bold(import_picocolors8.default.yellow("Generated SQL:"))}`,
3129
+ "",
3130
+ sql.split("\n").map((line) => ` ${line}`).join("\n"),
3131
+ "",
3132
+ SQL_DIVIDER,
3133
+ ""
3134
+ ].join("\n");
3135
+ process.stderr.write(panel);
3136
+ try {
3137
+ const response = await (0, import_prompts6.input)({ message: "Execute? (y/n):" });
3138
+ return response.trim().toLowerCase() === "y";
3139
+ } catch {
3140
+ return false;
3141
+ }
3142
+ }
3143
+ function buildChecklistItems(columns) {
3144
+ return columns.map((col) => ({
3145
+ label: `${col.column_name}: ${col.new_definition}`,
3146
+ description: col.description
3147
+ }));
3148
+ }
3149
+ async function alterTableColumns(session2, toolInput) {
3150
+ const { table_name, rationale, columns } = toolInput;
3151
+ const items = buildChecklistItems(columns);
3152
+ const selection = await showChecklist(
3153
+ "ALTER TABLE Column Changes",
3154
+ `Table: ${table_name}
3155
+ ${rationale}`,
3156
+ items
3157
+ );
3158
+ if (selection.action === "cancelled") {
3159
+ return {
3160
+ ok: true,
3161
+ data: {
3162
+ status: "cancelled",
3163
+ total_count: columns.length
3164
+ }
3165
+ };
3166
+ }
3167
+ const selectedColumns = selection.selectedIndices.map((i) => {
3168
+ const col = columns[i];
3169
+ return {
3170
+ column_name: col.column_name,
3171
+ new_definition: col.new_definition
3172
+ };
3173
+ });
3174
+ const sql = buildAlterTableSql(table_name, selectedColumns);
3175
+ const confirmed = await confirmSqlExecution(sql);
3176
+ if (!confirmed) {
3177
+ return {
3178
+ ok: true,
3179
+ data: {
3180
+ status: "declined",
3181
+ sql,
3182
+ selected_count: selectedColumns.length,
3183
+ total_count: columns.length
3184
+ }
3185
+ };
3186
+ }
3187
+ const result = await executeMutationSql(session2, sql);
3188
+ if (!result.ok) {
3189
+ return result;
3190
+ }
3191
+ return {
3192
+ ok: true,
3193
+ data: {
3194
+ status: "executed",
3195
+ sql,
3196
+ selected_count: selectedColumns.length,
3197
+ total_count: columns.length,
3198
+ execution_result: result.data
3199
+ }
3200
+ };
3201
+ }
3202
+ function makeAlterTableColumnsTool(session2, deps) {
3203
+ return (0, import_claude_agent_sdk2.tool)(
3204
+ "kinetica_alter_table_columns",
3205
+ "Batch multiple column type/property changes on a SINGLE table into one efficient ALTER TABLE statement. Use when recommending 2+ column changes on the same table (e.g., adding DICT encoding to multiple columns, adding TEXT_SEARCH, changing column types). Each column change requires: column_name, new_definition (full type definition with properties and nullability), and description (human-readable reason). The operator selects which columns to alter via interactive checklist, then confirms the generated SQL. For a single column change, use kinetica_execute_mutation_sql directly. Kinetica ALTER TABLE syntax requires repeating the FULL column definition \u2014 properties go INSIDE parentheses: VARCHAR(50, DICT), not VARCHAR(50) DICT.",
3206
+ AlterTableColumnsSchema.shape,
3207
+ async (args) => {
3208
+ const parsed = AlterTableColumnsSchema.parse(args);
3209
+ const result = await alterTableColumns(session2, parsed);
3210
+ deps.logMutationAudit("kinetica_alter_table_columns", result, {
3211
+ table_name: parsed.table_name,
3212
+ columns_proposed: parsed.columns.length
3213
+ });
3214
+ return {
3215
+ content: [{ type: "text", text: deps.applyOutputPipeline(result) }]
3216
+ };
3217
+ },
3218
+ { annotations: { destructive: true, readOnly: false } }
3219
+ );
3220
+ }
3221
+
3222
+ // src/tools/audit-redact.ts
3223
+ var import_node_crypto = require("crypto");
3224
+ var FINGERPRINT_THRESHOLD = 300;
3225
+ var FINGERPRINT_HEX_LEN = 12;
3226
+ var ALWAYS_FINGERPRINT_KEYS = /* @__PURE__ */ new Set(["config_string"]);
3227
+ var CREDENTIAL_PATTERNS = [
3228
+ // password = '...' or password='...'
3229
+ {
3230
+ regex: /(password\s*=\s*)['"][^'"]*['"]/gi,
3231
+ replacement: "$1'[REDACTED]'"
3232
+ },
3233
+ // IDENTIFIED BY '...' (Kinetica CREATE/ALTER USER)
3234
+ {
3235
+ regex: /(identified\s+by\s+)['"][^'"]*['"]/gi,
3236
+ replacement: "$1'[REDACTED]'"
3237
+ },
3238
+ // apiKey=... / api_key: ... / access_token=... / secret = '...' / "access_token": "..."
3239
+ {
3240
+ regex: /(api[_-]?key|access[_-]?token|secret)['"]?(\s*[:=]\s*)['"]?([^\s'"`,;)]+)['"]?/gi,
3241
+ replacement: "$1$2'[REDACTED]'"
3242
+ }
3243
+ ];
3244
+ function fingerprint(value) {
3245
+ const sha = (0, import_node_crypto.createHash)("sha256").update(value).digest("hex").slice(0, FINGERPRINT_HEX_LEN);
3246
+ return `<${value.length} bytes, sha256:${sha}\u2026>`;
3247
+ }
3248
+ function scrubCredentialPatterns(value) {
3249
+ return CREDENTIAL_PATTERNS.reduce(
3250
+ (text, { regex, replacement }) => text.replace(regex, replacement),
3251
+ value
3252
+ );
3253
+ }
3254
+ function redactValue(value) {
3255
+ if (typeof value === "string") {
3256
+ const scrubbed = scrubCredentialPatterns(value);
3257
+ return scrubbed.length > FINGERPRINT_THRESHOLD ? fingerprint(scrubbed) : scrubbed;
3258
+ }
3259
+ if (Array.isArray(value)) {
3260
+ return value.map(redactValue);
3261
+ }
3262
+ if (value && typeof value === "object") {
3263
+ return Object.fromEntries(
3264
+ Object.entries(value).map(([k, v]) => [k, redactNamedField(k, v)])
3265
+ );
3266
+ }
3267
+ return value;
3268
+ }
3269
+ function redactNamedField(key, value) {
3270
+ if (ALWAYS_FINGERPRINT_KEYS.has(key) && typeof value === "string") {
3271
+ return fingerprint(value);
3272
+ }
3273
+ return redactValue(value);
3274
+ }
3275
+ function redactAuditInput(input5) {
3276
+ return Object.fromEntries(Object.entries(input5).map(([k, v]) => [k, redactNamedField(k, v)]));
3277
+ }
3278
+
3279
+ // src/tools/index.ts
3280
+ var MUTATION_TOOL_NAMES = [
3281
+ "kinetica_alter_system_properties",
3282
+ "kinetica_execute_mutation_sql",
3283
+ "kinetica_admin_rebalance",
3284
+ "kinetica_alter_configuration"
3285
+ ];
3286
+ var ALTER_TABLE_COLUMNS_TOOL_NAME = "kinetica_alter_table_columns";
3287
+ var DIAGNOSTIC_TOOL_NAMES = [
3288
+ "kinetica_health_check",
3289
+ "kinetica_get_metrics",
3290
+ "kinetica_cluster_status",
3291
+ "kinetica_node_details",
3292
+ "kinetica_get_logs",
3293
+ "kinetica_show_configuration",
3294
+ "kinetica_get_system_properties",
3295
+ "kinetica_execute_sql",
3296
+ "kinetica_explain_query",
3297
+ "kinetica_system_timing",
3298
+ "kinetica_resource_groups",
3299
+ "kinetica_verify_db",
3300
+ "kinetica_show_security",
3301
+ "kinetica_show_table",
3302
+ "kinetica_resource_objects",
3303
+ "kinetica_host_manager_status"
3304
+ ];
3305
+ function createDiagnosticRegistry() {
3306
+ return DIAGNOSTIC_TOOL_NAMES.reduce(
3307
+ (registry, name) => registry.registerReadOnlyTool(name),
3308
+ createRegistry()
3309
+ );
3310
+ }
3311
+ function applyOutputPipeline(result) {
3312
+ const payload = result.ok ? result.data : result;
3313
+ return truncateOutput(formatOutput(payload));
3314
+ }
3315
+ function logMutationAudit(toolName, result, input5) {
3316
+ const statusLabel = result.ok ? import_picocolors9.default.bold(import_picocolors9.default.green("EXECUTED")) : import_picocolors9.default.bold(import_picocolors9.default.red("FAILED"));
3317
+ const redacted = redactAuditInput(input5);
3318
+ const inputSummary = Object.entries(redacted).map(([k, v]) => `${k}=${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
3319
+ const displayName = formatToolName(toolName);
3320
+ process.stderr.write(
3321
+ ` ${import_picocolors9.default.dim("MUTATION")} ${statusLabel} ${displayName}
3322
+ ${import_picocolors9.default.dim(inputSummary)}
3323
+
3324
+ `
3325
+ );
3326
+ }
3327
+ function makeAlterSystemPropertiesTool(session2) {
3328
+ return (0, import_claude_agent_sdk3.tool)(
3329
+ "kinetica_alter_system_properties",
3330
+ "Apply runtime configuration changes to Kinetica via /alter/system/properties. Accepts a map of property name to new value. Captures current values before applying and verifies changes after. Only the 43 documented properties are accepted \u2014 requests with unsupported property names are rejected before any API call. Blocked for safety: ai_api_key, external_files_directory. Common properties (7.2.x): subtask_concurrency_limit, request_timeout, max_get_records_size, concurrent_kernel_execution, max_concurrent_kernels, tcs_per_tom, tps_per_tom, chunk_size, enable_audit, egress_parquet_compression, background_worker_threads. All property names require 'conf.' prefix in /show/system/properties but NOT in /alter/system/properties.",
3331
+ AlterSystemPropertiesSchema.shape,
3332
+ async (args) => {
3333
+ const parsed = AlterSystemPropertiesSchema.parse(args);
3334
+ const result = await alterSystemProperties(session2, parsed);
3335
+ logMutationAudit("kinetica_alter_system_properties", result, parsed);
3336
+ return { content: [{ type: "text", text: applyOutputPipeline(result) }] };
3337
+ },
3338
+ { annotations: { destructive: true, readOnly: false } }
3339
+ );
3340
+ }
3341
+ function makeExecuteMutationSqlTool(session2) {
3342
+ return (0, import_claude_agent_sdk3.tool)(
3343
+ "kinetica_execute_mutation_sql",
3344
+ "Execute a SQL mutation statement on Kinetica (CREATE INDEX, ALTER TABLE, ALTER SYSTEM SET, REFRESH MATERIALIZED VIEW, etc.). ANALYZE TABLE is NOT supported by Kinetica \u2014 do not call it. DROP, TRUNCATE, DELETE, and UPDATE are always rejected \u2014 even when wrapped in a CTE (WITH ... DELETE/UPDATE). Requires user approval before execution.",
3345
+ ExecuteMutationSqlSchema.shape,
3346
+ async (args) => {
3347
+ const parsed = ExecuteMutationSqlSchema.parse(args);
3348
+ const result = await executeMutationSql(session2, parsed.statement, parsed.limit);
3349
+ logMutationAudit("kinetica_execute_mutation_sql", result, parsed);
3350
+ return { content: [{ type: "text", text: applyOutputPipeline(result) }] };
3351
+ },
3352
+ { annotations: { destructive: true, readOnly: false } }
3353
+ );
3354
+ }
3355
+ function makeAdminRebalanceTool(session2) {
3356
+ return (0, import_claude_agent_sdk3.tool)(
3357
+ "kinetica_admin_rebalance",
3358
+ "Trigger shard data rebalancing across Kinetica cluster ranks via /admin/rebalance. Options: rebalance_sharded_data, rebalance_unsharded_data, table_includes, table_excludes, aggressiveness (1-5, capped for safety), compact_after_rebalance, compact_only. Captures before/after shard distribution for verification. WARNING: rebalance causes delayed query responses while running \u2014 use low aggressiveness (1-3) during production hours. NOTE: On single-worker-rank clusters, /admin/rebalance returns 'Database must be offline' because there is only one data rank \u2014 rebalance is only meaningful with 2+ worker ranks.",
3359
+ AdminRebalanceSchema.shape,
3360
+ async (args) => {
3361
+ const parsed = AdminRebalanceSchema.parse(args);
3362
+ const result = await adminRebalance(session2, parsed);
3363
+ logMutationAudit("kinetica_admin_rebalance", result, parsed);
3364
+ return { content: [{ type: "text", text: applyOutputPipeline(result) }] };
3365
+ },
3366
+ { annotations: { destructive: true, readOnly: false } }
3367
+ );
3368
+ }
3369
+ function makeAlterConfigurationTool(session2) {
3370
+ return (0, import_claude_agent_sdk3.tool)(
3371
+ "kinetica_alter_configuration",
3372
+ "Replace the full gpudb.conf configuration on the Kinetica host manager via /admin/alter/configuration (port 9300). Requires the complete config_string content \u2014 the entire file is replaced. Captures before/after config summaries for verification. WARNING: This replaces the ENTIRE configuration file. Always read the current config via kinetica_show_configuration first, make targeted changes to specific lines, and submit the full modified content. Never compose a config from scratch. Requires host manager connectivity and user approval.",
3373
+ AlterConfigurationSchema.shape,
3374
+ async (args) => {
3375
+ const parsed = AlterConfigurationSchema.parse(args);
3376
+ const result = await alterConfiguration(session2, parsed);
3377
+ logMutationAudit("kinetica_alter_configuration", result, parsed);
3378
+ return { content: [{ type: "text", text: applyOutputPipeline(result) }] };
3379
+ },
3380
+ { annotations: { destructive: true, readOnly: false } }
3381
+ );
3382
+ }
3383
+ function makeHealthCheckTool(session2) {
3384
+ return (0, import_claude_agent_sdk3.tool)(
3385
+ "kinetica_health_check",
3386
+ "Query Kinetica system health status via /show/system/status. Returns 11 components as rows (component, status): system (cluster status, leader, offline), ranks (per-rank status/mode/accepting_jobs/read_only), hosts (hostname, memory, GPU IDs, sub-service statuses), http_server (connections current/refused, thread capacity), ha_cluster_info/ha_status, graph, text, migrations, triggers, symbols. NOTE: Each status value is a JSON-encoded string \u2014 parse mentally to extract nested fields. Healthy baseline: system.status='running', all ranks rank_status='running' + rank_mode='run', hosts.status='running', http_server.connections.refused=0.",
3387
+ {},
3388
+ async (_args) => {
3389
+ const result = await healthCheck(session2);
3390
+ return { content: [{ type: "text", text: applyOutputPipeline(result) }] };
3391
+ },
3392
+ { annotations: { readOnly: true } }
3393
+ );
3394
+ }
3395
+ function makeGetMetricsTool(session2) {
3396
+ return (0, import_claude_agent_sdk3.tool)(
3397
+ "kinetica_get_metrics",
3398
+ "Retrieve per-rank storage tier metrics from /show/resource/statistics. Returns rows with: rank, ram_used, ram_limit, ram_percent (computed as 'X.Y%' e.g. '9.6%'), persist_used, disk_used, vram_used (all string values). Rank 0 is the head/coordinator node with minimal RAM (~794MB limit) and empty persist/disk/vram fields. Worker ranks (1+) hold the actual data with ~5.6GB RAM limit. Empty string means tier not configured; '0' means configured but unused. Healthy baseline: ram_percent under 80%. Optional node_id to annotate which node was requested.",
3399
+ { node_id: import_zod16.z.string().optional() },
3400
+ async (args) => {
3401
+ const result = await getMetrics(session2, args.node_id);
3402
+ return { content: [{ type: "text", text: applyOutputPipeline(result) }] };
3403
+ },
3404
+ { annotations: { readOnly: true } }
3405
+ );
3406
+ }
3407
+ function makeClusterStatusTool(session2) {
3408
+ return (0, import_claude_agent_sdk3.tool)(
3409
+ "kinetica_cluster_status",
3410
+ "Get full cluster overview via 4 sub-calls: (1) /admin/show/cluster/operations \u2014 in_progress flag, percent_complete, rebalance/add/remove status; (2) /admin/show/shards \u2014 summarized as shard distribution per rank (total_shards, rank_count, per-rank shard_count/percent, balanced flag, shard_array_version); (3) /admin/show/alerts on host manager port \u2014 recent alerts (gracefully degrades if unavailable); (4) /admin/show/jobs \u2014 active async jobs as {job_id, status, endpoint} objects. Healthy baseline: operations.in_progress=false, shards.balanced=true, empty alerts/jobs arrays.",
3411
+ {},
3412
+ async (_args) => {
3413
+ const result = await clusterStatus(session2);
3414
+ return { content: [{ type: "text", text: applyOutputPipeline(result) }] };
3415
+ },
3416
+ { annotations: { readOnly: true } }
3417
+ );
3418
+ }
3419
+ function makeNodeDetailsTool(session2) {
3420
+ return (0, import_claude_agent_sdk3.tool)(
3421
+ "kinetica_node_details",
3422
+ "Get per-rank resource statistics from /show/resource/statistics (same endpoint as kinetica_get_metrics). Without node_id: returns summary rows for all ranks. With node_id: returns detailed tier + resource-group breakdown for that rank. Per-tier fields: limit, used, free, percent_used, num_evictable_objs, num_unevictable_objs, plus stats: evictions, pins, unpins, watermark_cycles, allocs, reallocs, deallocs (RAM/VRAM tiers) or reads, writes, deletes (PERSIST/DISK tiers). Per-resource-group fields: name, thread_running_count, data (bytes). Rank 0 is the head node \u2014 only RAM tier (no PERSIST/DISK/VRAM), no resource groups.",
3423
+ { node_id: import_zod16.z.string().optional() },
3424
+ async (args) => {
3425
+ const result = await nodeDetails(session2, args.node_id);
3426
+ return { content: [{ type: "text", text: applyOutputPipeline(result) }] };
3427
+ },
3428
+ { annotations: { readOnly: true } }
3429
+ );
3430
+ }
3431
+ function makeGetLogsTool(session2) {
3432
+ return (0, import_claude_agent_sdk3.tool)(
3433
+ "kinetica_get_logs",
3434
+ "Retrieve application logs via /admin/show/logs. Sources: kinetica, rank, syslog, gadmin, reveal, workbench. Severity: DEBUG|INFO|WARN|ERROR|FATAL. Time: duration (1h, 30m) or start_time+end_time. WARNING: This endpoint is NOT available on Kinetica 7.2.x \u2014 it always returns a stub response. Use kinetica_execute_sql to query ki_catalog.ki_query_history (for query errors) or ki_catalog.ki_query_span_metrics_all (for operation-level events) instead.",
3435
+ GetLogsSchema.shape,
3436
+ async (args) => {
3437
+ const parsed = GetLogsSchema.parse(args);
3438
+ const result = await getLogs(session2, parsed);
3439
+ return { content: [{ type: "text", text: applyOutputPipeline(result) }] };
3440
+ },
3441
+ { annotations: { readOnly: true } }
3442
+ );
3443
+ }
3444
+ function makeShowConfigurationTool(session2) {
3445
+ return (0, import_claude_agent_sdk3.tool)(
3446
+ "kinetica_show_configuration",
3447
+ "Retrieve the full gpudb.conf configuration file from the Kinetica host manager via /admin/show/configuration (port 9300). Returns the raw config_string in INI format with all sections and comments. Use this to inspect the complete server configuration for drift detection, misconfiguration diagnosis, or before proposing config changes via kinetica_alter_configuration. Requires host manager connectivity.",
3448
+ ShowConfigurationSchema.shape,
3449
+ async (args) => {
3450
+ const parsed = ShowConfigurationSchema.parse(args);
3451
+ const result = await showConfiguration(session2, parsed);
3452
+ return { content: [{ type: "text", text: applyOutputPipeline(result) }] };
3453
+ },
3454
+ { annotations: { readOnly: true } }
3455
+ );
3456
+ }
3457
+ function makeGetSystemPropertiesTool(session2) {
3458
+ return (0, import_claude_agent_sdk3.tool)(
3459
+ "kinetica_get_system_properties",
3460
+ "Read Kinetica system configuration properties from /show/system/properties. Returns 260+ property rows as {property, value} pairs (all values are strings). Filter by category prefix (e.g., 'conf.tier', 'conf.sql', 'version') or key_pattern substring. Key property groups: conf.tier.* (RAM limits, watermarks, tier strategy), conf.sql.* (parallel_execution, planner timeout), conf.enable_* (authorization, HA, ML, text_search), version.* (gpudb_core_version, compute_engine). Omit both filters to get the full property_map.",
3461
+ GetSystemPropertiesSchema.shape,
3462
+ async (args) => {
3463
+ const parsed = GetSystemPropertiesSchema.parse(args);
3464
+ const result = await getSystemProperties(session2, parsed);
3465
+ return { content: [{ type: "text", text: applyOutputPipeline(result) }] };
3466
+ },
3467
+ { annotations: { readOnly: true } }
3468
+ );
3469
+ }
3470
+ function makeExecuteSqlTool(session2, catalogSchemas) {
3471
+ return (0, import_claude_agent_sdk3.tool)(
3472
+ "kinetica_execute_sql",
3473
+ "Execute a read-only SQL query (SELECT, WITH, or EXPLAIN) against Kinetica. Key system tables: ki_catalog.ki_query_history (slow queries \u2014 columns: job_id, query_id, user_name, endpoint, execution_status, error_message, query_text, start_time, stop_time, sql_step_count, refresh_id, resource_group, source_ip), ki_catalog.ki_query_active_all (running queries \u2014 columns: job_id, query_id, user_name, resource_group, source_ip, endpoint, execution_status, error_message, start_time, query_text, sql_step_count, refresh_id, is_mh, is_perpetual, is_cancellable, is_using_timeout, source_rank), ki_catalog.ki_tiered_objects (per-object tier placement \u2014 size, id (string like @schema@oid[col][0] \u2014 NOT a numeric OID, do not join with ki_objects.oid), priority, tier, evictable, locked, pin_count, ram_evictions, persist_evictions, owner_resource_group, source_rank, outer_object; for per-table tier data prefer kinetica_resource_objects with table_names filter), ki_catalog.ki_obj_stat (table sizes \u2014 oid, schema_name, object_name, row_count, bytes_per_row, total_bytes), ki_catalog.ki_columns (column metadata), ki_catalog.ki_objects (object registry with obj_kind R=table/V=view). WARNING: ki_catalog.ki_tables and ki_catalog.ki_version do NOT exist in Kinetica 7.2.x \u2014 use ki_objects and /show/system/status instead.",
3474
+ ExecuteSqlSchema.shape,
3475
+ async (args) => {
3476
+ const result = await executeSql(session2, args.statement, args.limit);
3477
+ if (!result.ok) {
3478
+ const enrichedError = enrichSqlError(result.error, args.statement, catalogSchemas);
3479
+ const enrichedResult = enrichedError !== result.error ? { ok: false, status: result.status, error: enrichedError, raw: result.raw } : result;
3480
+ return { content: [{ type: "text", text: applyOutputPipeline(enrichedResult) }] };
3481
+ }
3482
+ return { content: [{ type: "text", text: applyOutputPipeline(result) }] };
3483
+ },
3484
+ { annotations: { readOnly: true } }
3485
+ );
3486
+ }
3487
+ function makeExplainQueryTool(session2) {
3488
+ return (0, import_claude_agent_sdk3.tool)(
3489
+ "kinetica_explain_query",
3490
+ "Get the execution plan for a SQL statement. Pass the SELECT statement without the EXPLAIN keyword \u2014 it will be added automatically. Returns rows with columns: ID (step number), ENDPOINT (internal REST endpoint used, e.g., /get/records/bycolumn), INPUT_TABLES, OUTPUT_TABLE, DEPENDENCIES (step IDs this depends on; -1 means none). Use to understand which internal operations a query triggers and verify index usage.",
3491
+ ExplainQuerySchema.shape,
3492
+ async (args) => {
3493
+ const result = await explainQuery(session2, args.statement, args.limit);
3494
+ return { content: [{ type: "text", text: applyOutputPipeline(result) }] };
3495
+ },
3496
+ { annotations: { readOnly: true } }
3497
+ );
3498
+ }
3499
+ function makeSystemTimingTool(session2) {
3500
+ return (0, import_claude_agent_sdk3.tool)(
3501
+ "kinetica_system_timing",
3502
+ "Show endpoint response timing statistics from /show/system/timing. Returns the last ~100 API calls as {endpoint, time_in_ms, job_id} rows with sub-millisecond precision. job_id='0' means synchronous execution at head node; non-zero means async. Typical baselines: /show/system/properties <4ms, /show/security <1ms, /execute/sql 14-1300ms, /admin/verifydb 500-4500ms. Use to identify slow API endpoints or confirm whether a specific call was abnormally slow.",
3503
+ {},
3504
+ async (_args) => {
3505
+ const result = await systemTiming(session2);
3506
+ return { content: [{ type: "text", text: applyOutputPipeline(result) }] };
3507
+ },
3508
+ { annotations: { readOnly: true } }
3509
+ );
3510
+ }
3511
+ function makeResourceGroupsTool(session2) {
3512
+ return (0, import_claude_agent_sdk3.tool)(
3513
+ "kinetica_resource_groups",
3514
+ "List resource groups and their configuration from /show/resourcegroups. Returns {groups, rank_usage, info}. Groups include: name, RAM.max_memory, VRAM.GPU0.max_memory, max_cpu_concurrency, max_data, max_scheduling_priority (100=system, 50=default), max_tier_priority. Value '9223372036854775807' (Long.MAX_VALUE) means unlimited. Default groups are kinetica_system_resource_group (priority 100) and kinetica_default_resource_group (priority 50). rank_usage maps rank IDs to JSON-encoded per-group usage (thread_running_count, data bytes, RAM.used, VRAM.GPU0.used). Only worker ranks appear in rank_usage \u2014 rank 0 (head) has no entry. Set show_tier_usage=true for rank-level breakdown.",
3515
+ ResourceGroupsSchema.shape,
3516
+ async (args) => {
3517
+ const parsed = ResourceGroupsSchema.parse(args);
3518
+ const result = await getResourceGroups(session2, parsed);
3519
+ return { content: [{ type: "text", text: applyOutputPipeline(result) }] };
3520
+ },
3521
+ { annotations: { readOnly: true } }
3522
+ );
3523
+ }
3524
+ function makeVerifyDbTool(session2) {
3525
+ return (0, import_claude_agent_sdk3.tool)(
3526
+ "kinetica_verify_db",
3527
+ "Run a read-only database integrity verification via /admin/verifydb. Checks for null values, persistence issues, and rank0 consistency. Always runs in concurrent_safe mode. Returns {verified_ok: boolean, error_list: [], orphaned_tables_total_size: number}. Healthy: verified_ok=true, empty error_list, orphaned_tables_total_size of -1 (not checked) or 0. WARNING: This is the slowest diagnostic tool \u2014 typically takes 500ms-4500ms. Use sparingly and only when data integrity issues are suspected.",
3528
+ VerifyDbSchema.shape,
3529
+ async (args) => {
3530
+ const parsed = VerifyDbSchema.parse(args);
3531
+ const result = await verifyDb(session2, parsed);
3532
+ return { content: [{ type: "text", text: applyOutputPipeline(result) }] };
3533
+ },
3534
+ { annotations: { readOnly: true } }
3535
+ );
3536
+ }
3537
+ function makeShowSecurityTool(session2) {
3538
+ return (0, import_claude_agent_sdk3.tool)(
3539
+ "kinetica_show_security",
3540
+ "Show security configuration from /show/security. Returns {types, roles, permissions, resource_groups, info}. When enable_authorization=false (check permissions[''].enable_authorization), types/roles/resource_groups are empty objects \u2014 the tool provides minimal data. When authorization is enabled: types maps usernames to 'internal_user'/'external_user', roles maps role names to member arrays, permissions maps users to permission arrays, resource_groups maps users to group names. Use to audit access control or diagnose authorization failures.",
3541
+ ShowSecuritySchema.shape,
3542
+ async (args) => {
3543
+ const parsed = ShowSecuritySchema.parse(args);
3544
+ const result = await showSecurity(session2, parsed);
3545
+ return { content: [{ type: "text", text: applyOutputPipeline(result) }] };
3546
+ },
3547
+ { annotations: { readOnly: true } }
3548
+ );
3549
+ }
3550
+ function makeShowTableTool(session2) {
3551
+ return (0, import_claude_agent_sdk3.tool)(
3552
+ "kinetica_show_table",
3553
+ "Show table metadata from /show/table. When a specific table_name is provided: returns table metadata with Kinetica-native column types, per-column properties (DICT, TEXT_SEARCH, COMPRESS, etc.), and index definitions from ki_catalog.ki_indexes (index_type, index_columns) \u2014 this is the preferred method for full table inspection. When table_name is omitted or empty: returns schema-level (collection) entries with sizes, but the processed output may be empty \u2014 use kinetica_execute_sql with 'SELECT * FROM ki_catalog.ki_objects WHERE obj_kind = ''R'' ORDER BY schema_name' for reliable table listing instead.",
3554
+ ShowTableSchema.shape,
3555
+ async (args) => {
3556
+ const parsed = ShowTableSchema.parse(args);
3557
+ const result = await showTable(session2, parsed);
3558
+ return { content: [{ type: "text", text: applyOutputPipeline(result) }] };
3559
+ },
3560
+ { annotations: { readOnly: true } }
3561
+ );
3562
+ }
3563
+ function makeResourceObjectsTool(session2) {
3564
+ return (0, import_claude_agent_sdk3.tool)(
3565
+ "kinetica_resource_objects",
3566
+ `Show per-rank resource tier usage from /show/resource/objects. Returns {rank_objects: {rank_id: JSON_string_with_objects_array}, info}. Each object has: id (naming convention: @table@oid[column][chunk] for data, AttrIndex[...] for indexes, PKIndex_... for PK hashes), size (bytes), priority (1=system, 5=user, 9=temp), tier ('RAM' or 'PERSIST'), evictable (boolean), locked (boolean), pin_count, ram_evictions, persist_evictions, owner_resource_group. The rank_objects JSON is {"objects": [...]} \u2014 an array nested under an 'objects' key. Only worker ranks have data \u2014 rank 0 (head) has no resource objects. Healthy: zero evictions, zero pin_count at rest.`,
3567
+ ResourceObjectsSchema.shape,
3568
+ async (args) => {
3569
+ const parsed = ResourceObjectsSchema.parse(args);
3570
+ const result = await getResourceObjects(session2, parsed);
3571
+ return { content: [{ type: "text", text: applyOutputPipeline(result) }] };
3572
+ },
3573
+ { annotations: { readOnly: true } }
3574
+ );
3575
+ }
3576
+ function makeHostManagerStatusTool(session2) {
3577
+ return (0, import_claude_agent_sdk3.tool)(
3578
+ "kinetica_host_manager_status",
3579
+ "Query the Kinetica host manager root endpoint (port 9300) for cluster-wide status. Returns a flat key-value map including: version, hostname, system_mode ('run'/'stop'), system_status ('running'/'stopped'), system_idle_time (seconds), cluster_leader (IP), cluster_operation ('none'/'rebalance'/etc.), license_type/status/expiration, per-host and per-rank mode/status/pid, and service statuses (ml, httpd, query_planner, reveal, stats, graph, text). Healthy baseline: system_mode='run', system_status='running', all rankN_status='running', license_status='ok'. Does NOT require Kinetica DB authentication \u2014 queries the host manager service directly.",
3580
+ {},
3581
+ async (_args) => {
3582
+ const result = await hostManagerStatus(session2);
3583
+ return { content: [{ type: "text", text: applyOutputPipeline(result) }] };
3584
+ },
3585
+ { annotations: { readOnly: true } }
3586
+ );
3587
+ }
3588
+ function makeDiagnosticTools(session2, catalogSchemas) {
3589
+ return [
3590
+ makeHealthCheckTool(session2),
3591
+ makeGetMetricsTool(session2),
3592
+ makeClusterStatusTool(session2),
3593
+ makeNodeDetailsTool(session2),
3594
+ makeGetLogsTool(session2),
3595
+ makeShowConfigurationTool(session2),
3596
+ makeGetSystemPropertiesTool(session2),
3597
+ makeExecuteSqlTool(session2, catalogSchemas),
3598
+ makeExplainQueryTool(session2),
3599
+ makeSystemTimingTool(session2),
3600
+ makeResourceGroupsTool(session2),
3601
+ makeVerifyDbTool(session2),
3602
+ makeShowSecurityTool(session2),
3603
+ makeShowTableTool(session2),
3604
+ makeResourceObjectsTool(session2),
3605
+ makeHostManagerStatusTool(session2)
3606
+ ];
3607
+ }
3608
+ function makeMutationTools(session2) {
3609
+ return [
3610
+ makeAlterSystemPropertiesTool(session2),
3611
+ makeExecuteMutationSqlTool(session2),
3612
+ makeAdminRebalanceTool(session2),
3613
+ makeAlterConfigurationTool(session2)
3614
+ ];
3615
+ }
3616
+ function makeAlterTableColumnsToolWithDeps(session2) {
3617
+ return makeAlterTableColumnsTool(session2, {
3618
+ applyOutputPipeline,
3619
+ logMutationAudit
3620
+ });
3621
+ }
3622
+
3623
+ // src/tools/catalog.ts
3624
+ var TOOL_CATALOG = {
3625
+ kinetica_health_check: {
3626
+ reveals: "System health status, version info",
3627
+ whenToUse: "Every investigation (Round 1)"
3628
+ },
3629
+ kinetica_host_manager_status: {
3630
+ reveals: "Host manager overview: version, license, system mode, per-rank process status/PIDs, service statuses (ML, query planner, reveal, graph, text). No auth required.",
3631
+ whenToUse: "Every investigation (Round 1)"
3632
+ },
3633
+ kinetica_get_metrics: {
3634
+ reveals: "CPU, GPU, memory usage per rank",
3635
+ whenToUse: "Performance issues, OOM, high load"
3636
+ },
3637
+ kinetica_cluster_status: {
3638
+ reveals: "Rebalance/add/remove ops, shard mapping, alerts, jobs",
3639
+ whenToUse: "Cluster instability, replication issues"
3640
+ },
3641
+ kinetica_node_details: {
3642
+ reveals: "Per-node resource statistics",
3643
+ whenToUse: "Identifying which node is under pressure"
3644
+ },
3645
+ kinetica_get_logs: {
3646
+ reveals: "Application errors, warnings, system events",
3647
+ whenToUse: "Any error investigation (Round 1)"
3648
+ },
3649
+ kinetica_show_configuration: {
3650
+ reveals: "Full gpudb.conf from host manager (port 9300)",
3651
+ whenToUse: "Config drift, misconfiguration, complete config inspection"
3652
+ },
3653
+ kinetica_get_system_properties: {
3654
+ reveals: "Runtime config properties (260+ rows); filter by category or key pattern",
3655
+ whenToUse: "Read current property values before ALTER, version lookup, feature-flag audit"
3656
+ },
3657
+ kinetica_execute_sql: {
3658
+ reveals: "Query history, active queries, table stats, schema",
3659
+ whenToUse: "Slow queries, contention, data issues"
3660
+ },
3661
+ kinetica_explain_query: {
3662
+ reveals: "Query execution plan with operator tree",
3663
+ whenToUse: "Query performance optimization"
3664
+ },
3665
+ kinetica_system_timing: {
3666
+ reveals: "Per-endpoint response times, slow API detection",
3667
+ whenToUse: "Performance issues, slow endpoint response"
3668
+ },
3669
+ kinetica_resource_groups: {
3670
+ reveals: "Resource group config, tier usage per rank",
3671
+ whenToUse: "Resource allocation, tier capacity issues"
3672
+ },
3673
+ kinetica_verify_db: {
3674
+ reveals: "Database integrity: nulls, persistence, rank0 (healthy: verified_ok=true, orphaned_size=-1)",
3675
+ whenToUse: "Data corruption, integrity verification"
3676
+ },
3677
+ kinetica_show_security: {
3678
+ reveals: "User types, roles, permissions, resource groups",
3679
+ whenToUse: "Access control audit, authorization failures"
3680
+ },
3681
+ kinetica_show_table: {
3682
+ reveals: "Table names, sizes, properties, column types",
3683
+ whenToUse: "Schema inspection, column type inspection, table size analysis"
3684
+ },
3685
+ kinetica_resource_objects: {
3686
+ reveals: "Per-rank object placement across storage tiers",
3687
+ whenToUse: "Tier capacity, eviction, data placement"
3688
+ },
3689
+ kinetica_alter_system_properties: {
3690
+ reveals: "Apply runtime config changes (before/after/verify)",
3691
+ whenToUse: "Config drift remediation, thread pool tuning"
3692
+ },
3693
+ kinetica_execute_mutation_sql: {
3694
+ reveals: "Execute approved DDL/DML (CREATE INDEX, ALTER TABLE, etc.) \u2014 ANALYZE TABLE is NOT supported by Kinetica",
3695
+ whenToUse: "Query optimization, index creation"
3696
+ },
3697
+ kinetica_admin_rebalance: {
3698
+ reveals: "Trigger shard rebalancing (requires 2+ worker ranks)",
3699
+ whenToUse: "Shard imbalance, uneven data distribution"
3700
+ },
3701
+ kinetica_alter_configuration: {
3702
+ reveals: "Replace gpudb.conf on host manager (before/after/verify)",
3703
+ whenToUse: "Config remediation, targeted config file changes"
3704
+ },
3705
+ kinetica_alter_table_columns: {
3706
+ reveals: "Batch column type/property changes (DICT, TEXT_SEARCH, etc.) via checklist",
3707
+ whenToUse: "When recommending 2+ column changes on one table"
3708
+ }
3709
+ };
3710
+ function buildEvidenceChecklist() {
3711
+ const ordered = [
3712
+ ...DIAGNOSTIC_TOOL_NAMES,
3713
+ ...MUTATION_TOOL_NAMES,
3714
+ ALTER_TABLE_COLUMNS_TOOL_NAME
3715
+ ];
3716
+ const rows = ordered.map((name) => {
3717
+ const entry = TOOL_CATALOG[name];
3718
+ return `| ${name} | ${entry.reveals} | ${entry.whenToUse} |`;
3719
+ });
3720
+ return [
3721
+ "| Tool | What it reveals | When to use |",
3722
+ "|------|----------------|-------------|",
3723
+ ...rows
3724
+ ].join("\n");
3725
+ }
3726
+
3727
+ // src/agent/report-template.ts
3728
+ var import_node_fs2 = require("fs");
3729
+ var import_node_path3 = require("path");
3730
+
3731
+ // src/agent/load-playbooks.ts
3732
+ var import_promises2 = require("fs/promises");
3733
+ var import_node_path2 = require("path");
3734
+ var import_node_fs = require("fs");
3735
+ function findPackageRoot(startDir) {
3736
+ let dir = startDir;
3737
+ while (dir !== (0, import_node_path2.dirname)(dir)) {
3738
+ if ((0, import_node_fs.existsSync)((0, import_node_path2.join)(dir, "package.json"))) return dir;
3739
+ dir = (0, import_node_path2.dirname)(dir);
3740
+ }
3741
+ return startDir;
3742
+ }
3743
+ function parseFrontmatter(raw) {
3744
+ const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(raw);
3745
+ if (!match) return null;
3746
+ const yamlBlock = match[1];
3747
+ const fields = {};
3748
+ for (const line of yamlBlock.split(/\r?\n/)) {
3749
+ const colonIdx = line.indexOf(":");
3750
+ if (colonIdx === -1) continue;
3751
+ const key = line.slice(0, colonIdx).trim();
3752
+ const value = line.slice(colonIdx + 1).trim();
3753
+ if (key) fields[key] = value;
3754
+ }
3755
+ const title = fields.title;
3756
+ if (!title) return null;
3757
+ const keywordsRaw = fields.keywords ?? "";
3758
+ const keywords = keywordsRaw.startsWith("[") && keywordsRaw.endsWith("]") ? keywordsRaw.slice(1, -1).split(",").map((s) => s.trim()).filter(Boolean) : [];
3759
+ return {
3760
+ title,
3761
+ category: fields.category ?? "general",
3762
+ severity: fields.severity ?? "info",
3763
+ keywords
3764
+ };
3765
+ }
3766
+ function extractBody(raw) {
3767
+ const match = /^---\r?\n[\s\S]*?\r?\n---\r?\n([\s\S]*)$/.exec(raw);
3768
+ return match ? match[1].trim() : raw.trim();
3769
+ }
3770
+ async function loadPlaybooks(playbooksDir) {
3771
+ try {
3772
+ const dir = playbooksDir ?? (0, import_node_path2.join)(findPackageRoot(__dirname), "knowledge", "playbooks");
3773
+ if (!(0, import_node_fs.existsSync)(dir)) return [];
3774
+ const files = await (0, import_promises2.readdir)(dir);
3775
+ const mdFiles = files.filter((f) => f.endsWith(".md")).sort();
3776
+ const playbooks = [];
3777
+ for (const file of mdFiles) {
3778
+ const raw = await (0, import_promises2.readFile)((0, import_node_path2.join)(dir, file), "utf-8");
3779
+ const frontmatter = parseFrontmatter(raw);
3780
+ if (!frontmatter) continue;
3781
+ playbooks.push({
3782
+ ...frontmatter,
3783
+ body: extractBody(raw),
3784
+ filename: file
3785
+ });
3786
+ }
3787
+ return playbooks;
3788
+ } catch {
3789
+ return [];
3790
+ }
3791
+ }
3792
+
3793
+ // src/agent/report-template.ts
3794
+ function loadReportTemplateSync() {
3795
+ try {
3796
+ const root = findPackageRoot(__dirname);
3797
+ const path2 = (0, import_node_path3.join)(root, "knowledge", "templates", "report.md");
3798
+ return (0, import_node_fs2.readFileSync)(path2, "utf-8");
3799
+ } catch (err) {
3800
+ console.warn(`[report-template] failed to load knowledge/templates/report.md: ${String(err)}`);
3801
+ return "";
3802
+ }
3803
+ }
3804
+ var REPORT_TEMPLATE = loadReportTemplateSync();
3805
+
3806
+ // src/agent/system-prompt.ts
3807
+ function buildColumnDirective(schemas) {
3808
+ const lines = [
3809
+ "> **Verified Column Names** \u2014 always use these exact columns in SQL queries:"
3810
+ ];
3811
+ for (const [table, columns] of schemas.tables) {
3812
+ lines.push(`> - **${table}**: ${columns.join(", ")}`);
3813
+ }
3814
+ return lines.join("\n") + "\n\n";
3815
+ }
3816
+ function buildDiagnosticSqlSection(schemas) {
3817
+ const getColumns = (table) => schemas?.tables.get(table);
3818
+ const directive = schemas ? buildColumnDirective(schemas) : "";
3819
+ const sectionSqls = /* @__PURE__ */ new Map();
3820
+ for (const entry of BUILDER_REGISTRY) {
3821
+ const cols = getColumns(entry.table);
3822
+ const sql = cols ? entry.build(cols) : entry.fallback;
3823
+ const existing = sectionSqls.get(entry.section) ?? [];
3824
+ sectionSqls.set(entry.section, [...existing, sql]);
3825
+ }
3826
+ let result = directive;
3827
+ for (const [heading, sqls] of sectionSqls) {
3828
+ result += `**${heading}:**
3829
+ \`\`\`sql
3830
+ ${sqls.join("\n\n")}
3831
+ \`\`\`
3832
+
3833
+ `;
3834
+ }
3835
+ return result.trimEnd();
3836
+ }
3837
+ function buildFailurePatternsSection(playbooks) {
3838
+ if (!playbooks || playbooks.length === 0) return "";
3839
+ const entries = playbooks.map((p) => `**${p.title}:**
3840
+
3841
+ ${p.body}`).join("\n\n");
3842
+ return `### Common Failure Patterns
3843
+
3844
+ ${entries}`;
3845
+ }
3846
+ function buildReferenceSection(references) {
3847
+ if (!references || references.length === 0) return "";
3848
+ const entries = references.map((r) => `**${r.title}:**
3849
+
3850
+ ${r.body}`).join("\n\n");
3851
+ return `### Reference Knowledge
3852
+
3853
+ ${entries}`;
3854
+ }
3855
+ function buildSystemPrompt(kineticaVersion, catalogSchemas, playbooks, references, degraded) {
3856
+ const versionSection = kineticaVersion ? `**Kinetica Version:** ${kineticaVersion} (provided at session start)` : "**Kinetica Version:** Unknown \u2014 detect via kinetica_health_check as the first action of every investigation.";
3857
+ const t = "`";
3858
+ const degradedSection = degraded ? `
3859
+ ---
3860
+
3861
+ ## DEGRADED MODE \u2014 DB Engine Unreachable
3862
+
3863
+ **CRITICAL:** The Kinetica DB engine on port 9191 is DOWN. You are connected to the host manager (port 9300) only.
3864
+
3865
+ ### What works:
3866
+ - ${t}kinetica_host_manager_status${t} \u2014 **USE THIS FIRST** for every investigation. Returns version, license info, system mode, per-rank process status/PIDs, and service statuses (ML, query planner, reveal, graph, text).
3867
+
3868
+ ### What will fail:
3869
+ - ALL other diagnostic tools (${t}kinetica_health_check${t}, ${t}kinetica_get_metrics${t}, ${t}kinetica_cluster_status${t}, ${t}kinetica_execute_sql${t}, etc.) \u2014 they target port 9191 and will return errors.
3870
+ - SQL queries against ki_catalog system tables \u2014 the DB engine is required.
3871
+ - Mutation tools \u2014 cannot modify a downed system.
3872
+
3873
+ ### Investigation strategy in degraded mode:
3874
+ 1. Call ${t}kinetica_host_manager_status${t} to gather all available data
3875
+ 2. Analyze rank process statuses \u2014 look for stopped/crashed ranks and their PIDs
3876
+ 3. Check ${t}system_mode${t} and ${t}system_status${t} for cluster state
3877
+ 4. Check ${t}license_status${t} and ${t}license_expiration${t} for license issues
3878
+ 5. Check service statuses: ${t}ml_status${t}, ${t}query_planner_status${t}, ${t}reveal_status${t}, ${t}graph0_status${t}, ${t}text0_status${t}
3879
+ 6. Report findings and clearly note that full diagnostics require the DB engine to be running
3880
+ 7. Recommend the operator check: process logs (${t}/opt/gpudb/core/logs/${t}), ${t}gadmin status${t}, disk space, and network connectivity
3881
+
3882
+ ### Report adjustments for degraded mode:
3883
+ - Evidence Gaps MUST include: "DB engine unreachable (port 9191) \u2014 all DB-dependent diagnostic tools unavailable"
3884
+ - Remediation should prioritize bringing the DB engine back online
3885
+ - Do NOT attempt Round 4 (mutations) or Round 5 (verification) \u2014 the DB engine must be running first
3886
+
3887
+ ` : "";
3888
+ return `You are an expert Kinetica GPU database administrator and diagnostician with deep knowledge of Kinetica's internals, system tables, REST API, and common failure patterns. Your job is to autonomously investigate database issues reported by operators, gather diagnostic evidence, reason over that evidence to identify root causes, and produce a structured diagnostic report with actionable remediation steps.
3889
+
3890
+ ${versionSection}
3891
+ ${degradedSection}
3892
+ ---
3893
+
3894
+ ## Role and Mandate
3895
+
3896
+ You are the operator's expert assistant. When an operator describes a problem, you take ownership of the investigation. You use diagnostic tools to gather evidence, reason over that evidence to identify the most likely root cause, and deliver a clear, specific, actionable report. You never give vague or generic advice.
3897
+
3898
+ ---
3899
+
3900
+ ## Investigation Protocol
3901
+
3902
+ ### Pre-Investigation: Announce Your Plan
3903
+
3904
+ Before gathering any evidence, announce a brief 2-3 line investigation plan:
3905
+ 1. Restate the issue in your own words
3906
+ 2. List the primary tools you will check first
3907
+ 3. Begin immediately \u2014 do not wait for user confirmation
3908
+
3909
+ ### 5-Round Investigation Protocol
3910
+
3911
+ You have up to 5 rounds of tool calls:
3912
+
3913
+ **Round 1 \u2014 Initial Sweep:**
3914
+ Run a broad baseline sweep. Use parallel tool calls where possible \u2014 issue health + metrics + logs simultaneously to maximize efficiency. Goal: establish baseline and surface obvious anomalies.
3915
+
3916
+ Recommended Round 1 tools (in parallel):
3917
+ - ${t}kinetica_health_check${t} \u2014 system health status
3918
+ - ${t}kinetica_host_manager_status${t} \u2014 host manager cluster overview (version, license, per-rank/service status)
3919
+ - ${t}kinetica_get_metrics${t} \u2014 CPU/GPU/memory resource usage
3920
+ - ${t}kinetica_get_logs${t} (severity: ERROR, duration: 1h) \u2014 recent errors
3921
+
3922
+ **Round 2 \u2014 Targeted Drill-Down:**
3923
+ Based on Round 1 findings, perform targeted drill-down on specific hypotheses. Use additional tools as needed:
3924
+ - ${t}kinetica_cluster_status${t} \u2014 cluster operations, shard mapping, alerts
3925
+ - ${t}kinetica_node_details${t} \u2014 per-node resource breakdown
3926
+ - ${t}kinetica_execute_sql${t} \u2014 query history, active queries, table stats
3927
+ - ${t}kinetica_show_configuration${t} \u2014 full gpudb.conf from host manager
3928
+ - ${t}kinetica_system_timing${t} \u2014 endpoint timing, slow API detection
3929
+ - ${t}kinetica_show_table${t} \u2014 table sizes, properties, column types
3930
+
3931
+ **Round 3 \u2014 Confirmation Pass:**
3932
+ Confirm your primary hypothesis with additional evidence. Use ${t}kinetica_explain_query${t} for query plan issues. Run targeted SQL queries to validate root cause. Use ${t}kinetica_verify_db${t} when suspecting data integrity issues. Collect any final missing evidence.
3933
+
3934
+ After Round 3, you MUST write the report \u2014 even if uncertainty remains.
3935
+
3936
+ ### Round 4 -- Mutation Proposal
3937
+
3938
+ When diagnostic evidence supports a specific remediation:
3939
+ 1. Explain your reasoning in the tool call (the approval panel will display it)
3940
+ 2. Call the appropriate mutation tool:
3941
+ - ${t}kinetica_alter_table_columns${t} -- for batching 2+ column type/property changes on a SINGLE table
3942
+ into one efficient ALTER TABLE statement. Provide column_name, new_definition (full type def), and
3943
+ description for each column. The operator selects which columns via interactive checklist.
3944
+ - ${t}kinetica_alter_system_properties${t} -- for runtime property changes
3945
+ - ${t}kinetica_execute_mutation_sql${t} -- for index creation, single column change, or other DDL (note: Kinetica does NOT support ANALYZE TABLE)
3946
+ - ${t}kinetica_admin_rebalance${t} -- for shard distribution issues
3947
+ - ${t}kinetica_alter_configuration${t} -- for gpudb.conf file changes (via host manager)
3948
+ 3. If the user denies: acknowledge, note in report as denied, move to next recommendation
3949
+ 4. If the tool fails after approval: note the failure in report, explain the error, suggest alternatives
3950
+
3951
+ **When to use ${t}kinetica_alter_table_columns${t} vs ${t}kinetica_execute_mutation_sql${t}:**
3952
+ - 2+ column changes on the same table \u2192 use ${t}kinetica_alter_table_columns${t} (single efficient statement)
3953
+ - 1 column change, or non-column DDL (e.g., CREATE INDEX) \u2192 use ${t}kinetica_execute_mutation_sql${t}. Do NOT call ANALYZE TABLE \u2014 it is not supported by Kinetica and there is no equivalent "refresh stats" command.
3954
+
3955
+ ### Round 5 -- Post-Mutation Verification
3956
+
3957
+ After all approved mutations:
3958
+ 1. Re-run relevant diagnostic tools (${t}kinetica_get_system_properties${t}, ${t}kinetica_cluster_status${t}, ${t}kinetica_get_metrics${t}, ${t}kinetica_host_manager_status${t})
3959
+ 2. Confirm changes took effect (compare before/after values from tool results)
3960
+ 3. Check for any new issues introduced by the mutations
3961
+ 4. Proceed to report generation
3962
+
3963
+ ### Parallel Tool Calls
3964
+
3965
+ Issue independent tool calls simultaneously when possible. For example:
3966
+ - In Round 1: issue ${t}kinetica_health_check${t}, ${t}kinetica_host_manager_status${t}, ${t}kinetica_get_metrics${t}, and ${t}kinetica_get_logs${t} together
3967
+ - In Round 2: issue ${t}kinetica_cluster_status${t} and ${t}kinetica_node_details${t} together
3968
+
3969
+ ---
3970
+
3971
+ ## Evidence Checklist \u2014 Diagnostic Tools
3972
+
3973
+ Each tool provides specific diagnostic value. Use them strategically:
3974
+
3975
+ ${buildEvidenceChecklist()}
3976
+
3977
+ ---
3978
+
3979
+ ## Kinetica Domain Knowledge
3980
+
3981
+ ### System Tables for Diagnostics
3982
+
3983
+ Use kinetica_execute_sql to query these system tables:
3984
+
3985
+ ` + buildDiagnosticSqlSection(catalogSchemas) + `
3986
+
3987
+ ### Tables That May Be Empty (Not an Error)
3988
+
3989
+ These tables return 0 rows when the feature is not configured \u2014 this is normal:
3990
+ - ${t}ki_catalog.ki_periodic_objects${t} \u2014 no scheduled/periodic refresh objects
3991
+ - ${t}ki_catalog.ki_backup_history${t} \u2014 no backups have been performed
3992
+ - ${t}ki_catalog.ki_kafka_lag_info${t} \u2014 no Kafka streaming ingestion configured
3993
+
3994
+ ### Response Data Interpretation
3995
+
3996
+ Tool responses have these consistent patterns:
3997
+ - **All values are strings** \u2014 numeric fields like ram_used, sizes, counts are returned as strings, not numbers
3998
+ - **Empty string vs "0":** Empty string ${t}""${t} means a tier/feature is not configured; ${t}"0"${t} means configured but currently unused
3999
+ - **JSON-encoded strings:** Health check status values, resource group rank_usage, and resource object rank_objects contain JSON as strings \u2014 parse mentally to extract nested fields
4000
+ - **Timestamps:** SQL queries return epoch milliseconds (e.g., 1774153326000); compute duration as ${t}(stop_time - start_time)${t} in milliseconds
4001
+ - **Long.MAX_VALUE:** ${t}9223372036854775807${t} in resource group limits means unlimited
4002
+
4003
+ ### Column Type Inspection
4004
+
4005
+ **Preferred method:** Use ${t}kinetica_show_table${t} with a specific ${t}table_name${t} to get
4006
+ Kinetica-native column types and per-column properties (DICT, TEXT_SEARCH, COMPRESS, etc.).
4007
+
4008
+ **Avoid for types:** ${t}ki_catalog.ki_columns${t} returns SQL-standard types (e.g., ${t}character(64)${t})
4009
+ not Kinetica-native types. Use ${t}ki_columns${t} only for structural metadata not available from
4010
+ ${t}kinetica_show_table${t} (e.g., ${t}is_shard_key${t}, ${t}is_primary_key${t}, disk compression stats).
4011
+
4012
+ ${buildFailurePatternsSection(playbooks)}
4013
+
4014
+ ${buildReferenceSection(references)}
4015
+
4016
+ ---
4017
+
4018
+ ## Analysis Instructions
4019
+
4020
+ ### Commit to the Best Hypothesis
4021
+
4022
+ After gathering evidence, you MUST name specific root causes. No generic hedging.
4023
+
4024
+ **DO:**
4025
+ - "Root cause: GPU OOM due to query materializing 45GB result set in VRAM on rank 3"
4026
+ - "Root cause: Stale rank \u2014 rank 2 failed to rejoin cluster after network event at 14:23 UTC"
4027
+ - "If uncertain, rank top 2-3 hypotheses by likelihood: Primary (70%): X; Secondary (25%): Y; ranked by likelihood"
4028
+
4029
+ **DO NOT:**
4030
+ - "There could be various reasons for this issue..."
4031
+ - "It might be a performance problem or possibly a configuration issue..."
4032
+ - "Further investigation may be needed..."
4033
+
4034
+ This is not about hedging on uncertainty \u2014 you can say "I don't have enough evidence for the exact version" \u2014 it is about not avoiding a conclusion. Always commit to the most likely root cause with supporting evidence.
4035
+
4036
+ ### Tie Evidence to Conclusions
4037
+
4038
+ Every conclusion must reference specific evidence:
4039
+ - Wrong: "The cluster appears to have memory issues"
4040
+ - Right: "GPU memory on rank 3 is at 98% (kinetica_get_metrics) with 3 queries materializing >10GB each in ki_catalog.ki_query_history"
4041
+
4042
+ ### Evidence Gap Handling
4043
+
4044
+ **SQL column errors (recoverable):**
4045
+ 1. Check the **Verified Column Names** list in this prompt for the correct columns
4046
+ 2. Rewrite the query using only verified columns and retry once
4047
+ 3. If no verified columns exist for that table, fall back to \`SELECT * FROM ki_catalog.<table> LIMIT 10\`
4048
+ 4. If the retry also fails, log the gap and continue
4049
+
4050
+ **Other tool failures (non-recoverable):**
4051
+ - Note the gap and continue. Use this format:
4052
+ - "Cluster status: unavailable (HTTP status 503 \u2014 WMS unreachable)"
4053
+ - "Log retrieval: failed (HTTP status 401 \u2014 authentication issue)"
4054
+
4055
+ Never halt the investigation on a single tool failure.
4056
+
4057
+ ---
4058
+
4059
+ ## Fix Instructions
4060
+
4061
+ Include specific, actionable remediation steps tied to your findings. Structure your actionable remediation as a numbered list:
4062
+
4063
+ 1. Immediate manual actions the operator can take now
4064
+ 2. Configuration changes to prevent recurrence
4065
+ 3. Monitoring/alerting improvements to add
4066
+ 4. Agent-assisted mutations (propose via mutation tools with user approval)
4067
+
4068
+ ---
4069
+
4070
+ ## Post-Report Behavior
4071
+
4072
+ 1. Call the ${t}save_report${t} tool with the complete report markdown content to save it to disk.
4073
+ 2. After the report is saved, ask: "Would you like to investigate another issue, or end the session?"
4074
+ 3. If the operator wants another investigation, start fresh with the same 5-round protocol.
4075
+ 4. On session end: summarize all issues investigated and list the saved report file paths, then exit.
4076
+
4077
+ ---
4078
+
4079
+ ## Context Window Awareness
4080
+
4081
+ Monitor your context window usage during long investigations:
4082
+ - After many tool calls with verbose results, the context window may be getting full.
4083
+ - If you detect that context is getting full (many rounds, many large tool responses), warn the operator: "The session context is getting long. Consider starting a fresh session after this report to maintain investigation quality. Your reports are saved to disk."
4084
+ - Do NOT continue investigating when context is too full \u2014 write the report with evidence gathered so far.
4085
+
4086
+ ---
4087
+
4088
+ ## Output Formatting
4089
+
4090
+ When presenting data in your response, use clean, well-structured markdown tables:
4091
+
4092
+ - Use standard markdown table syntax: header row, separator row (with dashes), then data rows
4093
+ - Keep column count low (3\u20136 columns max). If data has more dimensions, split into multiple focused tables
4094
+ - **Bold** key identifiers in the first column for scannability
4095
+ - Use consistent status indicators: \`OK\`, \`WARN\`, \`ERROR\`, \`N/A\`
4096
+ - Do NOT dump raw tool output \u2014 synthesize findings into clean, readable tables
4097
+ - Align numeric columns for easy comparison
4098
+
4099
+ Example:
4100
+
4101
+ | **Node** | CPU | Memory | Status |
4102
+ | ---------- | --- | ------ | ------ |
4103
+ | **node_0** | 45% | 12 GB | OK |
4104
+ | **node_1** | 92% | 15 GB | WARN |
4105
+
4106
+ ---
4107
+
4108
+ ## REPORT TEMPLATE
4109
+
4110
+ At the end of each investigation, generate a structured markdown report using this EXACT template and section order:
4111
+
4112
+ \`\`\`markdown
4113
+ ` + REPORT_TEMPLATE + `\`\`\`
4114
+
4115
+ **CRITICAL:** Use this exact section order. The metadata table comes first. Summary before Remediation. Evidence Collected before Evidence Gaps. Mutations Applied before Post-Remediation Verification. Do NOT reorder sections.
4116
+
4117
+ **Section order:** Metadata -> Summary -> Remediation -> Root Cause Analysis -> Evidence Collected -> Evidence Gaps -> Mutations Applied -> Post-Remediation Verification
4118
+
4119
+ **Evidence Collected guidance:** Include only the key data points that led to your conclusion. No raw JSON dumps. No full log output. Extract the 3-10 most relevant findings.
4120
+ `;
4121
+ }
4122
+
4123
+ // src/agent/discover-schemas.ts
4124
+ var TARGET_TABLES = [
4125
+ "ki_query_history",
4126
+ "ki_query_active_all",
4127
+ "ki_query_span_metrics_all",
4128
+ "ki_query_workers",
4129
+ "ki_tiered_objects",
4130
+ "ki_obj_stat",
4131
+ "ki_partitions",
4132
+ "ki_objects",
4133
+ "ki_indexes",
4134
+ "ki_periodic_objects",
4135
+ "ki_depend",
4136
+ "ki_users_and_roles",
4137
+ "ki_object_permissions",
4138
+ "ki_load_history",
4139
+ "ki_backup_history",
4140
+ "ki_kafka_lag_info",
4141
+ "ki_columns",
4142
+ "ki_datatypes"
4143
+ ];
4144
+ async function discoverCatalogSchemas(session2) {
4145
+ try {
4146
+ const tableList = TARGET_TABLES.map((t) => `'${t}'`).join(", ");
4147
+ const statement = `SELECT table_name, column_name FROM ki_catalog.ki_columns WHERE table_name IN (${tableList}) ORDER BY table_name, column_name`;
4148
+ const result = await executeSql(session2, statement, 1e3);
4149
+ if (!result.ok) {
4150
+ return void 0;
4151
+ }
4152
+ const rows = result.data;
4153
+ if (rows.length === 0) {
4154
+ return void 0;
4155
+ }
4156
+ const grouped = /* @__PURE__ */ new Map();
4157
+ for (const row of rows) {
4158
+ const existing = grouped.get(row.table_name) ?? [];
4159
+ grouped.set(row.table_name, [...existing, row.column_name]);
4160
+ }
4161
+ return { tables: grouped };
4162
+ } catch {
4163
+ return void 0;
4164
+ }
4165
+ }
4166
+
4167
+ // src/agent/load-references.ts
4168
+ var import_promises3 = require("fs/promises");
4169
+ var import_node_path4 = require("path");
4170
+ var import_node_fs3 = require("fs");
4171
+ async function loadReferences(refsDir) {
4172
+ try {
4173
+ const dir = refsDir ?? (0, import_node_path4.join)(findPackageRoot(__dirname), "knowledge", "references");
4174
+ if (!(0, import_node_fs3.existsSync)(dir)) return [];
4175
+ const files = await (0, import_promises3.readdir)(dir);
4176
+ const mdFiles = files.filter((f) => f.endsWith(".md")).sort();
4177
+ const references = [];
4178
+ for (const file of mdFiles) {
4179
+ const raw = await (0, import_promises3.readFile)((0, import_node_path4.join)(dir, file), "utf-8");
4180
+ const frontmatter = parseFrontmatter(raw);
4181
+ if (!frontmatter) continue;
4182
+ references.push({
4183
+ title: frontmatter.title,
4184
+ category: frontmatter.category,
4185
+ keywords: frontmatter.keywords,
4186
+ body: extractBody(raw),
4187
+ filename: file
4188
+ });
4189
+ }
4190
+ return references;
4191
+ } catch {
4192
+ return [];
4193
+ }
4194
+ }
4195
+
4196
+ // src/agent/prompt-budget.ts
4197
+ var CHARS_PER_TOKEN = 4;
4198
+ var DEFAULT_PROMPT_BUDGET_TOKENS = 15e3;
4199
+ function estimateTokens(text) {
4200
+ if (!text) return 0;
4201
+ return Math.ceil(text.length / CHARS_PER_TOKEN);
4202
+ }
4203
+ function checkPromptBudget(prompt, opts) {
4204
+ const threshold = opts?.warnAtTokens ?? DEFAULT_PROMPT_BUDGET_TOKENS;
4205
+ const tokens = estimateTokens(prompt);
4206
+ return {
4207
+ tokens,
4208
+ chars: prompt ? prompt.length : 0,
4209
+ threshold,
4210
+ overBudget: tokens > threshold
4211
+ };
4212
+ }
4213
+
4214
+ // src/report/save-report.ts
4215
+ var import_promises4 = require("fs/promises");
4216
+ var import_node_path5 = require("path");
4217
+ var import_claude_agent_sdk4 = require("@anthropic-ai/claude-agent-sdk");
4218
+ var import_zod17 = require("zod");
4219
+ var PARTIAL_MARKER = "(PARTIAL -- investigation interrupted)\n\n";
4220
+ function formatTimestamp(date) {
4221
+ const year = date.getUTCFullYear();
4222
+ const month = String(date.getUTCMonth() + 1).padStart(2, "0");
4223
+ const day = String(date.getUTCDate()).padStart(2, "0");
4224
+ const hours = String(date.getUTCHours()).padStart(2, "0");
4225
+ const minutes = String(date.getUTCMinutes()).padStart(2, "0");
4226
+ const seconds = String(date.getUTCSeconds()).padStart(2, "0");
4227
+ return `${year}-${month}-${day}-${hours}${minutes}${seconds}`;
4228
+ }
4229
+ function makeSaveReportTool() {
4230
+ return (0, import_claude_agent_sdk4.tool)(
4231
+ "save_report",
4232
+ "Save a diagnostic report to disk. Automatically scrubs credentials, creates a timestamped filename in reports/, and auto-creates the directory. Use at the end of each investigation or when interrupted.",
4233
+ {
4234
+ content: import_zod17.z.string().describe("The full markdown diagnostic report content"),
4235
+ partial: import_zod17.z.boolean().optional().describe(
4236
+ "Set to true if the investigation was interrupted (e.g., Ctrl+C). Prepends a PARTIAL marker to the report."
4237
+ )
4238
+ },
4239
+ async (args) => {
4240
+ const rawContent = args.partial ? `${PARTIAL_MARKER}${args.content}` : args.content;
4241
+ const scrubbed = scrubCredentials(rawContent);
4242
+ const timestamp = formatTimestamp(/* @__PURE__ */ new Date());
4243
+ const filename = `kinetica-diag-${timestamp}.md`;
4244
+ const dir = (0, import_node_path5.resolve)(process.cwd(), "reports");
4245
+ await (0, import_promises4.mkdir)(dir, { recursive: true });
4246
+ const filepath = (0, import_node_path5.join)(dir, filename);
4247
+ await (0, import_promises4.writeFile)(filepath, scrubbed, "utf-8");
4248
+ return {
4249
+ content: [{ type: "text", text: `Report saved: ${filepath}` }]
4250
+ };
4251
+ },
4252
+ { annotations: { readOnly: true } }
4253
+ );
4254
+ }
4255
+
4256
+ // src/approval/gate.ts
4257
+ var import_prompts7 = require("@inquirer/prompts");
4258
+
4259
+ // src/approval/display.ts
4260
+ var import_picocolors10 = __toESM(require("picocolors"));
4261
+ var IMPACT_FALLBACK = "Impact unknown \u2014 review parameters carefully";
4262
+ var DIVIDER2 = import_picocolors10.default.dim("\u2500".repeat(50));
4263
+ var LABEL_WIDTH = 8;
4264
+ function formatLabel(label) {
4265
+ return ` ${label.padEnd(LABEL_WIDTH)}: `;
4266
+ }
4267
+ function renderApprovalPanel(toolName, toolInput, impact, beforeAfter, reasoningSummary) {
4268
+ const header = import_picocolors10.default.bold(import_picocolors10.default.yellow(" Mutation Approval Required"));
4269
+ const action = `${formatLabel("Action")}${import_picocolors10.default.bold(formatToolName(toolName))}`;
4270
+ const paramEntries = Object.entries(toolInput);
4271
+ const paramSection = paramEntries.length === 0 ? " (no parameters)" : paramEntries.map(([key, value]) => {
4272
+ const formatted = typeof value === "string" ? value : JSON.stringify(value, null, 2);
4273
+ return ` ${import_picocolors10.default.dim(key)}: ${formatted}`;
4274
+ }).join("\n");
4275
+ const impactLine = `${formatLabel("Impact")}${impact ?? IMPACT_FALLBACK}`;
4276
+ const prompt = import_picocolors10.default.dim(
4277
+ `${formatLabel("Respond")}y (proceed) | n (abort) | explain (show reasoning)`
4278
+ );
4279
+ const hasBeforeAfter = beforeAfter !== void 0 && beforeAfter.length > 0;
4280
+ const beforeAfterSection = hasBeforeAfter ? beforeAfter.map(
4281
+ (entry) => ` ${import_picocolors10.default.dim(entry.key)}: ${entry.current} ${import_picocolors10.default.yellow("->")} ${entry.proposed}`
4282
+ ).join("\n") : null;
4283
+ const hasReasoning = reasoningSummary !== void 0 && reasoningSummary.length > 0;
4284
+ const reasoningSection = hasReasoning ? `${formatLabel("Reason")}${reasoningSummary}` : null;
4285
+ const sections = ["", DIVIDER2, header, "", action, paramSection, ""];
4286
+ if (beforeAfterSection !== null) {
4287
+ sections.push(beforeAfterSection, "");
4288
+ }
4289
+ if (reasoningSection !== null) {
4290
+ sections.push(reasoningSection, "");
4291
+ }
4292
+ sections.push(impactLine, "", prompt, DIVIDER2, "");
4293
+ return sections.join("\n");
4294
+ }
4295
+
4296
+ // src/approval/gate.ts
4297
+ var DENY_MESSAGE = "User denied this mutation. Skip and continue with the investigation.";
4298
+ var REASONING_FALLBACK = "Reasoning not available. Review the action details above before proceeding.";
4299
+ function createApprovalGate(isReadOnly) {
4300
+ return async (toolName, toolInput, options) => {
4301
+ if (isReadOnly(toolName)) {
4302
+ return {
4303
+ behavior: "allow",
4304
+ updatedInput: toolInput,
4305
+ toolUseID: options.toolUseID
4306
+ };
4307
+ }
4308
+ const impact = options.decisionReason;
4309
+ const panel = renderApprovalPanel(toolName, toolInput, impact);
4310
+ console.error(panel);
4311
+ while (true) {
4312
+ try {
4313
+ const raw = await (0, import_prompts7.input)({ message: "Proceed? (y/n/explain):" }, { signal: options.signal });
4314
+ const normalized = raw.trim().toLowerCase();
4315
+ if (normalized === "y") {
4316
+ process.stderr.write("\n");
4317
+ return {
4318
+ behavior: "allow",
4319
+ updatedInput: toolInput,
4320
+ toolUseID: options.toolUseID
4321
+ };
4322
+ }
4323
+ if (normalized === "n") {
4324
+ process.stderr.write("\n");
4325
+ return {
4326
+ behavior: "deny",
4327
+ message: DENY_MESSAGE,
4328
+ toolUseID: options.toolUseID
4329
+ };
4330
+ }
4331
+ if (normalized === "explain") {
4332
+ const reasoning = options.decisionReason;
4333
+ if (reasoning) {
4334
+ console.error(`
4335
+ Agent reasoning: ${reasoning}
4336
+ `);
4337
+ } else {
4338
+ console.error(`
4339
+ ${REASONING_FALLBACK}
4340
+ `);
4341
+ }
4342
+ }
4343
+ } catch {
4344
+ return {
4345
+ behavior: "deny",
4346
+ message: DENY_MESSAGE,
4347
+ toolUseID: options.toolUseID
4348
+ };
4349
+ }
4350
+ }
4351
+ };
4352
+ }
4353
+
4354
+ // src/agent/turn-gate.ts
4355
+ function createTurnGate() {
4356
+ let resolve2 = () => {
4357
+ };
4358
+ let promise = new Promise((r) => {
4359
+ resolve2 = r;
4360
+ });
4361
+ return Object.freeze({
4362
+ wait: () => promise,
4363
+ open: () => {
4364
+ resolve2();
4365
+ },
4366
+ close: () => {
4367
+ promise = new Promise((r) => {
4368
+ resolve2 = r;
4369
+ });
4370
+ }
4371
+ });
4372
+ }
4373
+
4374
+ // src/output/render-markdown.ts
4375
+ var import_picocolors11 = __toESM(require("picocolors"));
4376
+ var BOLD_RE = /\*\*(.+?)\*\*/g;
4377
+ var HEADING_RE = /^(#{1,6})\s+(.+)$/;
4378
+ function renderMarkdownLine(line) {
4379
+ const headingMatch = HEADING_RE.exec(line);
4380
+ if (headingMatch) {
4381
+ return import_picocolors11.default.bold(headingMatch[2]);
4382
+ }
4383
+ if (line.includes("**")) {
4384
+ return line.replace(BOLD_RE, (_, text) => import_picocolors11.default.bold(text));
4385
+ }
4386
+ return line;
4387
+ }
4388
+
4389
+ // src/output/reformat-tables.ts
4390
+ var SEPARATOR_CELL_RE = /^:?-+:?$/;
4391
+ var BOLD_MARKERS_RE = /\*\*(.+?)\*\*/g;
4392
+ function visualWidth(text) {
4393
+ return text.replace(BOLD_MARKERS_RE, "$1").length;
4394
+ }
4395
+ function isSeparatorCell(cell) {
4396
+ return SEPARATOR_CELL_RE.test(cell);
4397
+ }
4398
+ function isSeparatorRow(cells) {
4399
+ return cells.length > 0 && cells.every(isSeparatorCell);
4400
+ }
4401
+ function parseCells(line) {
4402
+ return line.split("|").slice(1, -1).map((c) => c.trim());
4403
+ }
4404
+ function reformatTableBlock(lines) {
4405
+ const parsed = lines.map(parseCells);
4406
+ const colCount = Math.max(...parsed.map((row) => row.length));
4407
+ const normalised = parsed.map((row) => {
4408
+ const padded = [...row];
4409
+ while (padded.length < colCount) {
4410
+ padded.push("");
4411
+ }
4412
+ return padded;
4413
+ });
4414
+ const colWidths = Array.from(
4415
+ { length: colCount },
4416
+ (_, col) => Math.max(
4417
+ 3,
4418
+ ...normalised.filter((row) => !isSeparatorRow(row)).map((row) => visualWidth(row[col]))
4419
+ )
4420
+ );
4421
+ const borderRow = `+${colWidths.map((w) => "-".repeat(w + 2)).join("+")}+`;
4422
+ const bodyRows = normalised.map((row) => {
4423
+ if (isSeparatorRow(row)) {
4424
+ return borderRow;
4425
+ }
4426
+ const cells = row.map((cell, col) => {
4427
+ const rendered = renderMarkdownLine(cell);
4428
+ const pad = colWidths[col] - visualWidth(cell);
4429
+ return rendered + " ".repeat(Math.max(0, pad));
4430
+ });
4431
+ return `| ${cells.join(" | ")} |`;
4432
+ });
4433
+ return [borderRow, ...bodyRows, borderRow];
4434
+ }
4435
+
4436
+ // src/output/streaming-table-aligner.ts
4437
+ var TABLE_LINE_RE = /^\|.*\|$/;
4438
+ function createStreamingTableAligner() {
4439
+ let lineBuffer = "";
4440
+ let tableLines = [];
4441
+ function flushTable() {
4442
+ if (tableLines.length === 0) return "";
4443
+ const aligned = reformatTableBlock(tableLines);
4444
+ tableLines = [];
4445
+ return aligned.join("\n") + "\n";
4446
+ }
4447
+ function push(text) {
4448
+ if (!text) return "";
4449
+ const combined = lineBuffer + text;
4450
+ const segments = combined.split("\n");
4451
+ lineBuffer = segments[segments.length - 1];
4452
+ const completeLines = segments.slice(0, -1);
4453
+ let output = "";
4454
+ for (const line of completeLines) {
4455
+ const trimmed = line.trim();
4456
+ if (TABLE_LINE_RE.test(trimmed)) {
4457
+ tableLines.push(trimmed);
4458
+ } else {
4459
+ output += flushTable();
4460
+ output += renderMarkdownLine(line) + "\n";
4461
+ }
4462
+ }
4463
+ return output;
4464
+ }
4465
+ function flush() {
4466
+ let output = flushTable();
4467
+ if (lineBuffer) {
4468
+ output += renderMarkdownLine(lineBuffer);
4469
+ lineBuffer = "";
4470
+ }
4471
+ return output;
4472
+ }
4473
+ return Object.freeze({ push, flush });
4474
+ }
4475
+
4476
+ // src/output/spinner.ts
4477
+ var import_picocolors12 = __toESM(require("picocolors"));
4478
+ var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
4479
+ var FRAME_INTERVAL_MS = 80;
4480
+ var DEFAULT_LABEL = "Thinking";
4481
+ function createSpinner() {
4482
+ let timer = null;
4483
+ let frameIndex = 0;
4484
+ const start = (label = DEFAULT_LABEL) => {
4485
+ if (timer !== null) return;
4486
+ frameIndex = 0;
4487
+ timer = setInterval(() => {
4488
+ const frame = FRAMES[frameIndex % FRAMES.length];
4489
+ process.stderr.write(`\r${import_picocolors12.default.dim(`${frame} ${label}...`)}`);
4490
+ frameIndex += 1;
4491
+ }, FRAME_INTERVAL_MS);
4492
+ timer.unref();
4493
+ };
4494
+ const stop = () => {
4495
+ if (timer === null) return;
4496
+ clearInterval(timer);
4497
+ timer = null;
4498
+ process.stderr.write("\r\x1B[K");
4499
+ };
4500
+ const isRunning = () => timer !== null;
4501
+ return Object.freeze({ start, stop, isRunning });
4502
+ }
4503
+
4504
+ // src/agent/run-agent.ts
4505
+ var MCP_SERVER_NAME = "kinetica-diagnostics";
4506
+ var EXIT_COMMANDS = /* @__PURE__ */ new Set(["exit", "quit", "end", "q"]);
4507
+ var SUPPORTED_MODELS = ["sonnet", "haiku", "opus"];
4508
+ var DEFAULT_AGENT_MODEL = "sonnet";
4509
+ var DEFAULT_MAX_BUDGET_USD = 5;
4510
+ var ALLOWED_TOOL_NAMES = [
4511
+ ...DIAGNOSTIC_TOOL_NAMES.map((name) => `mcp__${MCP_SERVER_NAME}__${name}`),
4512
+ `mcp__${MCP_SERVER_NAME}__save_report`,
4513
+ `mcp__${MCP_SERVER_NAME}__${ALTER_TABLE_COLUMNS_TOOL_NAME}`
4514
+ ];
4515
+ var DISALLOWED_TOOLS = ["Bash", "Edit", "Write", "MultiEdit"];
4516
+ var ERROR_LABELS = {
4517
+ authentication_failed: "Authentication failed \u2014 check your API key or re-run with --login",
4518
+ billing_error: "Billing error \u2014 check your Anthropic account",
4519
+ rate_limit: "Rate limit exceeded",
4520
+ server_error: "Anthropic API server error",
4521
+ invalid_request: "Invalid API request",
4522
+ max_output_tokens: "Response exceeded maximum output length",
4523
+ unknown: "Unknown API error"
4524
+ };
4525
+ function isExitCommand(text) {
4526
+ return EXIT_COMMANDS.has(text.trim().toLowerCase());
4527
+ }
4528
+ function makeUserMessage(content) {
4529
+ return {
4530
+ type: "user",
4531
+ message: { role: "user", content },
4532
+ parent_tool_use_id: null,
4533
+ session_id: ""
4534
+ };
4535
+ }
4536
+ async function* makeInteractivePrompt(abortController, turnGate, spinner) {
4537
+ while (!abortController.signal.aborted) {
4538
+ try {
4539
+ process.stderr.write("\n");
4540
+ const issue = await (0, import_prompts8.input)({ message: "Describe the issue to investigate:" });
4541
+ process.stderr.write("\n");
4542
+ const trimmed = issue.trim();
4543
+ if (!trimmed) continue;
4544
+ if (isExitCommand(trimmed)) return;
4545
+ spinner.start();
4546
+ yield makeUserMessage(trimmed);
4547
+ break;
4548
+ } catch {
4549
+ return;
4550
+ }
4551
+ }
4552
+ while (!abortController.signal.aborted) {
4553
+ try {
4554
+ await turnGate.wait();
4555
+ if (abortController.signal.aborted) break;
4556
+ process.stderr.write("\n");
4557
+ const response = await (0, import_prompts8.input)({ message: "You:" });
4558
+ process.stderr.write("\n");
4559
+ const trimmed = response.trim();
4560
+ if (!trimmed) continue;
4561
+ if (isExitCommand(trimmed)) return;
4562
+ turnGate.close();
4563
+ spinner.start();
4564
+ yield makeUserMessage(trimmed);
4565
+ } catch {
4566
+ return;
4567
+ }
4568
+ }
4569
+ }
4570
+ async function displayDegradedStatus(session2) {
4571
+ const [statusResult, alertsResult] = await Promise.all([
4572
+ hostManagerStatus(session2),
4573
+ hostManagerAlerts(session2)
4574
+ ]);
4575
+ process.stderr.write(import_picocolors13.default.bold("\u2500\u2500 Host Manager Status \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
4576
+ if (statusResult.ok) {
4577
+ const rows = statusResult.data;
4578
+ const maxKeyLen = rows.reduce((max, r) => Math.max(max, r.key.length), 0);
4579
+ for (const row of rows) {
4580
+ process.stderr.write(` ${import_picocolors13.default.dim(row.key.padEnd(maxKeyLen))} ${row.value}
4581
+ `);
4582
+ }
4583
+ } else {
4584
+ process.stderr.write(` ${import_picocolors13.default.red(`Error: ${statusResult.error}`)}
4585
+ `);
4586
+ }
4587
+ process.stderr.write("\n");
4588
+ process.stderr.write(import_picocolors13.default.bold("\u2500\u2500 Recent Alerts \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
4589
+ if (alertsResult.ok) {
4590
+ const alerts = alertsResult.data;
4591
+ if (alerts.length === 0) {
4592
+ process.stderr.write(` ${import_picocolors13.default.dim("No recent alerts.")}
4593
+ `);
4594
+ } else {
4595
+ for (const alert of alerts) {
4596
+ process.stderr.write(` ${import_picocolors13.default.dim(alert.timestamp)} ${alert.type} ${alert.params}
4597
+ `);
4598
+ }
4599
+ }
4600
+ } else {
4601
+ process.stderr.write(` ${import_picocolors13.default.dim(`Unavailable: ${alertsResult.error}`)}
4602
+ `);
4603
+ }
4604
+ process.stderr.write("\n");
4605
+ }
4606
+ async function runAgent(session2, kineticaVersion, degraded, model) {
4607
+ const [catalogSchemas, playbooks, references] = await Promise.all([
4608
+ degraded ? Promise.resolve(void 0) : discoverCatalogSchemas(session2),
4609
+ loadPlaybooks(),
4610
+ loadReferences()
4611
+ ]);
4612
+ const systemPrompt = buildSystemPrompt(
4613
+ kineticaVersion,
4614
+ catalogSchemas,
4615
+ playbooks,
4616
+ references,
4617
+ degraded
4618
+ );
4619
+ const budget = checkPromptBudget(systemPrompt);
4620
+ if (process.env.DEBUG) {
4621
+ process.stderr.write(
4622
+ import_picocolors13.default.dim(`system prompt: ~${budget.tokens} tokens (${budget.chars} chars)
4623
+ `)
4624
+ );
4625
+ }
4626
+ if (budget.overBudget) {
4627
+ process.stderr.write(
4628
+ import_picocolors13.default.yellow(
4629
+ `\u26A0 system prompt is ~${budget.tokens} tokens (threshold ${budget.threshold}) \u2014 knowledge corpus is getting expensive; consider keyword-based playbook selection.
4630
+ `
4631
+ )
4632
+ );
4633
+ }
4634
+ const diagnosticTools = makeDiagnosticTools(session2, catalogSchemas);
4635
+ const mutationTools = makeMutationTools(session2);
4636
+ const saveReportTool = makeSaveReportTool();
4637
+ const alterTableColumnsTool = makeAlterTableColumnsToolWithDeps(session2);
4638
+ const server = (0, import_claude_agent_sdk5.createSdkMcpServer)({
4639
+ name: MCP_SERVER_NAME,
4640
+ version: "1.0.0",
4641
+ tools: [...diagnosticTools, ...mutationTools, saveReportTool, alterTableColumnsTool]
4642
+ });
4643
+ const spinner = createSpinner();
4644
+ const registry = createDiagnosticRegistry();
4645
+ const approvalGate = createApprovalGate(registry.isReadOnlyTool);
4646
+ const canUseTool = async (toolName, toolInput, options2) => {
4647
+ spinner.stop();
4648
+ return approvalGate(toolName, toolInput, options2);
4649
+ };
4650
+ const abortController = new AbortController();
4651
+ const effectiveModel = model ?? DEFAULT_AGENT_MODEL;
4652
+ const options = {
4653
+ mcpServers: { [MCP_SERVER_NAME]: server },
4654
+ allowedTools: ALLOWED_TOOL_NAMES,
4655
+ disallowedTools: [...DISALLOWED_TOOLS],
4656
+ canUseTool,
4657
+ systemPrompt,
4658
+ model: effectiveModel,
4659
+ fallbackModel: "haiku",
4660
+ thinking: { type: "adaptive" },
4661
+ maxTurns: 100,
4662
+ maxBudgetUsd: DEFAULT_MAX_BUDGET_USD,
4663
+ persistSession: false,
4664
+ includePartialMessages: true,
4665
+ abortController,
4666
+ env: { ...process.env, CLAUDE_AGENT_SDK_CLIENT_APP: "admin-agent" }
4667
+ };
4668
+ if (degraded) {
4669
+ process.stderr.write("\nKinetica Diagnostic Session Ready (DEGRADED MODE)\n");
4670
+ process.stderr.write(
4671
+ "DB engine (port 9191) is unreachable. Only host manager tools are available.\n\n"
4672
+ );
4673
+ await displayDegradedStatus(session2);
4674
+ process.stderr.write("Type 'exit' to end the session.\n\n");
4675
+ } else {
4676
+ process.stderr.write("\nKinetica Diagnostic Session Ready\n");
4677
+ process.stderr.write("Type 'exit' to end the session.\n\n");
4678
+ }
4679
+ const turnGate = createTurnGate();
4680
+ const agentQuery = (0, import_claude_agent_sdk5.query)({
4681
+ prompt: makeInteractivePrompt(abortController, turnGate, spinner),
4682
+ options
4683
+ });
4684
+ process.once("SIGINT", () => {
4685
+ spinner.stop();
4686
+ process.stderr.write("\nInterrupted \u2014 aborting investigation...\n");
4687
+ abortController.abort();
4688
+ agentQuery.close();
4689
+ });
4690
+ let numTurns = 0;
4691
+ let totalCostUsd = 0;
4692
+ let durationMs = 0;
4693
+ let durationApiMs = 0;
4694
+ let lastStreamCharWasNewline = true;
4695
+ const tableAligner = createStreamingTableAligner();
4696
+ let hadNonAbortError = false;
4697
+ try {
4698
+ for await (const message of agentQuery) {
4699
+ if (message.type === "stream_event") {
4700
+ const { event: evt } = message;
4701
+ if (evt.type === "content_block_delta" && evt.delta.type === "text_delta") {
4702
+ const text = evt.delta.text ?? "";
4703
+ if (text) {
4704
+ spinner.stop();
4705
+ const output = tableAligner.push(text);
4706
+ if (output) {
4707
+ process.stderr.write(output);
4708
+ lastStreamCharWasNewline = output.endsWith("\n");
4709
+ }
4710
+ }
4711
+ }
4712
+ } else if (message.type === "assistant") {
4713
+ const assistantMsg = message;
4714
+ const remaining = tableAligner.flush();
4715
+ if (remaining) {
4716
+ process.stderr.write(remaining);
4717
+ lastStreamCharWasNewline = remaining.endsWith("\n");
4718
+ }
4719
+ if (!lastStreamCharWasNewline) {
4720
+ process.stderr.write("\n");
4721
+ lastStreamCharWasNewline = true;
4722
+ }
4723
+ if (assistantMsg.message.stop_reason === "end_turn") {
4724
+ spinner.stop();
4725
+ turnGate.open();
4726
+ } else if (assistantMsg.message.stop_reason === "tool_use") {
4727
+ spinner.start("Investigating");
4728
+ } else {
4729
+ spinner.stop();
4730
+ }
4731
+ if (assistantMsg.error) {
4732
+ spinner.stop();
4733
+ const label = ERROR_LABELS[assistantMsg.error] ?? assistantMsg.error;
4734
+ process.stderr.write(import_picocolors13.default.yellow(`
4735
+ API error: ${label}
4736
+ `));
4737
+ turnGate.open();
4738
+ }
4739
+ } else if (message.type === "result") {
4740
+ spinner.stop();
4741
+ const resultMsg = message;
4742
+ numTurns = resultMsg.num_turns;
4743
+ totalCostUsd = resultMsg.total_cost_usd;
4744
+ durationMs = resultMsg.duration_ms;
4745
+ durationApiMs = resultMsg.duration_api_ms;
4746
+ if (resultMsg.subtype === "error_max_turns") {
4747
+ process.stderr.write(
4748
+ "\nInvestigation hit turn limit. Partial report may be available.\n"
4749
+ );
4750
+ } else if (resultMsg.subtype === "error_max_budget_usd") {
4751
+ process.stderr.write("\nBudget limit reached.\n");
4752
+ } else if (resultMsg.subtype === "error_during_execution") {
4753
+ process.stderr.write(
4754
+ "\nExecution error \u2014 the agent encountered an unrecoverable failure.\n"
4755
+ );
4756
+ } else if (resultMsg.subtype !== "success") {
4757
+ process.stderr.write(`
4758
+ Agent session ended with error: ${resultMsg.subtype}
4759
+ `);
4760
+ }
4761
+ if (resultMsg.permission_denials.length > 0) {
4762
+ const denied = resultMsg.permission_denials.map((d) => d.tool_name).join(", ");
4763
+ process.stderr.write(`
4764
+ Permission denials: ${denied}
4765
+ `);
4766
+ }
4767
+ turnGate.open();
4768
+ } else if (message.type === "system") {
4769
+ const sysMsg = message;
4770
+ if (sysMsg.subtype === "init") {
4771
+ const initMsg = message;
4772
+ const failed = (initMsg.mcp_servers ?? []).filter(
4773
+ (s) => s.name === MCP_SERVER_NAME && s.status !== "connected"
4774
+ );
4775
+ for (const s of failed) {
4776
+ process.stderr.write(
4777
+ `
4778
+ Warning: MCP server "${s.name}" failed to connect (${s.status})
4779
+ `
4780
+ );
4781
+ }
4782
+ } else if (sysMsg.subtype === "api_retry") {
4783
+ const retryMsg = message;
4784
+ const statusStr = retryMsg.error_status !== null ? ` (HTTP ${retryMsg.error_status})` : "";
4785
+ const delaySec = Math.round(retryMsg.retry_delay_ms / 1e3);
4786
+ process.stderr.write(
4787
+ import_picocolors13.default.yellow(
4788
+ `
4789
+ API error${statusStr}. Retrying (attempt ${retryMsg.attempt}/${retryMsg.max_retries}) in ${delaySec}s...
4790
+ `
4791
+ )
4792
+ );
4793
+ } else if (sysMsg.subtype === "compact_boundary") {
4794
+ const compactMsg = message;
4795
+ const preTokens = compactMsg.compact_metadata.pre_tokens;
4796
+ process.stderr.write(
4797
+ `
4798
+ [Context compressed (${preTokens} tokens before compaction) \u2014 investigation continues]
4799
+ `
4800
+ );
4801
+ }
4802
+ } else if (message.type === "rate_limit_event") {
4803
+ const rateMsg = message;
4804
+ const { status, resetsAt } = rateMsg.rate_limit_info;
4805
+ if (status === "rejected") {
4806
+ const resetStr = resetsAt ? ` Resets at ${new Date(resetsAt * 1e3).toISOString()}.` : "";
4807
+ process.stderr.write(`
4808
+ Rate limited \u2014 requests rejected.${resetStr}
4809
+ `);
4810
+ } else if (status === "allowed_warning") {
4811
+ process.stderr.write("\nApproaching rate limit \u2014 investigation may slow.\n");
4812
+ }
4813
+ } else if (message.type === "control_request") {
4814
+ const controlMsg = message;
4815
+ if (controlMsg.request.subtype === "claude_authenticate") {
4816
+ process.stderr.write(import_picocolors13.default.yellow("\nRe-authentication requested by SDK...\n"));
4817
+ }
4818
+ }
4819
+ }
4820
+ } catch (error) {
4821
+ spinner.stop();
4822
+ if (error instanceof import_claude_agent_sdk5.AbortError) {
4823
+ hadNonAbortError = false;
4824
+ } else {
4825
+ hadNonAbortError = true;
4826
+ const message = error instanceof Error ? error.message : String(error);
4827
+ process.stderr.write(import_picocolors13.default.red(`
4828
+ Agent error: ${message}
4829
+ `));
4830
+ }
4831
+ } finally {
4832
+ spinner.stop();
4833
+ const remaining = tableAligner.flush();
4834
+ if (remaining) {
4835
+ process.stderr.write(remaining);
4836
+ }
4837
+ turnGate.open();
4838
+ const durationSec = Math.round(durationMs / 1e3);
4839
+ const apiPct = durationMs > 0 ? Math.round(durationApiMs / durationMs * 100) : 0;
4840
+ const costStr = totalCostUsd > 0 ? ` Cost: $${totalCostUsd.toFixed(4)}.` : "";
4841
+ if (hadNonAbortError) {
4842
+ process.stderr.write(`
4843
+ Session ended due to error. Turns: ${numTurns}.${costStr}
4844
+ `);
4845
+ } else {
4846
+ process.stderr.write(
4847
+ `
4848
+ Session ended. Turns: ${numTurns}. Duration: ${durationSec}s (${apiPct}% API).${costStr}
4849
+ `
4850
+ );
4851
+ }
4852
+ }
4853
+ }
4854
+
4855
+ // src/cli/index.ts
4856
+ var verbose = false;
4857
+ var session;
4858
+ function printHelp() {
4859
+ const lines = [
4860
+ "",
4861
+ " admin-agent",
4862
+ "",
4863
+ " Autonomous diagnostic agent for Kinetica databases",
4864
+ "",
4865
+ " Usage:",
4866
+ " admin-agent [flags]",
4867
+ "",
4868
+ " Flags:",
4869
+ " --help Show this help message",
4870
+ " --version Print version and exit",
4871
+ " --verbose Enable verbose output (stack traces on error)",
4872
+ " --login Force OAuth login (even if ANTHROPIC_API_KEY is set)",
4873
+ " --login-method=TYPE Login method: claudeai (Pro/Max) or console",
4874
+ " --login-org=UUID Target organization UUID for OAuth",
4875
+ " --logout Log out from Anthropic account and exit",
4876
+ ` --model=NAME Override agent model (${SUPPORTED_MODELS.join(" | ")}); default: sonnet`,
4877
+ "",
4878
+ " Environment variables:",
4879
+ " ANTHROPIC_API_KEY Anthropic API key (if not set, OAuth login via browser is used)",
4880
+ " KINETICA_URL Kinetica endpoint URL",
4881
+ " KINETICA_USER Admin username",
4882
+ " KINETICA_PASS Admin password",
4883
+ ""
4884
+ ];
4885
+ process.stdout.write(lines.join("\n") + "\n");
4886
+ }
4887
+ async function main() {
4888
+ const args = process.argv.slice(2);
4889
+ if (args.includes("--verbose")) {
4890
+ verbose = true;
4891
+ }
4892
+ if (args.includes("--version")) {
4893
+ process.stdout.write(getVersion() + "\n");
4894
+ return;
4895
+ }
4896
+ if (args.includes("--help")) {
4897
+ printHelp();
4898
+ return;
4899
+ }
4900
+ if (args.includes("--logout")) {
4901
+ const result = await logout();
4902
+ process.stderr.write(result.message + "\n");
4903
+ process.exitCode = result.success ? 0 : 1;
4904
+ return;
4905
+ }
4906
+ const forceLogin = args.includes("--login");
4907
+ const loginMethodArg = args.find((a) => a.startsWith("--login-method="));
4908
+ const loginMethod = loginMethodArg?.split("=")[1];
4909
+ const loginOrgArg = args.find((a) => a.startsWith("--login-org="));
4910
+ const loginOrgUUID = loginOrgArg?.split("=")[1];
4911
+ const modelArg = args.find((a) => a.startsWith("--model="));
4912
+ const modelValue = modelArg?.split("=")[1];
4913
+ let model;
4914
+ if (modelValue !== void 0) {
4915
+ if (SUPPORTED_MODELS.includes(modelValue)) {
4916
+ model = modelValue;
4917
+ } else {
4918
+ const valid = SUPPORTED_MODELS.join(", ");
4919
+ process.stderr.write(
4920
+ import_picocolors14.default.red(`Error: unknown --model value "${modelValue}". Valid models: ${valid}
4921
+ `)
4922
+ );
4923
+ process.exitCode = 1;
4924
+ return;
4925
+ }
4926
+ }
4927
+ loadEnvFile();
4928
+ const effectiveModel = model ?? DEFAULT_AGENT_MODEL;
4929
+ printBanner(effectiveModel);
4930
+ const authResult = await authenticateAnthropic({ forceLogin, loginMethod, loginOrgUUID });
4931
+ if (authResult.method === "oauth") {
4932
+ const acctInfo = authResult.email ? ` (${authResult.email})` : "";
4933
+ process.stderr.write(import_picocolors14.default.dim(`Authenticated via OAuth${acctInfo}
4934
+ `));
4935
+ } else {
4936
+ process.stderr.write(import_picocolors14.default.dim("Authenticated via API key\n"));
4937
+ }
4938
+ const { session: connectedSession, kineticaVersion, degraded } = await connectWithRetry();
4939
+ session = connectedSession;
4940
+ await runAgent(session, kineticaVersion, degraded, model);
4941
+ }
4942
+ function getSession() {
4943
+ return session;
4944
+ }
4945
+ if (process.env.NODE_ENV !== "test") {
4946
+ main().catch((err) => {
4947
+ const message = err instanceof Error ? err.message : String(err);
4948
+ process.stderr.write(import_picocolors14.default.red(`Error: ${message}
4949
+ `));
4950
+ if (verbose && err instanceof Error && err.stack) {
4951
+ process.stderr.write(err.stack + "\n");
4952
+ }
4953
+ process.exit(1);
4954
+ });
4955
+ }
4956
+ // Annotate the CommonJS export names for ESM import in node:
4957
+ 0 && (module.exports = {
4958
+ getSession,
4959
+ main,
4960
+ verbose
4961
+ });