@kinetica/admin-agent 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/admin-agent.js +1842 -1807
  2. package/package.json +1 -1
@@ -116,1129 +116,596 @@ ${import_picocolors.default.dim(`model: ${model}`)}` : subtitle;
116
116
  return subtitle;
117
117
  }
118
118
 
119
- // src/auth/preflight.ts
120
- var import_claude_agent_sdk = require("@anthropic-ai/claude-agent-sdk");
119
+ // src/cli/select-model.ts
120
+ var import_prompts5 = require("@inquirer/prompts");
121
121
 
122
- // src/auth/oauth-flow.ts
123
- var import_picocolors2 = __toESM(require("picocolors"));
122
+ // src/agent/run-agent.ts
123
+ var import_claude_agent_sdk4 = require("@anthropic-ai/claude-agent-sdk");
124
+ var import_prompts4 = require("@inquirer/prompts");
125
+ var import_picocolors8 = __toESM(require("picocolors"));
124
126
 
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
- }
127
+ // src/agent/diagnostic-sql.ts
128
+ function has(columns, name) {
129
+ return columns.includes(name);
137
130
  }
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
- }
131
+ var FALLBACK_QUERY_HISTORY_SQL = `-- Slow queries in the last hour
132
+ SELECT query_id, user_name, query_text, start_time, stop_time,
133
+ TIMESTAMPDIFF(SECOND, start_time, stop_time) AS elapsed_sec
134
+ FROM ki_catalog.ki_query_history
135
+ WHERE start_time > NOW() - INTERVAL '1' HOUR
136
+ ORDER BY elapsed_sec DESC
137
+ LIMIT 20;`;
138
+ var FALLBACK_ACTIVE_QUERIES_SQL = `-- Currently active queries
139
+ SELECT query_id, user_name, query_text, start_time, execution_status
140
+ FROM ki_catalog.ki_query_active_all
141
+ ORDER BY start_time ASC;`;
142
+ var FALLBACK_TIERED_OBJECTS_SQL = `-- Objects in disk tier (potential memory pressure)
143
+ -- NOTE: id is a string (e.g. @schema@oid[col][0]), NOT a numeric OID. Filter by table: WHERE id LIKE '%table_name%'
144
+ -- For per-table tier placement, prefer kinetica_resource_objects with table_names filter.
145
+ SELECT id, tier, size, source_rank, owner_resource_group
146
+ FROM ki_catalog.ki_tiered_objects
147
+ WHERE tier != 'VRAM'
148
+ ORDER BY size DESC
149
+ LIMIT 20;`;
150
+ var FALLBACK_OBJ_STAT_SQL = `-- Table sizes and row counts
151
+ SELECT object_name, total_bytes, row_count
152
+ FROM ki_catalog.ki_obj_stat
153
+ ORDER BY total_bytes DESC
154
+ LIMIT 30;`;
155
+ var FALLBACK_COLUMNS_SQL = `-- Structural column metadata (use kinetica_show_table for Kinetica-native types)
156
+ SELECT c.table_name, c.column_name, c.column_position
157
+ FROM ki_catalog.ki_columns c
158
+ WHERE c.table_name = '<TABLE_NAME>'
159
+ ORDER BY c.column_position;`;
160
+ var FALLBACK_DATATYPES_SQL = `-- Resolve column_type_oid to human-readable type name
161
+ SELECT oid, name, sql_typename
162
+ FROM ki_catalog.ki_datatypes
163
+ ORDER BY oid;`;
164
+ var FALLBACK_QUERY_SPAN_METRICS_SQL = `-- Query span metrics for a specific query
165
+ SELECT query_id, span_id, parent_span_id, operator, sql_step,
166
+ metric_data, start_time, stop_time, source_rank
167
+ FROM ki_catalog.ki_query_span_metrics_all
168
+ WHERE query_id = '<QUERY_ID>'
169
+ ORDER BY start_time;`;
170
+ var FALLBACK_QUERY_WORKERS_SQL = `-- Active query workers (non-idle)
171
+ SELECT job_id, worker_id, type, status, elapsed_time_ms, source_rank
172
+ FROM ki_catalog.ki_query_workers
173
+ WHERE status != 'IDLE'
174
+ ORDER BY elapsed_time_ms DESC;`;
175
+ var FALLBACK_OBJECTS_SQL = `-- Object registry and metadata
176
+ SELECT oid, object_name, schema_name, type_id, persistence, obj_kind,
177
+ creation_time, last_read_time, read_count, write_count
178
+ FROM ki_catalog.ki_objects
179
+ ORDER BY last_read_time DESC
180
+ LIMIT 30;`;
181
+ var FALLBACK_PARTITIONS_SQL = `-- Partition sizes and tier distribution
182
+ SELECT oid, object_name, schema_name, rank_num, partition_type,
183
+ partition_id, num_rows, actual_bytes, tier
184
+ FROM ki_catalog.ki_partitions
185
+ ORDER BY actual_bytes DESC
186
+ LIMIT 30;`;
187
+ var FALLBACK_INDEXES_SQL = `-- Index definitions
188
+ SELECT oid, object_name, schema_name, index_type, index_columns
189
+ FROM ki_catalog.ki_indexes
190
+ ORDER BY object_name;`;
191
+ var FALLBACK_PERIODIC_OBJECTS_SQL = `-- Periodic refresh schedules
192
+ SELECT oid, object_name, schema_name, last_refresh_time,
193
+ next_refresh_time, additional_info
194
+ FROM ki_catalog.ki_periodic_objects
195
+ ORDER BY next_refresh_time;`;
196
+ var FALLBACK_USERS_AND_ROLES_SQL = `-- Users and roles
197
+ SELECT oid, name, can_login, is_superuser, resource_group
198
+ FROM ki_catalog.ki_users_and_roles
199
+ ORDER BY name;`;
200
+ var FALLBACK_OBJECT_PERMISSIONS_SQL = `-- Object permissions
201
+ SELECT role_name, permission_type, object_type, object_name, with_grant_option
202
+ FROM ki_catalog.ki_object_permissions
203
+ ORDER BY object_name, role_name;`;
204
+ var FALLBACK_DEPEND_SQL = `-- Object dependency graph
205
+ SELECT src_obj_oid, src_obj_kind, dep_obj_oid, dep_obj_kind, mv_oid, dep_kind
206
+ FROM ki_catalog.ki_depend;`;
207
+ var FALLBACK_LOAD_HISTORY_SQL = `-- Recent data load history
208
+ SELECT table_oid, datasource_oid, user_name, load_kind,
209
+ start_time, end_time, rows_inserted, event_message
210
+ FROM ki_catalog.ki_load_history
211
+ WHERE start_time > NOW() - INTERVAL '1' HOUR
212
+ ORDER BY start_time DESC
213
+ LIMIT 20;`;
214
+ var FALLBACK_BACKUP_HISTORY_SQL = `-- Backup history
215
+ SELECT backup_name, operation, status, start_time, end_time,
216
+ num_files, num_bytes, num_records
217
+ FROM ki_catalog.ki_backup_history
218
+ ORDER BY start_time DESC
219
+ LIMIT 20;`;
220
+ var FALLBACK_KAFKA_LAG_INFO_SQL = `-- Kafka consumer lag
221
+ SELECT datasource_oid, table_oid, schema_name, table_name,
222
+ partition_id, highest_offset, last_committed_offset
223
+ FROM ki_catalog.ki_kafka_lag_info
224
+ ORDER BY datasource_oid, partition_id;`;
225
+ function buildQueryHistorySql(columns) {
226
+ const select2 = columns.join(", ");
227
+ const elapsed = has(columns, "start_time") && has(columns, "stop_time") ? ",\n TIMESTAMPDIFF(SECOND, start_time, stop_time) AS elapsed_sec" : "";
228
+ const where = has(columns, "start_time") ? "\nWHERE start_time > NOW() - INTERVAL '1' HOUR" : "";
229
+ const orderBy = has(columns, "start_time") && has(columns, "stop_time") ? (
230
+ // Kinetica does not support timestamp arithmetic in ORDER BY; use the elapsed_sec alias instead
231
+ "\nORDER BY elapsed_sec DESC"
232
+ ) : "";
233
+ return `-- Slow queries in the last hour
234
+ SELECT ${select2}${elapsed}
235
+ FROM ki_catalog.ki_query_history${where}${orderBy}
236
+ LIMIT 20;`;
169
237
  }
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
- }
238
+ function buildActiveQueriesSql(columns) {
239
+ const select2 = columns.join(", ");
240
+ const orderBy = has(columns, "start_time") ? "\nORDER BY start_time ASC" : "";
241
+ return `-- Currently active queries
242
+ SELECT ${select2}
243
+ FROM ki_catalog.ki_query_active_all${orderBy};`;
188
244
  }
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
- }
245
+ function buildTieredObjectsSql(columns) {
246
+ const select2 = columns.join(", ");
247
+ const where = has(columns, "tier") ? "\nWHERE tier != 'VRAM'" : "";
248
+ const orderBy = has(columns, "size") ? "\nORDER BY size DESC" : "";
249
+ 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%'" : "";
250
+ return `-- Objects in disk tier (potential memory pressure)${idNote}
251
+ SELECT ${select2}
252
+ FROM ki_catalog.ki_tiered_objects${where}${orderBy}
253
+ LIMIT 20;`;
235
254
  }
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");
255
+ function buildObjStatSql(columns) {
256
+ const select2 = columns.join(", ");
257
+ const orderBy = has(columns, "total_bytes") ? "\nORDER BY total_bytes DESC" : "";
258
+ return `-- Table sizes and row counts
259
+ SELECT ${select2}
260
+ FROM ki_catalog.ki_obj_stat${orderBy}
261
+ LIMIT 30;`;
246
262
  }
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);
263
+ var STRUCTURAL_COLUMNS = /* @__PURE__ */ new Set([
264
+ "table_name",
265
+ "column_name",
266
+ "column_position",
267
+ "is_nullable",
268
+ "is_shard_key",
269
+ "is_primary_key",
270
+ "is_dict_encoded",
271
+ "default_value",
272
+ "bytes_on_disk_uncompressed",
273
+ "bytes_on_disk_compressed"
274
+ ]);
275
+ function buildColumnsSql(columns) {
276
+ const filtered = columns.filter((c) => STRUCTURAL_COLUMNS.has(c));
277
+ const selectCols = filtered.length > 0 ? filtered : columns;
278
+ const select2 = selectCols.map((c) => `c.${c}`).join(", ");
279
+ const where = has(selectCols, "table_name") ? "\nWHERE c.table_name = '<TABLE_NAME>'" : "";
280
+ const orderBy = has(selectCols, "column_position") ? "\nORDER BY c.column_position" : has(selectCols, "column_name") ? "\nORDER BY c.column_name" : "";
281
+ return `-- Structural column metadata (use kinetica_show_table for Kinetica-native types)
282
+ SELECT ${select2}
283
+ FROM ki_catalog.ki_columns c${where}${orderBy};`;
286
284
  }
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}"`;
285
+ function buildDatatypesSql(columns) {
286
+ const select2 = columns.join(", ");
287
+ const orderBy = has(columns, "oid") ? "\nORDER BY oid" : "";
288
+ return `-- Resolve column_type_oid to human-readable type name
289
+ SELECT ${select2}
290
+ FROM ki_catalog.ki_datatypes${orderBy};`;
300
291
  }
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");
292
+ function buildQuerySpanMetricsSql(columns) {
293
+ const select2 = columns.join(", ");
294
+ const where = has(columns, "query_id") ? "\nWHERE query_id = '<QUERY_ID>'" : "";
295
+ const orderBy = has(columns, "start_time") ? "\nORDER BY start_time" : "";
296
+ return `-- Query span metrics for a specific query
297
+ SELECT ${select2}
298
+ FROM ki_catalog.ki_query_span_metrics_all${where}${orderBy};`;
333
299
  }
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
- }
300
+ function buildQueryWorkersSql(columns) {
301
+ const select2 = columns.join(", ");
302
+ const where = has(columns, "status") ? "\nWHERE status != 'IDLE'" : "";
303
+ const orderBy = has(columns, "elapsed_time_ms") ? "\nORDER BY elapsed_time_ms DESC" : "";
304
+ return `-- Active query workers (non-idle)
305
+ SELECT ${select2}
306
+ FROM ki_catalog.ki_query_workers${where}${orderBy};`;
346
307
  }
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
- }
308
+ function buildObjectsSql(columns) {
309
+ const select2 = columns.join(", ");
310
+ const orderBy = has(columns, "last_read_time") ? "\nORDER BY last_read_time DESC" : has(columns, "creation_time") ? "\nORDER BY creation_time DESC" : "";
311
+ return `-- Object registry and metadata
312
+ SELECT ${select2}
313
+ FROM ki_catalog.ki_objects${orderBy}
314
+ LIMIT 30;`;
368
315
  }
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 };
316
+ function buildPartitionsSql(columns) {
317
+ const select2 = columns.join(", ");
318
+ const orderBy = has(columns, "actual_bytes") ? "\nORDER BY actual_bytes DESC" : "";
319
+ return `-- Partition sizes and tier distribution
320
+ SELECT ${select2}
321
+ FROM ki_catalog.ki_partitions${orderBy}
322
+ LIMIT 30;`;
400
323
  }
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 };
324
+ function buildIndexesSql(columns) {
325
+ const select2 = columns.join(", ");
326
+ const orderBy = has(columns, "object_name") ? "\nORDER BY object_name" : "";
327
+ return `-- Index definitions
328
+ SELECT ${select2}
329
+ FROM ki_catalog.ki_indexes${orderBy};`;
405
330
  }
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;
331
+ function buildPeriodicObjectsSql(columns) {
332
+ const select2 = columns.join(", ");
333
+ const orderBy = has(columns, "next_refresh_time") ? "\nORDER BY next_refresh_time" : "";
334
+ return `-- Periodic refresh schedules
335
+ SELECT ${select2}
336
+ FROM ki_catalog.ki_periodic_objects${orderBy};`;
413
337
  }
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
- };
338
+ function buildUsersAndRolesSql(columns) {
339
+ const select2 = columns.join(", ");
340
+ const orderBy = has(columns, "name") ? "\nORDER BY name" : "";
341
+ return `-- Users and roles
342
+ SELECT ${select2}
343
+ FROM ki_catalog.ki_users_and_roles${orderBy};`;
435
344
  }
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);
345
+ function buildObjectPermissionsSql(columns) {
346
+ const select2 = columns.join(", ");
347
+ const orderBy = has(columns, "object_name") && has(columns, "role_name") ? "\nORDER BY object_name, role_name" : has(columns, "object_name") ? "\nORDER BY object_name" : "";
348
+ return `-- Object permissions
349
+ SELECT ${select2}
350
+ FROM ki_catalog.ki_object_permissions${orderBy};`;
445
351
  }
446
- function stripTrailingSlashes(url) {
447
- return url.replace(/\/+$/, "");
352
+ function buildDependSql(columns) {
353
+ const select2 = columns.join(", ");
354
+ return `-- Object dependency graph
355
+ SELECT ${select2}
356
+ FROM ki_catalog.ki_depend;`;
448
357
  }
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
- }
358
+ function buildLoadHistorySql(columns) {
359
+ const select2 = columns.join(", ");
360
+ const where = has(columns, "start_time") ? "\nWHERE start_time > NOW() - INTERVAL '1' HOUR" : "";
361
+ const orderBy = has(columns, "start_time") ? "\nORDER BY start_time DESC" : "";
362
+ return `-- Recent data load history
363
+ SELECT ${select2}
364
+ FROM ki_catalog.ki_load_history${where}${orderBy}
365
+ LIMIT 20;`;
459
366
  }
460
- function isHttpsOnly() {
461
- return process.env.KINETICA_HTTPS_ONLY === "1";
367
+ function buildBackupHistorySql(columns) {
368
+ const select2 = columns.join(", ");
369
+ const orderBy = has(columns, "start_time") ? "\nORDER BY start_time DESC" : "";
370
+ return `-- Backup history
371
+ SELECT ${select2}
372
+ FROM ki_catalog.ki_backup_history${orderBy}
373
+ LIMIT 20;`;
462
374
  }
463
- function isInteractive() {
464
- return Boolean(process.stdin.isTTY);
375
+ function buildKafkaLagInfoSql(columns) {
376
+ const select2 = columns.join(", ");
377
+ const orderBy = has(columns, "datasource_oid") && has(columns, "partition_id") ? "\nORDER BY datasource_oid, partition_id" : has(columns, "datasource_oid") ? "\nORDER BY datasource_oid" : "";
378
+ return `-- Kafka consumer lag
379
+ SELECT ${select2}
380
+ FROM ki_catalog.ki_kafka_lag_info${orderBy};`;
465
381
  }
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;
382
+ var BUILDER_REGISTRY = [
383
+ // Query History and Performance
384
+ {
385
+ table: "ki_query_history",
386
+ section: "Query History and Performance",
387
+ build: buildQueryHistorySql,
388
+ fallback: FALLBACK_QUERY_HISTORY_SQL
389
+ },
390
+ {
391
+ table: "ki_query_active_all",
392
+ section: "Query History and Performance",
393
+ build: buildActiveQueriesSql,
394
+ fallback: FALLBACK_ACTIVE_QUERIES_SQL
395
+ },
396
+ {
397
+ table: "ki_query_span_metrics_all",
398
+ section: "Query History and Performance",
399
+ build: buildQuerySpanMetricsSql,
400
+ fallback: FALLBACK_QUERY_SPAN_METRICS_SQL
401
+ },
402
+ {
403
+ table: "ki_query_workers",
404
+ section: "Query History and Performance",
405
+ build: buildQueryWorkersSql,
406
+ fallback: FALLBACK_QUERY_WORKERS_SQL
407
+ },
408
+ // Memory and Storage Tiers
409
+ {
410
+ table: "ki_tiered_objects",
411
+ section: "Memory and Storage Tiers",
412
+ build: buildTieredObjectsSql,
413
+ fallback: FALLBACK_TIERED_OBJECTS_SQL
414
+ },
415
+ {
416
+ table: "ki_obj_stat",
417
+ section: "Memory and Storage Tiers",
418
+ build: buildObjStatSql,
419
+ fallback: FALLBACK_OBJ_STAT_SQL
420
+ },
421
+ {
422
+ table: "ki_partitions",
423
+ section: "Memory and Storage Tiers",
424
+ build: buildPartitionsSql,
425
+ fallback: FALLBACK_PARTITIONS_SQL
426
+ },
427
+ // Object Registry and Metadata
428
+ {
429
+ table: "ki_objects",
430
+ section: "Object Registry and Metadata",
431
+ build: buildObjectsSql,
432
+ fallback: FALLBACK_OBJECTS_SQL
433
+ },
434
+ {
435
+ table: "ki_indexes",
436
+ section: "Object Registry and Metadata",
437
+ build: buildIndexesSql,
438
+ fallback: FALLBACK_INDEXES_SQL
439
+ },
440
+ {
441
+ table: "ki_periodic_objects",
442
+ section: "Object Registry and Metadata",
443
+ build: buildPeriodicObjectsSql,
444
+ fallback: FALLBACK_PERIODIC_OBJECTS_SQL
445
+ },
446
+ {
447
+ table: "ki_depend",
448
+ section: "Object Registry and Metadata",
449
+ build: buildDependSql,
450
+ fallback: FALLBACK_DEPEND_SQL
451
+ },
452
+ // Security and Access Control
453
+ {
454
+ table: "ki_users_and_roles",
455
+ section: "Security and Access Control",
456
+ build: buildUsersAndRolesSql,
457
+ fallback: FALLBACK_USERS_AND_ROLES_SQL
458
+ },
459
+ {
460
+ table: "ki_object_permissions",
461
+ section: "Security and Access Control",
462
+ build: buildObjectPermissionsSql,
463
+ fallback: FALLBACK_OBJECT_PERMISSIONS_SQL
464
+ },
465
+ // Data Ingestion and Operations
466
+ {
467
+ table: "ki_load_history",
468
+ section: "Data Ingestion and Operations",
469
+ build: buildLoadHistorySql,
470
+ fallback: FALLBACK_LOAD_HISTORY_SQL
471
+ },
472
+ {
473
+ table: "ki_backup_history",
474
+ section: "Data Ingestion and Operations",
475
+ build: buildBackupHistorySql,
476
+ fallback: FALLBACK_BACKUP_HISTORY_SQL
477
+ },
478
+ {
479
+ table: "ki_kafka_lag_info",
480
+ section: "Data Ingestion and Operations",
481
+ build: buildKafkaLagInfoSql,
482
+ fallback: FALLBACK_KAFKA_LAG_INFO_SQL
483
+ },
484
+ // Schema Inspection
485
+ {
486
+ table: "ki_columns",
487
+ section: "Schema Inspection",
488
+ build: buildColumnsSql,
489
+ fallback: FALLBACK_COLUMNS_SQL
490
+ },
491
+ {
492
+ table: "ki_datatypes",
493
+ section: "Schema Inspection",
494
+ build: buildDatatypesSql,
495
+ fallback: FALLBACK_DATATYPES_SQL
487
496
  }
497
+ ];
498
+
499
+ // src/tools/index.ts
500
+ var import_claude_agent_sdk2 = require("@anthropic-ai/claude-agent-sdk");
501
+ var import_zod16 = require("zod");
502
+ var import_picocolors4 = __toESM(require("picocolors"));
503
+
504
+ // src/approval/registry.ts
505
+ var DEFAULT_READ_ONLY_TOOLS = /* @__PURE__ */ new Set();
506
+ function createRegistry(tools = DEFAULT_READ_ONLY_TOOLS) {
507
+ return {
508
+ isReadOnlyTool: (toolName) => tools.has(toolName),
509
+ // Returns a NEW registry — never mutates the current one
510
+ registerReadOnlyTool: (toolName) => createRegistry(/* @__PURE__ */ new Set([...tools, toolName])),
511
+ tools
512
+ };
488
513
  }
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
- };
514
+ var defaultRegistry = createRegistry();
515
+ var isReadOnlyTool = defaultRegistry.isReadOnlyTool;
516
+ var READ_ONLY_TOOLS = defaultRegistry.tools;
517
+
518
+ // src/output/stringify.ts
519
+ function stringifyValue(v) {
520
+ if (v === void 0 || v === null) return "";
521
+ if (typeof v === "string") return v;
522
+ if (typeof v === "number" || typeof v === "boolean" || typeof v === "bigint") {
523
+ return v.toString();
535
524
  }
536
- return { ok: true, url: httpUrl };
525
+ if (typeof v === "object") return JSON.stringify(v);
526
+ if (typeof v === "symbol") return v.toString();
527
+ return "";
537
528
  }
538
529
 
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;
530
+ // src/output/format.ts
531
+ function formatOutput(json) {
532
+ if (json === null || json === void 0) {
533
+ return "(empty)";
554
534
  }
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;
535
+ if (Array.isArray(json)) {
536
+ return formatArray(json);
537
+ }
538
+ if (typeof json === "object") {
539
+ return formatObject(json);
562
540
  }
541
+ return stringifyValue(json);
563
542
  }
564
- async function probeHostManager(session2) {
565
- if (!session2.makeRequestToPort) {
566
- return { ok: false };
543
+ function formatArray(arr) {
544
+ if (arr.length === 0) {
545
+ return "(no results)";
567
546
  }
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 };
547
+ const first = arr[0];
548
+ if (typeof first === "object" && first !== null && !Array.isArray(first)) {
549
+ return formatTableArray(arr);
575
550
  }
551
+ return arr.map(stringifyValue).join("\n");
576
552
  }
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}`);
553
+ function formatTableArray(rows) {
554
+ const headers = Object.keys(rows[0]);
555
+ const cells = rows.map((row) => headers.map((h) => stringifyValue(row[h])));
556
+ const colWidths = headers.map(
557
+ (h, i) => Math.max(h.length, ...cells.map((row) => row[i].length), 3)
558
+ );
559
+ const pad = (text, col) => text.padEnd(colWidths[col]);
560
+ const headerRow = `| ${headers.map((h, i) => pad(h, i)).join(" | ")} |`;
561
+ const separatorRow = `| ${colWidths.map((w) => "-".repeat(w)).join(" | ")} |`;
562
+ const dataRows = cells.map((row) => `| ${row.map((cell, i) => pad(cell, i)).join(" | ")} |`);
563
+ return [headerRow, separatorRow, ...dataRows].join("\n");
564
+ }
565
+ function formatObject(obj) {
566
+ return Object.entries(obj).map(([key, value]) => {
567
+ if (typeof value === "object" && value !== null) {
568
+ return `**${key}:**
569
+ ${formatOutput(value)}`;
570
+ }
571
+ return `**${key}:** ${stringifyValue(value)}`;
572
+ }).join("\n");
573
+ }
574
+
575
+ // src/output/format-tool-name.ts
576
+ function formatToolName(toolName) {
577
+ const stripped = toolName.replace(/^mcp__[^_]+__/, "").replace(/^kinetica_/, "");
578
+ return stripped.replace(/_/g, " ");
579
+ }
580
+
581
+ // src/types/index.ts
582
+ var DEFAULT_TRUNCATION = {
583
+ headLines: 150,
584
+ tailLines: 50
585
+ };
586
+
587
+ // src/output/truncate.ts
588
+ function truncateOutput(text, options = DEFAULT_TRUNCATION) {
589
+ if (text === "") return "";
590
+ const lines = text.split("\n");
591
+ const { headLines, tailLines } = options;
592
+ const threshold = headLines + tailLines;
593
+ if (lines.length <= threshold) {
594
+ return text;
582
595
  }
583
- const body = await response.text();
584
- return extractVersion(body);
596
+ const truncatedCount = lines.length - headLines - tailLines;
597
+ const head = lines.slice(0, headLines);
598
+ const tail = lines.slice(lines.length - tailLines);
599
+ return [...head, "", `[... ${truncatedCount} lines truncated ...]`, "", ...tail].join("\n");
585
600
  }
586
- function isCredentialError(errorMessage) {
587
- return errorMessage.startsWith("HTTP 401") || errorMessage.startsWith("HTTP 403");
601
+
602
+ // src/output/reshape.ts
603
+ function safeString(v) {
604
+ if (typeof v === "object" && v !== null) {
605
+ return JSON.stringify(v);
606
+ }
607
+ return String(v);
588
608
  }
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);
609
+ function flatObjectToRows(obj, keyLabel = "key", valueLabel = "value") {
610
+ return Object.entries(obj).map(([k, v]) => ({
611
+ [keyLabel]: k,
612
+ [valueLabel]: safeString(v)
613
+ }));
614
+ }
615
+
616
+ // src/tools/rest/parse-data-str.ts
617
+ function parseDataStr(outerDataStr, raw) {
618
+ if (typeof outerDataStr !== "string") {
619
+ return { ok: true, data: void 0 };
595
620
  }
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
- }
621
+ try {
622
+ return { ok: true, data: JSON.parse(outerDataStr) };
623
+ } catch (err) {
624
+ const message = err instanceof Error ? err.message : String(err);
625
+ return { ok: false, status: 200, error: `data_str parse error: ${message}`, raw };
626
+ }
627
+ }
628
+
629
+ // src/tools/rest/health.ts
630
+ async function healthCheck(session2) {
631
+ let response;
632
+ let rawText;
633
+ try {
634
+ response = await session2.makeRequest("/show/system/status", {});
635
+ rawText = await response.text();
636
+ } catch (err) {
637
+ const message = err instanceof Error ? err.message : String(err);
638
+ return {
639
+ ok: false,
640
+ status: 0,
641
+ error: message,
642
+ raw: ""
643
+ };
644
+ }
645
+ if (!response.ok) {
646
+ return {
647
+ ok: false,
648
+ status: response.status,
649
+ error: `HTTP ${response.status}`,
650
+ raw: rawText
651
+ };
652
+ }
653
+ let parsed;
654
+ try {
655
+ parsed = JSON.parse(rawText);
656
+ } catch (err) {
657
+ const message = err instanceof Error ? err.message : String(err);
658
+ return {
659
+ ok: false,
660
+ status: 200,
661
+ error: `JSON parse error: ${message}`,
662
+ raw: rawText
663
+ };
664
+ }
665
+ const inner = parseDataStr(parsed.data_str, rawText);
666
+ if (!inner.ok) return inner;
667
+ const statusMap = inner.data?.status_map ?? {};
668
+ return {
669
+ ok: true,
670
+ data: flatObjectToRows(statusMap, "component", "status")
671
+ };
672
+ }
673
+
674
+ // src/tools/rest/decode-nested-json.ts
675
+ function decodeNestedJsonStrings(obj) {
676
+ return Object.fromEntries(
677
+ Object.entries(obj).map(([k, v]) => {
678
+ if (typeof v !== "string") return [k, v];
679
+ try {
680
+ const parsed = JSON.parse(v);
681
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
682
+ return [k, decodeNestedJsonStrings(parsed)];
629
683
  }
630
- console.error(import_picocolors6.default.red("Authentication failed. Exiting."));
631
- process.exit(1);
684
+ return [k, parsed];
685
+ } catch {
686
+ return [k, v];
632
687
  }
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);
688
+ })
689
+ );
690
+ }
691
+
692
+ // src/tools/rest/flatten-rank-stats.ts
693
+ function findTier(tiers, prefix) {
694
+ for (const [name, data] of Object.entries(tiers)) {
695
+ if (name === prefix || name.startsWith(`${prefix}.`) || name.startsWith(`${prefix}_`) || name.startsWith(prefix) && /^\d/.test(name.slice(prefix.length))) {
696
+ if (typeof data === "object" && data !== null && !Array.isArray(data)) {
697
+ return data;
649
698
  }
650
699
  }
651
700
  }
652
- throw new Error("unreachable");
701
+ return void 0;
653
702
  }
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);
703
+ function overallField(tier, field) {
704
+ if (!tier) return "";
705
+ const overall = tier.overall;
706
+ if (typeof overall !== "object" || overall === null) return "";
707
+ const value = overall[field];
708
+ return stringifyValue(value);
1242
709
  }
1243
710
  function computePercent(used, limit) {
1244
711
  if (!used || !limit) return "";
@@ -1448,15 +915,15 @@ function applyFilters(propertyMap, input5) {
1448
915
  }
1449
916
 
1450
917
  // src/tools/rest/discover-hm-port.ts
1451
- var DEFAULT_HM_PORT2 = 9300;
918
+ var DEFAULT_HM_PORT = 9300;
1452
919
  async function discoverHmPort(session2) {
1453
920
  const result = await getSystemProperties(session2, { key_pattern: "hm_http_port" });
1454
- if (!result.ok) return DEFAULT_HM_PORT2;
921
+ if (!result.ok) return DEFAULT_HM_PORT;
1455
922
  const rows = result.data;
1456
923
  const entry = rows.find((r) => r.property?.includes("hm_http_port"));
1457
- if (!entry?.value) return DEFAULT_HM_PORT2;
924
+ if (!entry?.value) return DEFAULT_HM_PORT;
1458
925
  const port = parseInt(entry.value, 10);
1459
- return Number.isFinite(port) ? port : DEFAULT_HM_PORT2;
926
+ return Number.isFinite(port) ? port : DEFAULT_HM_PORT;
1460
927
  }
1461
928
 
1462
929
  // src/tools/rest/cluster.ts
@@ -3055,28 +2522,28 @@ async function alterConfiguration(session2, input5) {
3055
2522
 
3056
2523
  // src/tools/mutation/alter-table-columns.ts
3057
2524
  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");
2525
+ var import_prompts2 = require("@inquirer/prompts");
2526
+ var import_picocolors3 = __toESM(require("picocolors"));
2527
+ var import_claude_agent_sdk = require("@anthropic-ai/claude-agent-sdk");
3061
2528
 
3062
2529
  // 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));
2530
+ var import_prompts = require("@inquirer/prompts");
2531
+ var import_picocolors2 = __toESM(require("picocolors"));
2532
+ var DIVIDER = import_picocolors2.default.dim("\u2500".repeat(60));
3066
2533
  function renderChecklist(header, summary, items) {
3067
2534
  const lines = [
3068
2535
  "",
3069
2536
  DIVIDER,
3070
- ` ${import_picocolors7.default.bold(import_picocolors7.default.yellow(header))}`,
2537
+ ` ${import_picocolors2.default.bold(import_picocolors2.default.yellow(header))}`,
3071
2538
  "",
3072
- ` ${import_picocolors7.default.dim("Summary:")} ${summary}`,
2539
+ ` ${import_picocolors2.default.dim("Summary:")} ${summary}`,
3073
2540
  "",
3074
- ` ${import_picocolors7.default.bold(`${items.length} proposed column change(s):`)}`,
2541
+ ` ${import_picocolors2.default.bold(`${items.length} proposed column change(s):`)}`,
3075
2542
  ""
3076
2543
  ];
3077
2544
  for (let i = 0; i < items.length; i++) {
3078
2545
  const item = items[i];
3079
- lines.push(` ${import_picocolors7.default.bold(`${i + 1}.`)} ${item.label}`, ` ${import_picocolors7.default.dim(item.description)}`);
2546
+ lines.push(` ${import_picocolors2.default.bold(`${i + 1}.`)} ${item.label}`, ` ${import_picocolors2.default.dim(item.description)}`);
3080
2547
  }
3081
2548
  lines.push("", DIVIDER, "");
3082
2549
  return lines.join("\n");
@@ -3085,7 +2552,7 @@ async function showChecklist(header, summary, items) {
3085
2552
  const panel = renderChecklist(header, summary, items);
3086
2553
  process.stderr.write(panel);
3087
2554
  try {
3088
- const selected = await (0, import_prompts5.checkbox)({
2555
+ const selected = await (0, import_prompts.checkbox)({
3089
2556
  message: "Select columns to alter (space to toggle, enter to confirm):",
3090
2557
  choices: items.map((item, i) => ({
3091
2558
  value: i,
@@ -3120,12 +2587,12 @@ function buildAlterTableSql(tableName, columns) {
3120
2587
  return `ALTER TABLE ${tableName}
3121
2588
  ${clauses.join(",\n")}`;
3122
2589
  }
3123
- var SQL_DIVIDER = import_picocolors8.default.dim("\u2500".repeat(60));
2590
+ var SQL_DIVIDER = import_picocolors3.default.dim("\u2500".repeat(60));
3124
2591
  async function confirmSqlExecution(sql) {
3125
2592
  const panel = [
3126
2593
  "",
3127
2594
  SQL_DIVIDER,
3128
- ` ${import_picocolors8.default.bold(import_picocolors8.default.yellow("Generated SQL:"))}`,
2595
+ ` ${import_picocolors3.default.bold(import_picocolors3.default.yellow("Generated SQL:"))}`,
3129
2596
  "",
3130
2597
  sql.split("\n").map((line) => ` ${line}`).join("\n"),
3131
2598
  "",
@@ -3134,7 +2601,7 @@ async function confirmSqlExecution(sql) {
3134
2601
  ].join("\n");
3135
2602
  process.stderr.write(panel);
3136
2603
  try {
3137
- const response = await (0, import_prompts6.input)({ message: "Execute? (y/n):" });
2604
+ const response = await (0, import_prompts2.input)({ message: "Execute? (y/n):" });
3138
2605
  return response.trim().toLowerCase() === "y";
3139
2606
  } catch {
3140
2607
  return false;
@@ -3200,7 +2667,7 @@ async function alterTableColumns(session2, toolInput) {
3200
2667
  };
3201
2668
  }
3202
2669
  function makeAlterTableColumnsTool(session2, deps) {
3203
- return (0, import_claude_agent_sdk2.tool)(
2670
+ return (0, import_claude_agent_sdk.tool)(
3204
2671
  "kinetica_alter_table_columns",
3205
2672
  "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
2673
  AlterTableColumnsSchema.shape,
@@ -3313,19 +2780,19 @@ function applyOutputPipeline(result) {
3313
2780
  return truncateOutput(formatOutput(payload));
3314
2781
  }
3315
2782
  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"));
2783
+ const statusLabel = result.ok ? import_picocolors4.default.bold(import_picocolors4.default.green("EXECUTED")) : import_picocolors4.default.bold(import_picocolors4.default.red("FAILED"));
3317
2784
  const redacted = redactAuditInput(input5);
3318
2785
  const inputSummary = Object.entries(redacted).map(([k, v]) => `${k}=${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
3319
2786
  const displayName = formatToolName(toolName);
3320
2787
  process.stderr.write(
3321
- ` ${import_picocolors9.default.dim("MUTATION")} ${statusLabel} ${displayName}
3322
- ${import_picocolors9.default.dim(inputSummary)}
2788
+ ` ${import_picocolors4.default.dim("MUTATION")} ${statusLabel} ${displayName}
2789
+ ${import_picocolors4.default.dim(inputSummary)}
3323
2790
 
3324
2791
  `
3325
2792
  );
3326
2793
  }
3327
2794
  function makeAlterSystemPropertiesTool(session2) {
3328
- return (0, import_claude_agent_sdk3.tool)(
2795
+ return (0, import_claude_agent_sdk2.tool)(
3329
2796
  "kinetica_alter_system_properties",
3330
2797
  "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
2798
  AlterSystemPropertiesSchema.shape,
@@ -3339,7 +2806,7 @@ function makeAlterSystemPropertiesTool(session2) {
3339
2806
  );
3340
2807
  }
3341
2808
  function makeExecuteMutationSqlTool(session2) {
3342
- return (0, import_claude_agent_sdk3.tool)(
2809
+ return (0, import_claude_agent_sdk2.tool)(
3343
2810
  "kinetica_execute_mutation_sql",
3344
2811
  "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
2812
  ExecuteMutationSqlSchema.shape,
@@ -3353,7 +2820,7 @@ function makeExecuteMutationSqlTool(session2) {
3353
2820
  );
3354
2821
  }
3355
2822
  function makeAdminRebalanceTool(session2) {
3356
- return (0, import_claude_agent_sdk3.tool)(
2823
+ return (0, import_claude_agent_sdk2.tool)(
3357
2824
  "kinetica_admin_rebalance",
3358
2825
  "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
2826
  AdminRebalanceSchema.shape,
@@ -3367,7 +2834,7 @@ function makeAdminRebalanceTool(session2) {
3367
2834
  );
3368
2835
  }
3369
2836
  function makeAlterConfigurationTool(session2) {
3370
- return (0, import_claude_agent_sdk3.tool)(
2837
+ return (0, import_claude_agent_sdk2.tool)(
3371
2838
  "kinetica_alter_configuration",
3372
2839
  "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
2840
  AlterConfigurationSchema.shape,
@@ -3381,7 +2848,7 @@ function makeAlterConfigurationTool(session2) {
3381
2848
  );
3382
2849
  }
3383
2850
  function makeHealthCheckTool(session2) {
3384
- return (0, import_claude_agent_sdk3.tool)(
2851
+ return (0, import_claude_agent_sdk2.tool)(
3385
2852
  "kinetica_health_check",
3386
2853
  "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
2854
  {},
@@ -3393,7 +2860,7 @@ function makeHealthCheckTool(session2) {
3393
2860
  );
3394
2861
  }
3395
2862
  function makeGetMetricsTool(session2) {
3396
- return (0, import_claude_agent_sdk3.tool)(
2863
+ return (0, import_claude_agent_sdk2.tool)(
3397
2864
  "kinetica_get_metrics",
3398
2865
  "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
2866
  { node_id: import_zod16.z.string().optional() },
@@ -3405,7 +2872,7 @@ function makeGetMetricsTool(session2) {
3405
2872
  );
3406
2873
  }
3407
2874
  function makeClusterStatusTool(session2) {
3408
- return (0, import_claude_agent_sdk3.tool)(
2875
+ return (0, import_claude_agent_sdk2.tool)(
3409
2876
  "kinetica_cluster_status",
3410
2877
  "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
2878
  {},
@@ -3417,7 +2884,7 @@ function makeClusterStatusTool(session2) {
3417
2884
  );
3418
2885
  }
3419
2886
  function makeNodeDetailsTool(session2) {
3420
- return (0, import_claude_agent_sdk3.tool)(
2887
+ return (0, import_claude_agent_sdk2.tool)(
3421
2888
  "kinetica_node_details",
3422
2889
  "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
2890
  { node_id: import_zod16.z.string().optional() },
@@ -3429,7 +2896,7 @@ function makeNodeDetailsTool(session2) {
3429
2896
  );
3430
2897
  }
3431
2898
  function makeGetLogsTool(session2) {
3432
- return (0, import_claude_agent_sdk3.tool)(
2899
+ return (0, import_claude_agent_sdk2.tool)(
3433
2900
  "kinetica_get_logs",
3434
2901
  "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
2902
  GetLogsSchema.shape,
@@ -3442,7 +2909,7 @@ function makeGetLogsTool(session2) {
3442
2909
  );
3443
2910
  }
3444
2911
  function makeShowConfigurationTool(session2) {
3445
- return (0, import_claude_agent_sdk3.tool)(
2912
+ return (0, import_claude_agent_sdk2.tool)(
3446
2913
  "kinetica_show_configuration",
3447
2914
  "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
2915
  ShowConfigurationSchema.shape,
@@ -3455,7 +2922,7 @@ function makeShowConfigurationTool(session2) {
3455
2922
  );
3456
2923
  }
3457
2924
  function makeGetSystemPropertiesTool(session2) {
3458
- return (0, import_claude_agent_sdk3.tool)(
2925
+ return (0, import_claude_agent_sdk2.tool)(
3459
2926
  "kinetica_get_system_properties",
3460
2927
  "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
2928
  GetSystemPropertiesSchema.shape,
@@ -3468,7 +2935,7 @@ function makeGetSystemPropertiesTool(session2) {
3468
2935
  );
3469
2936
  }
3470
2937
  function makeExecuteSqlTool(session2, catalogSchemas) {
3471
- return (0, import_claude_agent_sdk3.tool)(
2938
+ return (0, import_claude_agent_sdk2.tool)(
3472
2939
  "kinetica_execute_sql",
3473
2940
  "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
2941
  ExecuteSqlSchema.shape,
@@ -3485,7 +2952,7 @@ function makeExecuteSqlTool(session2, catalogSchemas) {
3485
2952
  );
3486
2953
  }
3487
2954
  function makeExplainQueryTool(session2) {
3488
- return (0, import_claude_agent_sdk3.tool)(
2955
+ return (0, import_claude_agent_sdk2.tool)(
3489
2956
  "kinetica_explain_query",
3490
2957
  "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
2958
  ExplainQuerySchema.shape,
@@ -3497,7 +2964,7 @@ function makeExplainQueryTool(session2) {
3497
2964
  );
3498
2965
  }
3499
2966
  function makeSystemTimingTool(session2) {
3500
- return (0, import_claude_agent_sdk3.tool)(
2967
+ return (0, import_claude_agent_sdk2.tool)(
3501
2968
  "kinetica_system_timing",
3502
2969
  "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
2970
  {},
@@ -3509,7 +2976,7 @@ function makeSystemTimingTool(session2) {
3509
2976
  );
3510
2977
  }
3511
2978
  function makeResourceGroupsTool(session2) {
3512
- return (0, import_claude_agent_sdk3.tool)(
2979
+ return (0, import_claude_agent_sdk2.tool)(
3513
2980
  "kinetica_resource_groups",
3514
2981
  "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
2982
  ResourceGroupsSchema.shape,
@@ -3522,7 +2989,7 @@ function makeResourceGroupsTool(session2) {
3522
2989
  );
3523
2990
  }
3524
2991
  function makeVerifyDbTool(session2) {
3525
- return (0, import_claude_agent_sdk3.tool)(
2992
+ return (0, import_claude_agent_sdk2.tool)(
3526
2993
  "kinetica_verify_db",
3527
2994
  "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
2995
  VerifyDbSchema.shape,
@@ -3535,7 +3002,7 @@ function makeVerifyDbTool(session2) {
3535
3002
  );
3536
3003
  }
3537
3004
  function makeShowSecurityTool(session2) {
3538
- return (0, import_claude_agent_sdk3.tool)(
3005
+ return (0, import_claude_agent_sdk2.tool)(
3539
3006
  "kinetica_show_security",
3540
3007
  "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
3008
  ShowSecuritySchema.shape,
@@ -3548,7 +3015,7 @@ function makeShowSecurityTool(session2) {
3548
3015
  );
3549
3016
  }
3550
3017
  function makeShowTableTool(session2) {
3551
- return (0, import_claude_agent_sdk3.tool)(
3018
+ return (0, import_claude_agent_sdk2.tool)(
3552
3019
  "kinetica_show_table",
3553
3020
  "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
3021
  ShowTableSchema.shape,
@@ -3561,7 +3028,7 @@ function makeShowTableTool(session2) {
3561
3028
  );
3562
3029
  }
3563
3030
  function makeResourceObjectsTool(session2) {
3564
- return (0, import_claude_agent_sdk3.tool)(
3031
+ return (0, import_claude_agent_sdk2.tool)(
3565
3032
  "kinetica_resource_objects",
3566
3033
  `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
3034
  ResourceObjectsSchema.shape,
@@ -3574,7 +3041,7 @@ function makeResourceObjectsTool(session2) {
3574
3041
  );
3575
3042
  }
3576
3043
  function makeHostManagerStatusTool(session2) {
3577
- return (0, import_claude_agent_sdk3.tool)(
3044
+ return (0, import_claude_agent_sdk2.tool)(
3578
3045
  "kinetica_host_manager_status",
3579
3046
  "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
3047
  {},
@@ -3726,17 +3193,17 @@ function buildEvidenceChecklist() {
3726
3193
 
3727
3194
  // src/agent/report-template.ts
3728
3195
  var import_node_fs2 = require("fs");
3729
- var import_node_path3 = require("path");
3196
+ var import_node_path2 = require("path");
3730
3197
 
3731
3198
  // src/agent/load-playbooks.ts
3732
- var import_promises2 = require("fs/promises");
3733
- var import_node_path2 = require("path");
3199
+ var import_promises = require("fs/promises");
3200
+ var import_node_path = require("path");
3734
3201
  var import_node_fs = require("fs");
3735
3202
  function findPackageRoot(startDir) {
3736
3203
  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);
3204
+ while (dir !== (0, import_node_path.dirname)(dir)) {
3205
+ if ((0, import_node_fs.existsSync)((0, import_node_path.join)(dir, "package.json"))) return dir;
3206
+ dir = (0, import_node_path.dirname)(dir);
3740
3207
  }
3741
3208
  return startDir;
3742
3209
  }
@@ -3769,13 +3236,13 @@ function extractBody(raw) {
3769
3236
  }
3770
3237
  async function loadPlaybooks(playbooksDir) {
3771
3238
  try {
3772
- const dir = playbooksDir ?? (0, import_node_path2.join)(findPackageRoot(__dirname), "knowledge", "playbooks");
3239
+ const dir = playbooksDir ?? (0, import_node_path.join)(findPackageRoot(__dirname), "knowledge", "playbooks");
3773
3240
  if (!(0, import_node_fs.existsSync)(dir)) return [];
3774
- const files = await (0, import_promises2.readdir)(dir);
3241
+ const files = await (0, import_promises.readdir)(dir);
3775
3242
  const mdFiles = files.filter((f) => f.endsWith(".md")).sort();
3776
3243
  const playbooks = [];
3777
3244
  for (const file of mdFiles) {
3778
- const raw = await (0, import_promises2.readFile)((0, import_node_path2.join)(dir, file), "utf-8");
3245
+ const raw = await (0, import_promises.readFile)((0, import_node_path.join)(dir, file), "utf-8");
3779
3246
  const frontmatter = parseFrontmatter(raw);
3780
3247
  if (!frontmatter) continue;
3781
3248
  playbooks.push({
@@ -3794,7 +3261,7 @@ async function loadPlaybooks(playbooksDir) {
3794
3261
  function loadReportTemplateSync() {
3795
3262
  try {
3796
3263
  const root = findPackageRoot(__dirname);
3797
- const path2 = (0, import_node_path3.join)(root, "knowledge", "templates", "report.md");
3264
+ const path2 = (0, import_node_path2.join)(root, "knowledge", "templates", "report.md");
3798
3265
  return (0, import_node_fs2.readFileSync)(path2, "utf-8");
3799
3266
  } catch (err) {
3800
3267
  console.warn(`[report-template] failed to load knowledge/templates/report.md: ${String(err)}`);
@@ -4112,744 +3579,1307 @@ At the end of each investigation, generate a structured markdown report using th
4112
3579
  \`\`\`markdown
4113
3580
  ` + REPORT_TEMPLATE + `\`\`\`
4114
3581
 
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.
3582
+ **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.
3583
+
3584
+ **Section order:** Metadata -> Summary -> Remediation -> Root Cause Analysis -> Evidence Collected -> Evidence Gaps -> Mutations Applied -> Post-Remediation Verification
3585
+
3586
+ **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.
3587
+ `;
3588
+ }
3589
+
3590
+ // src/agent/discover-schemas.ts
3591
+ var TARGET_TABLES = [
3592
+ "ki_query_history",
3593
+ "ki_query_active_all",
3594
+ "ki_query_span_metrics_all",
3595
+ "ki_query_workers",
3596
+ "ki_tiered_objects",
3597
+ "ki_obj_stat",
3598
+ "ki_partitions",
3599
+ "ki_objects",
3600
+ "ki_indexes",
3601
+ "ki_periodic_objects",
3602
+ "ki_depend",
3603
+ "ki_users_and_roles",
3604
+ "ki_object_permissions",
3605
+ "ki_load_history",
3606
+ "ki_backup_history",
3607
+ "ki_kafka_lag_info",
3608
+ "ki_columns",
3609
+ "ki_datatypes"
3610
+ ];
3611
+ async function discoverCatalogSchemas(session2) {
3612
+ try {
3613
+ const tableList = TARGET_TABLES.map((t) => `'${t}'`).join(", ");
3614
+ const statement = `SELECT table_name, column_name FROM ki_catalog.ki_columns WHERE table_name IN (${tableList}) ORDER BY table_name, column_name`;
3615
+ const result = await executeSql(session2, statement, 1e3);
3616
+ if (!result.ok) {
3617
+ return void 0;
3618
+ }
3619
+ const rows = result.data;
3620
+ if (rows.length === 0) {
3621
+ return void 0;
3622
+ }
3623
+ const grouped = /* @__PURE__ */ new Map();
3624
+ for (const row of rows) {
3625
+ const existing = grouped.get(row.table_name) ?? [];
3626
+ grouped.set(row.table_name, [...existing, row.column_name]);
3627
+ }
3628
+ return { tables: grouped };
3629
+ } catch {
3630
+ return void 0;
3631
+ }
3632
+ }
3633
+
3634
+ // src/agent/load-references.ts
3635
+ var import_promises2 = require("fs/promises");
3636
+ var import_node_path3 = require("path");
3637
+ var import_node_fs3 = require("fs");
3638
+ async function loadReferences(refsDir) {
3639
+ try {
3640
+ const dir = refsDir ?? (0, import_node_path3.join)(findPackageRoot(__dirname), "knowledge", "references");
3641
+ if (!(0, import_node_fs3.existsSync)(dir)) return [];
3642
+ const files = await (0, import_promises2.readdir)(dir);
3643
+ const mdFiles = files.filter((f) => f.endsWith(".md")).sort();
3644
+ const references = [];
3645
+ for (const file of mdFiles) {
3646
+ const raw = await (0, import_promises2.readFile)((0, import_node_path3.join)(dir, file), "utf-8");
3647
+ const frontmatter = parseFrontmatter(raw);
3648
+ if (!frontmatter) continue;
3649
+ references.push({
3650
+ title: frontmatter.title,
3651
+ category: frontmatter.category,
3652
+ keywords: frontmatter.keywords,
3653
+ body: extractBody(raw),
3654
+ filename: file
3655
+ });
3656
+ }
3657
+ return references;
3658
+ } catch {
3659
+ return [];
3660
+ }
3661
+ }
3662
+
3663
+ // src/agent/prompt-budget.ts
3664
+ var CHARS_PER_TOKEN = 4;
3665
+ var DEFAULT_PROMPT_BUDGET_TOKENS = 2e4;
3666
+ function estimateTokens(text) {
3667
+ if (!text) return 0;
3668
+ return Math.ceil(text.length / CHARS_PER_TOKEN);
3669
+ }
3670
+ function checkPromptBudget(prompt, opts) {
3671
+ const threshold = opts?.warnAtTokens ?? DEFAULT_PROMPT_BUDGET_TOKENS;
3672
+ const tokens = estimateTokens(prompt);
3673
+ return {
3674
+ tokens,
3675
+ chars: prompt ? prompt.length : 0,
3676
+ threshold,
3677
+ overBudget: tokens > threshold
3678
+ };
3679
+ }
3680
+
3681
+ // src/report/save-report.ts
3682
+ var import_promises3 = require("fs/promises");
3683
+ var import_node_path4 = require("path");
3684
+ var import_claude_agent_sdk3 = require("@anthropic-ai/claude-agent-sdk");
3685
+ var import_zod17 = require("zod");
3686
+ var PARTIAL_MARKER = "(PARTIAL -- investigation interrupted)\n\n";
3687
+ function formatTimestamp(date) {
3688
+ const year = date.getUTCFullYear();
3689
+ const month = String(date.getUTCMonth() + 1).padStart(2, "0");
3690
+ const day = String(date.getUTCDate()).padStart(2, "0");
3691
+ const hours = String(date.getUTCHours()).padStart(2, "0");
3692
+ const minutes = String(date.getUTCMinutes()).padStart(2, "0");
3693
+ const seconds = String(date.getUTCSeconds()).padStart(2, "0");
3694
+ return `${year}-${month}-${day}-${hours}${minutes}${seconds}`;
3695
+ }
3696
+ function makeSaveReportTool() {
3697
+ return (0, import_claude_agent_sdk3.tool)(
3698
+ "save_report",
3699
+ "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.",
3700
+ {
3701
+ content: import_zod17.z.string().describe("The full markdown diagnostic report content"),
3702
+ partial: import_zod17.z.boolean().optional().describe(
3703
+ "Set to true if the investigation was interrupted (e.g., Ctrl+C). Prepends a PARTIAL marker to the report."
3704
+ )
3705
+ },
3706
+ async (args) => {
3707
+ const rawContent = args.partial ? `${PARTIAL_MARKER}${args.content}` : args.content;
3708
+ const scrubbed = scrubCredentials(rawContent);
3709
+ const timestamp = formatTimestamp(/* @__PURE__ */ new Date());
3710
+ const filename = `kinetica-diag-${timestamp}.md`;
3711
+ const dir = (0, import_node_path4.resolve)(process.cwd(), "reports");
3712
+ await (0, import_promises3.mkdir)(dir, { recursive: true });
3713
+ const filepath = (0, import_node_path4.join)(dir, filename);
3714
+ await (0, import_promises3.writeFile)(filepath, scrubbed, "utf-8");
3715
+ return {
3716
+ content: [{ type: "text", text: `Report saved: ${filepath}` }]
3717
+ };
3718
+ },
3719
+ { annotations: { readOnly: true } }
3720
+ );
3721
+ }
3722
+
3723
+ // src/approval/gate.ts
3724
+ var import_prompts3 = require("@inquirer/prompts");
3725
+
3726
+ // src/approval/display.ts
3727
+ var import_picocolors5 = __toESM(require("picocolors"));
3728
+ var IMPACT_FALLBACK = "Impact unknown \u2014 review parameters carefully";
3729
+ var DIVIDER2 = import_picocolors5.default.dim("\u2500".repeat(50));
3730
+ var LABEL_WIDTH = 8;
3731
+ function formatLabel(label) {
3732
+ return ` ${label.padEnd(LABEL_WIDTH)}: `;
3733
+ }
3734
+ function renderApprovalPanel(toolName, toolInput, impact, beforeAfter, reasoningSummary) {
3735
+ const header = import_picocolors5.default.bold(import_picocolors5.default.yellow(" Mutation Approval Required"));
3736
+ const action = `${formatLabel("Action")}${import_picocolors5.default.bold(formatToolName(toolName))}`;
3737
+ const paramEntries = Object.entries(toolInput);
3738
+ const paramSection = paramEntries.length === 0 ? " (no parameters)" : paramEntries.map(([key, value]) => {
3739
+ const formatted = typeof value === "string" ? value : JSON.stringify(value, null, 2);
3740
+ return ` ${import_picocolors5.default.dim(key)}: ${formatted}`;
3741
+ }).join("\n");
3742
+ const impactLine = `${formatLabel("Impact")}${impact ?? IMPACT_FALLBACK}`;
3743
+ const prompt = import_picocolors5.default.dim(
3744
+ `${formatLabel("Respond")}y (proceed) | n (abort) | explain (show reasoning)`
3745
+ );
3746
+ const hasBeforeAfter = beforeAfter !== void 0 && beforeAfter.length > 0;
3747
+ const beforeAfterSection = hasBeforeAfter ? beforeAfter.map(
3748
+ (entry) => ` ${import_picocolors5.default.dim(entry.key)}: ${entry.current} ${import_picocolors5.default.yellow("->")} ${entry.proposed}`
3749
+ ).join("\n") : null;
3750
+ const hasReasoning = reasoningSummary !== void 0 && reasoningSummary.length > 0;
3751
+ const reasoningSection = hasReasoning ? `${formatLabel("Reason")}${reasoningSummary}` : null;
3752
+ const sections = ["", DIVIDER2, header, "", action, paramSection, ""];
3753
+ if (beforeAfterSection !== null) {
3754
+ sections.push(beforeAfterSection, "");
3755
+ }
3756
+ if (reasoningSection !== null) {
3757
+ sections.push(reasoningSection, "");
3758
+ }
3759
+ sections.push(impactLine, "", prompt, DIVIDER2, "");
3760
+ return sections.join("\n");
3761
+ }
3762
+
3763
+ // src/approval/gate.ts
3764
+ var DENY_MESSAGE = "User denied this mutation. Skip and continue with the investigation.";
3765
+ var REASONING_FALLBACK = "Reasoning not available. Review the action details above before proceeding.";
3766
+ function createApprovalGate(isReadOnly) {
3767
+ return async (toolName, toolInput, options) => {
3768
+ if (isReadOnly(toolName)) {
3769
+ return {
3770
+ behavior: "allow",
3771
+ updatedInput: toolInput,
3772
+ toolUseID: options.toolUseID
3773
+ };
3774
+ }
3775
+ const impact = options.decisionReason;
3776
+ const panel = renderApprovalPanel(toolName, toolInput, impact);
3777
+ console.error(panel);
3778
+ while (true) {
3779
+ try {
3780
+ const raw = await (0, import_prompts3.input)({ message: "Proceed? (y/n/explain):" }, { signal: options.signal });
3781
+ const normalized = raw.trim().toLowerCase();
3782
+ if (normalized === "y") {
3783
+ process.stderr.write("\n");
3784
+ return {
3785
+ behavior: "allow",
3786
+ updatedInput: toolInput,
3787
+ toolUseID: options.toolUseID
3788
+ };
3789
+ }
3790
+ if (normalized === "n") {
3791
+ process.stderr.write("\n");
3792
+ return {
3793
+ behavior: "deny",
3794
+ message: DENY_MESSAGE,
3795
+ toolUseID: options.toolUseID
3796
+ };
3797
+ }
3798
+ if (normalized === "explain") {
3799
+ const reasoning = options.decisionReason;
3800
+ if (reasoning) {
3801
+ console.error(`
3802
+ Agent reasoning: ${reasoning}
3803
+ `);
3804
+ } else {
3805
+ console.error(`
3806
+ ${REASONING_FALLBACK}
3807
+ `);
3808
+ }
3809
+ }
3810
+ } catch {
3811
+ return {
3812
+ behavior: "deny",
3813
+ message: DENY_MESSAGE,
3814
+ toolUseID: options.toolUseID
3815
+ };
3816
+ }
3817
+ }
3818
+ };
3819
+ }
3820
+
3821
+ // src/agent/turn-gate.ts
3822
+ function createTurnGate() {
3823
+ let resolve2 = () => {
3824
+ };
3825
+ let promise = new Promise((r) => {
3826
+ resolve2 = r;
3827
+ });
3828
+ return Object.freeze({
3829
+ wait: () => promise,
3830
+ open: () => {
3831
+ resolve2();
3832
+ },
3833
+ close: () => {
3834
+ promise = new Promise((r) => {
3835
+ resolve2 = r;
3836
+ });
3837
+ }
3838
+ });
3839
+ }
3840
+
3841
+ // src/output/render-markdown.ts
3842
+ var import_picocolors6 = __toESM(require("picocolors"));
3843
+ var BOLD_RE = /\*\*(.+?)\*\*/g;
3844
+ var HEADING_RE = /^(#{1,6})\s+(.+)$/;
3845
+ function renderMarkdownLine(line) {
3846
+ const headingMatch = HEADING_RE.exec(line);
3847
+ if (headingMatch) {
3848
+ return import_picocolors6.default.bold(headingMatch[2]);
3849
+ }
3850
+ if (line.includes("**")) {
3851
+ return line.replace(BOLD_RE, (_, text) => import_picocolors6.default.bold(text));
3852
+ }
3853
+ return line;
3854
+ }
3855
+
3856
+ // src/output/reformat-tables.ts
3857
+ var SEPARATOR_CELL_RE = /^:?-+:?$/;
3858
+ var BOLD_MARKERS_RE = /\*\*(.+?)\*\*/g;
3859
+ function visualWidth(text) {
3860
+ return text.replace(BOLD_MARKERS_RE, "$1").length;
3861
+ }
3862
+ function isSeparatorCell(cell) {
3863
+ return SEPARATOR_CELL_RE.test(cell);
3864
+ }
3865
+ function isSeparatorRow(cells) {
3866
+ return cells.length > 0 && cells.every(isSeparatorCell);
3867
+ }
3868
+ function parseCells(line) {
3869
+ return line.split("|").slice(1, -1).map((c) => c.trim());
3870
+ }
3871
+ function reformatTableBlock(lines) {
3872
+ const parsed = lines.map(parseCells);
3873
+ const colCount = Math.max(...parsed.map((row) => row.length));
3874
+ const normalised = parsed.map((row) => {
3875
+ const padded = [...row];
3876
+ while (padded.length < colCount) {
3877
+ padded.push("");
3878
+ }
3879
+ return padded;
3880
+ });
3881
+ const colWidths = Array.from(
3882
+ { length: colCount },
3883
+ (_, col) => Math.max(
3884
+ 3,
3885
+ ...normalised.filter((row) => !isSeparatorRow(row)).map((row) => visualWidth(row[col]))
3886
+ )
3887
+ );
3888
+ const borderRow = `+${colWidths.map((w) => "-".repeat(w + 2)).join("+")}+`;
3889
+ const bodyRows = normalised.map((row) => {
3890
+ if (isSeparatorRow(row)) {
3891
+ return borderRow;
3892
+ }
3893
+ const cells = row.map((cell, col) => {
3894
+ const rendered = renderMarkdownLine(cell);
3895
+ const pad = colWidths[col] - visualWidth(cell);
3896
+ return rendered + " ".repeat(Math.max(0, pad));
3897
+ });
3898
+ return `| ${cells.join(" | ")} |`;
3899
+ });
3900
+ return [borderRow, ...bodyRows, borderRow];
3901
+ }
4116
3902
 
4117
- **Section order:** Metadata -> Summary -> Remediation -> Root Cause Analysis -> Evidence Collected -> Evidence Gaps -> Mutations Applied -> Post-Remediation Verification
3903
+ // src/output/streaming-table-aligner.ts
3904
+ var TABLE_LINE_RE = /^\|.*\|$/;
3905
+ function createStreamingTableAligner() {
3906
+ let lineBuffer = "";
3907
+ let tableLines = [];
3908
+ function flushTable() {
3909
+ if (tableLines.length === 0) return "";
3910
+ const aligned = reformatTableBlock(tableLines);
3911
+ tableLines = [];
3912
+ return aligned.join("\n") + "\n";
3913
+ }
3914
+ function push(text) {
3915
+ if (!text) return "";
3916
+ const combined = lineBuffer + text;
3917
+ const segments = combined.split("\n");
3918
+ lineBuffer = segments[segments.length - 1];
3919
+ const completeLines = segments.slice(0, -1);
3920
+ let output = "";
3921
+ for (const line of completeLines) {
3922
+ const trimmed = line.trim();
3923
+ if (TABLE_LINE_RE.test(trimmed)) {
3924
+ tableLines.push(trimmed);
3925
+ } else {
3926
+ output += flushTable();
3927
+ output += renderMarkdownLine(line) + "\n";
3928
+ }
3929
+ }
3930
+ return output;
3931
+ }
3932
+ function flush() {
3933
+ let output = flushTable();
3934
+ if (lineBuffer) {
3935
+ output += renderMarkdownLine(lineBuffer);
3936
+ lineBuffer = "";
3937
+ }
3938
+ return output;
3939
+ }
3940
+ return Object.freeze({ push, flush });
3941
+ }
4118
3942
 
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
- `;
3943
+ // src/output/spinner.ts
3944
+ var import_picocolors7 = __toESM(require("picocolors"));
3945
+ var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
3946
+ var FRAME_INTERVAL_MS = 80;
3947
+ var DEFAULT_LABEL = "Thinking";
3948
+ function createSpinner() {
3949
+ let timer = null;
3950
+ let frameIndex = 0;
3951
+ const start = (label = DEFAULT_LABEL) => {
3952
+ if (timer !== null) return;
3953
+ frameIndex = 0;
3954
+ timer = setInterval(() => {
3955
+ const frame = FRAMES[frameIndex % FRAMES.length];
3956
+ process.stderr.write(`\r${import_picocolors7.default.dim(`${frame} ${label}...`)}`);
3957
+ frameIndex += 1;
3958
+ }, FRAME_INTERVAL_MS);
3959
+ timer.unref();
3960
+ };
3961
+ const stop = () => {
3962
+ if (timer === null) return;
3963
+ clearInterval(timer);
3964
+ timer = null;
3965
+ process.stderr.write("\r\x1B[K");
3966
+ };
3967
+ const isRunning = () => timer !== null;
3968
+ return Object.freeze({ start, stop, isRunning });
4121
3969
  }
4122
3970
 
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"
3971
+ // src/agent/run-agent.ts
3972
+ var MCP_SERVER_NAME = "kinetica-diagnostics";
3973
+ var EXIT_COMMANDS = /* @__PURE__ */ new Set(["exit", "quit", "end", "q"]);
3974
+ var SUPPORTED_MODELS = ["sonnet", "haiku", "opus"];
3975
+ var DEFAULT_AGENT_MODEL = "sonnet";
3976
+ var DEFAULT_MAX_BUDGET_USD = 5;
3977
+ var ALLOWED_TOOL_NAMES = [
3978
+ ...DIAGNOSTIC_TOOL_NAMES.map((name) => `mcp__${MCP_SERVER_NAME}__${name}`),
3979
+ `mcp__${MCP_SERVER_NAME}__save_report`,
3980
+ `mcp__${MCP_SERVER_NAME}__${ALTER_TABLE_COLUMNS_TOOL_NAME}`
4143
3981
  ];
4144
- async function discoverCatalogSchemas(session2) {
3982
+ var DISALLOWED_TOOLS = ["Bash", "Edit", "Write", "MultiEdit"];
3983
+ var ERROR_LABELS = {
3984
+ authentication_failed: "Authentication failed \u2014 check your API key or re-run with --login",
3985
+ billing_error: "Billing error \u2014 check your Anthropic account",
3986
+ rate_limit: "Rate limit exceeded",
3987
+ server_error: "Anthropic API server error",
3988
+ invalid_request: "Invalid API request",
3989
+ max_output_tokens: "Response exceeded maximum output length",
3990
+ unknown: "Unknown API error"
3991
+ };
3992
+ function isExitCommand(text) {
3993
+ return EXIT_COMMANDS.has(text.trim().toLowerCase());
3994
+ }
3995
+ function makeUserMessage(content) {
3996
+ return {
3997
+ type: "user",
3998
+ message: { role: "user", content },
3999
+ parent_tool_use_id: null,
4000
+ session_id: ""
4001
+ };
4002
+ }
4003
+ async function* makeInteractivePrompt(abortController, turnGate, spinner) {
4004
+ while (!abortController.signal.aborted) {
4005
+ try {
4006
+ process.stderr.write("\n");
4007
+ const issue = await (0, import_prompts4.input)({ message: "Describe the issue to investigate:" });
4008
+ process.stderr.write("\n");
4009
+ const trimmed = issue.trim();
4010
+ if (!trimmed) continue;
4011
+ if (isExitCommand(trimmed)) return;
4012
+ spinner.start();
4013
+ yield makeUserMessage(trimmed);
4014
+ break;
4015
+ } catch {
4016
+ return;
4017
+ }
4018
+ }
4019
+ while (!abortController.signal.aborted) {
4020
+ try {
4021
+ await turnGate.wait();
4022
+ if (abortController.signal.aborted) break;
4023
+ process.stderr.write("\n");
4024
+ const response = await (0, import_prompts4.input)({ message: "You:" });
4025
+ process.stderr.write("\n");
4026
+ const trimmed = response.trim();
4027
+ if (!trimmed) continue;
4028
+ if (isExitCommand(trimmed)) return;
4029
+ turnGate.close();
4030
+ spinner.start();
4031
+ yield makeUserMessage(trimmed);
4032
+ } catch {
4033
+ return;
4034
+ }
4035
+ }
4036
+ }
4037
+ async function displayDegradedStatus(session2) {
4038
+ const [statusResult, alertsResult] = await Promise.all([
4039
+ hostManagerStatus(session2),
4040
+ hostManagerAlerts(session2)
4041
+ ]);
4042
+ process.stderr.write(import_picocolors8.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"));
4043
+ if (statusResult.ok) {
4044
+ const rows = statusResult.data;
4045
+ const maxKeyLen = rows.reduce((max, r) => Math.max(max, r.key.length), 0);
4046
+ for (const row of rows) {
4047
+ process.stderr.write(` ${import_picocolors8.default.dim(row.key.padEnd(maxKeyLen))} ${row.value}
4048
+ `);
4049
+ }
4050
+ } else {
4051
+ process.stderr.write(` ${import_picocolors8.default.red(`Error: ${statusResult.error}`)}
4052
+ `);
4053
+ }
4054
+ process.stderr.write("\n");
4055
+ process.stderr.write(import_picocolors8.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"));
4056
+ if (alertsResult.ok) {
4057
+ const alerts = alertsResult.data;
4058
+ if (alerts.length === 0) {
4059
+ process.stderr.write(` ${import_picocolors8.default.dim("No recent alerts.")}
4060
+ `);
4061
+ } else {
4062
+ for (const alert of alerts) {
4063
+ process.stderr.write(` ${import_picocolors8.default.dim(alert.timestamp)} ${alert.type} ${alert.params}
4064
+ `);
4065
+ }
4066
+ }
4067
+ } else {
4068
+ process.stderr.write(` ${import_picocolors8.default.dim(`Unavailable: ${alertsResult.error}`)}
4069
+ `);
4070
+ }
4071
+ process.stderr.write("\n");
4072
+ }
4073
+ async function runAgent(session2, kineticaVersion, degraded, model) {
4074
+ const [catalogSchemas, playbooks, references] = await Promise.all([
4075
+ degraded ? Promise.resolve(void 0) : discoverCatalogSchemas(session2),
4076
+ loadPlaybooks(),
4077
+ loadReferences()
4078
+ ]);
4079
+ const systemPrompt = buildSystemPrompt(
4080
+ kineticaVersion,
4081
+ catalogSchemas,
4082
+ playbooks,
4083
+ references,
4084
+ degraded
4085
+ );
4086
+ const budget = checkPromptBudget(systemPrompt);
4087
+ if (process.env.DEBUG) {
4088
+ process.stderr.write(
4089
+ import_picocolors8.default.dim(`system prompt: ~${budget.tokens} tokens (${budget.chars} chars)
4090
+ `)
4091
+ );
4092
+ }
4093
+ if (budget.overBudget) {
4094
+ process.stderr.write(
4095
+ import_picocolors8.default.yellow(
4096
+ `\u26A0 system prompt is ~${budget.tokens} tokens (threshold ${budget.threshold}) \u2014 knowledge corpus is getting expensive; consider keyword-based playbook selection.
4097
+ `
4098
+ )
4099
+ );
4100
+ }
4101
+ const diagnosticTools = makeDiagnosticTools(session2, catalogSchemas);
4102
+ const mutationTools = makeMutationTools(session2);
4103
+ const saveReportTool = makeSaveReportTool();
4104
+ const alterTableColumnsTool = makeAlterTableColumnsToolWithDeps(session2);
4105
+ const server = (0, import_claude_agent_sdk4.createSdkMcpServer)({
4106
+ name: MCP_SERVER_NAME,
4107
+ version: "1.0.0",
4108
+ tools: [...diagnosticTools, ...mutationTools, saveReportTool, alterTableColumnsTool]
4109
+ });
4110
+ const spinner = createSpinner();
4111
+ const registry = createDiagnosticRegistry();
4112
+ const approvalGate = createApprovalGate(registry.isReadOnlyTool);
4113
+ const canUseTool = async (toolName, toolInput, options2) => {
4114
+ spinner.stop();
4115
+ return approvalGate(toolName, toolInput, options2);
4116
+ };
4117
+ const abortController = new AbortController();
4118
+ const effectiveModel = model ?? DEFAULT_AGENT_MODEL;
4119
+ const options = {
4120
+ mcpServers: { [MCP_SERVER_NAME]: server },
4121
+ allowedTools: ALLOWED_TOOL_NAMES,
4122
+ disallowedTools: [...DISALLOWED_TOOLS],
4123
+ canUseTool,
4124
+ systemPrompt,
4125
+ model: effectiveModel,
4126
+ fallbackModel: "haiku",
4127
+ thinking: { type: "adaptive" },
4128
+ maxTurns: 100,
4129
+ maxBudgetUsd: DEFAULT_MAX_BUDGET_USD,
4130
+ persistSession: false,
4131
+ includePartialMessages: true,
4132
+ abortController,
4133
+ env: { ...process.env, CLAUDE_AGENT_SDK_CLIENT_APP: "admin-agent" }
4134
+ };
4135
+ if (degraded) {
4136
+ process.stderr.write("\nKinetica Diagnostic Session Ready (DEGRADED MODE)\n");
4137
+ process.stderr.write(
4138
+ "DB engine (port 9191) is unreachable. Only host manager tools are available.\n\n"
4139
+ );
4140
+ await displayDegradedStatus(session2);
4141
+ process.stderr.write("Type 'exit' to end the session.\n\n");
4142
+ } else {
4143
+ process.stderr.write("\nKinetica Diagnostic Session Ready\n");
4144
+ process.stderr.write("Type 'exit' to end the session.\n\n");
4145
+ }
4146
+ const turnGate = createTurnGate();
4147
+ const agentQuery = (0, import_claude_agent_sdk4.query)({
4148
+ prompt: makeInteractivePrompt(abortController, turnGate, spinner),
4149
+ options
4150
+ });
4151
+ process.once("SIGINT", () => {
4152
+ spinner.stop();
4153
+ process.stderr.write("\nInterrupted \u2014 aborting investigation...\n");
4154
+ abortController.abort();
4155
+ agentQuery.close();
4156
+ });
4157
+ let numTurns = 0;
4158
+ let totalCostUsd = 0;
4159
+ let durationMs = 0;
4160
+ let durationApiMs = 0;
4161
+ let cacheReadTokens = 0;
4162
+ let cacheCreationTokens = 0;
4163
+ let lastStreamCharWasNewline = true;
4164
+ const tableAligner = createStreamingTableAligner();
4165
+ let hadNonAbortError = false;
4145
4166
  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;
4167
+ for await (const message of agentQuery) {
4168
+ if (message.type === "stream_event") {
4169
+ const { event: evt } = message;
4170
+ if (evt.type === "content_block_delta" && evt.delta.type === "text_delta") {
4171
+ const text = evt.delta.text ?? "";
4172
+ if (text) {
4173
+ spinner.stop();
4174
+ const output = tableAligner.push(text);
4175
+ if (output) {
4176
+ process.stderr.write(output);
4177
+ lastStreamCharWasNewline = output.endsWith("\n");
4178
+ }
4179
+ }
4180
+ }
4181
+ } else if (message.type === "assistant") {
4182
+ const assistantMsg = message;
4183
+ const remaining = tableAligner.flush();
4184
+ if (remaining) {
4185
+ process.stderr.write(remaining);
4186
+ lastStreamCharWasNewline = remaining.endsWith("\n");
4187
+ }
4188
+ if (!lastStreamCharWasNewline) {
4189
+ process.stderr.write("\n");
4190
+ lastStreamCharWasNewline = true;
4191
+ }
4192
+ if (assistantMsg.message.stop_reason === "end_turn") {
4193
+ spinner.stop();
4194
+ turnGate.open();
4195
+ } else if (assistantMsg.message.stop_reason === "tool_use") {
4196
+ spinner.start("Investigating");
4197
+ } else {
4198
+ spinner.stop();
4199
+ }
4200
+ if (assistantMsg.error) {
4201
+ spinner.stop();
4202
+ const label = ERROR_LABELS[assistantMsg.error] ?? assistantMsg.error;
4203
+ process.stderr.write(import_picocolors8.default.yellow(`
4204
+ API error: ${label}
4205
+ `));
4206
+ turnGate.open();
4207
+ }
4208
+ } else if (message.type === "result") {
4209
+ spinner.stop();
4210
+ const resultMsg = message;
4211
+ numTurns = resultMsg.num_turns;
4212
+ totalCostUsd = resultMsg.total_cost_usd;
4213
+ durationMs = resultMsg.duration_ms;
4214
+ durationApiMs = resultMsg.duration_api_ms;
4215
+ const usages = Object.values(resultMsg.modelUsage ?? {});
4216
+ cacheReadTokens = usages.reduce((sum, u) => sum + (u.cacheReadInputTokens ?? 0), 0);
4217
+ cacheCreationTokens = usages.reduce((sum, u) => sum + (u.cacheCreationInputTokens ?? 0), 0);
4218
+ if (resultMsg.subtype === "error_max_turns") {
4219
+ process.stderr.write(
4220
+ "\nInvestigation hit turn limit. Partial report may be available.\n"
4221
+ );
4222
+ } else if (resultMsg.subtype === "error_max_budget_usd") {
4223
+ process.stderr.write("\nBudget limit reached.\n");
4224
+ } else if (resultMsg.subtype === "error_during_execution") {
4225
+ process.stderr.write(
4226
+ "\nExecution error \u2014 the agent encountered an unrecoverable failure.\n"
4227
+ );
4228
+ } else if (resultMsg.subtype !== "success") {
4229
+ process.stderr.write(`
4230
+ Agent session ended with error: ${resultMsg.subtype}
4231
+ `);
4232
+ }
4233
+ if (resultMsg.permission_denials.length > 0) {
4234
+ const denied = resultMsg.permission_denials.map((d) => d.tool_name).join(", ");
4235
+ process.stderr.write(`
4236
+ Permission denials: ${denied}
4237
+ `);
4238
+ }
4239
+ turnGate.open();
4240
+ } else if (message.type === "system") {
4241
+ const sysMsg = message;
4242
+ if (sysMsg.subtype === "init") {
4243
+ const initMsg = message;
4244
+ const failed = (initMsg.mcp_servers ?? []).filter(
4245
+ (s) => s.name === MCP_SERVER_NAME && s.status !== "connected"
4246
+ );
4247
+ for (const s of failed) {
4248
+ process.stderr.write(
4249
+ `
4250
+ Warning: MCP server "${s.name}" failed to connect (${s.status})
4251
+ `
4252
+ );
4253
+ }
4254
+ } else if (sysMsg.subtype === "api_retry") {
4255
+ const retryMsg = message;
4256
+ const statusStr = retryMsg.error_status !== null ? ` (HTTP ${retryMsg.error_status})` : "";
4257
+ const delaySec = Math.round(retryMsg.retry_delay_ms / 1e3);
4258
+ process.stderr.write(
4259
+ import_picocolors8.default.yellow(
4260
+ `
4261
+ API error${statusStr}. Retrying (attempt ${retryMsg.attempt}/${retryMsg.max_retries}) in ${delaySec}s...
4262
+ `
4263
+ )
4264
+ );
4265
+ } else if (sysMsg.subtype === "compact_boundary") {
4266
+ const compactMsg = message;
4267
+ const preTokens = compactMsg.compact_metadata.pre_tokens;
4268
+ process.stderr.write(
4269
+ `
4270
+ [Context compressed (${preTokens} tokens before compaction) \u2014 investigation continues]
4271
+ `
4272
+ );
4273
+ }
4274
+ } else if (message.type === "rate_limit_event") {
4275
+ const rateMsg = message;
4276
+ const { status, resetsAt } = rateMsg.rate_limit_info;
4277
+ if (status === "rejected") {
4278
+ const resetStr = resetsAt ? ` Resets at ${new Date(resetsAt * 1e3).toISOString()}.` : "";
4279
+ process.stderr.write(`
4280
+ Rate limited \u2014 requests rejected.${resetStr}
4281
+ `);
4282
+ } else if (status === "allowed_warning") {
4283
+ process.stderr.write("\nApproaching rate limit \u2014 investigation may slow.\n");
4284
+ }
4285
+ } else if (message.type === "control_request") {
4286
+ const controlMsg = message;
4287
+ if (controlMsg.request.subtype === "claude_authenticate") {
4288
+ process.stderr.write(import_picocolors8.default.yellow("\nRe-authentication requested by SDK...\n"));
4289
+ }
4290
+ }
4151
4291
  }
4152
- const rows = result.data;
4153
- if (rows.length === 0) {
4154
- return void 0;
4292
+ } catch (error) {
4293
+ spinner.stop();
4294
+ if (error instanceof import_claude_agent_sdk4.AbortError) {
4295
+ hadNonAbortError = false;
4296
+ } else {
4297
+ hadNonAbortError = true;
4298
+ const message = error instanceof Error ? error.message : String(error);
4299
+ process.stderr.write(import_picocolors8.default.red(`
4300
+ Agent error: ${message}
4301
+ `));
4155
4302
  }
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]);
4303
+ } finally {
4304
+ spinner.stop();
4305
+ const remaining = tableAligner.flush();
4306
+ if (remaining) {
4307
+ process.stderr.write(remaining);
4160
4308
  }
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
- });
4309
+ turnGate.open();
4310
+ const durationSec = Math.round(durationMs / 1e3);
4311
+ const apiPct = durationMs > 0 ? Math.round(durationApiMs / durationMs * 100) : 0;
4312
+ const costStr = totalCostUsd > 0 ? ` Cost: $${totalCostUsd.toFixed(4)}.` : "";
4313
+ if (process.env.DEBUG && (cacheReadTokens > 0 || cacheCreationTokens > 0)) {
4314
+ process.stderr.write(
4315
+ import_picocolors8.default.dim(
4316
+ `cache: ${cacheReadTokens} read / ${cacheCreationTokens} created input tokens (read > 0 confirms the system prompt is served from cache)
4317
+ `
4318
+ )
4319
+ );
4320
+ }
4321
+ if (hadNonAbortError) {
4322
+ process.stderr.write(`
4323
+ Session ended due to error. Turns: ${numTurns}.${costStr}
4324
+ `);
4325
+ } else {
4326
+ process.stderr.write(
4327
+ `
4328
+ Session ended. Turns: ${numTurns}. Duration: ${durationSec}s (${apiPct}% API).${costStr}
4329
+ `
4330
+ );
4189
4331
  }
4190
- return references;
4191
- } catch {
4192
- return [];
4193
4332
  }
4194
4333
  }
4195
4334
 
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
- };
4335
+ // src/cli/select-model.ts
4336
+ var MODEL_LABELS = {
4337
+ sonnet: "Sonnet \u2014 balanced, best general coding (default)",
4338
+ haiku: "Haiku \u2014 fastest & cheapest, lighter reasoning",
4339
+ opus: "Opus \u2014 deepest reasoning, slower & pricier"
4340
+ };
4341
+ async function selectModel() {
4342
+ return (0, import_prompts5.select)({
4343
+ message: "Select model for this session:",
4344
+ default: DEFAULT_AGENT_MODEL,
4345
+ choices: SUPPORTED_MODELS.map((value) => ({ value, name: MODEL_LABELS[value] }))
4346
+ });
4212
4347
  }
4213
4348
 
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
- }
4349
+ // src/auth/preflight.ts
4350
+ var import_claude_agent_sdk5 = require("@anthropic-ai/claude-agent-sdk");
4255
4351
 
4256
- // src/approval/gate.ts
4257
- var import_prompts7 = require("@inquirer/prompts");
4352
+ // src/auth/oauth-flow.ts
4353
+ var import_picocolors9 = __toESM(require("picocolors"));
4258
4354
 
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)}: `;
4355
+ // src/auth/open-browser.ts
4356
+ var import_child_process = require("child_process");
4357
+ function openBrowser(url) {
4358
+ try {
4359
+ const platform = process.platform;
4360
+ const { command, args } = platform === "darwin" ? { command: "open", args: [url] } : platform === "win32" ? { command: "cmd", args: ["/c", "start", "", url] } : { command: "xdg-open", args: [url] };
4361
+ const child = (0, import_child_process.spawn)(command, args, { detached: true, stdio: "ignore" });
4362
+ child.unref();
4363
+ return true;
4364
+ } catch {
4365
+ return false;
4366
+ }
4266
4367
  }
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, "");
4368
+
4369
+ // src/auth/oauth-flow.ts
4370
+ async function resolveAuthentication(agentQuery, options) {
4371
+ if (options.hasApiKey && !options.forceLogin) {
4372
+ return { method: "api_key" };
4288
4373
  }
4289
- if (reasoningSection !== null) {
4290
- sections.push(reasoningSection, "");
4374
+ const query3 = agentQuery;
4375
+ try {
4376
+ const { manualUrl, automaticUrl } = await query3.claudeAuthenticate(options.loginWithClaudeAi);
4377
+ const opened = openBrowser(automaticUrl);
4378
+ if (opened) {
4379
+ process.stderr.write(import_picocolors9.default.dim("Browser opened for login. Waiting for authentication...\n"));
4380
+ } else {
4381
+ process.stderr.write(`
4382
+ Open this URL in your browser to log in:
4383
+ ${import_picocolors9.default.bold(manualUrl)}
4384
+
4385
+ `);
4386
+ process.stderr.write(import_picocolors9.default.dim("Waiting for browser login to complete...\n"));
4387
+ }
4388
+ await query3.claudeOAuthWaitForCompletion();
4389
+ return { method: "oauth" };
4390
+ } catch (err) {
4391
+ const message = err instanceof Error ? err.message : String(err);
4392
+ process.stderr.write(
4393
+ import_picocolors9.default.yellow(`
4394
+ Warning: OAuth login failed (${message}). SDK may retry automatically.
4395
+ `)
4396
+ );
4397
+ return { method: "oauth" };
4291
4398
  }
4292
- sections.push(impactLine, "", prompt, DIVIDER2, "");
4293
- return sections.join("\n");
4294
4399
  }
4295
4400
 
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
- };
4401
+ // src/auth/preflight.ts
4402
+ var PROBE_TIMEOUT_MS = 1e4;
4403
+ async function probeCachedCredentials(authQuery) {
4404
+ try {
4405
+ const info = await Promise.race([
4406
+ authQuery.accountInfo(),
4407
+ new Promise(
4408
+ (_, reject) => setTimeout(() => reject(new Error("Probe timed out")), PROBE_TIMEOUT_MS)
4409
+ )
4410
+ ]);
4411
+ if (info.email || info.apiKeySource) {
4412
+ return info;
4307
4413
  }
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
- };
4414
+ return null;
4415
+ } catch {
4416
+ return null;
4417
+ }
4418
+ }
4419
+ async function authenticateAnthropic(options) {
4420
+ const hasApiKey = Boolean(process.env.ANTHROPIC_API_KEY);
4421
+ if (hasApiKey && !options.forceLogin) {
4422
+ return { method: "api_key" };
4423
+ }
4424
+ if (!process.stdin.isTTY) {
4425
+ throw new Error(
4426
+ "No ANTHROPIC_API_KEY set and terminal is non-interactive. Set ANTHROPIC_API_KEY or run in an interactive terminal with --login."
4427
+ );
4428
+ }
4429
+ const env = options.forceLogin ? (() => {
4430
+ const { ANTHROPIC_API_KEY: _stripped, ...rest } = process.env;
4431
+ return { ...rest, CLAUDE_AGENT_SDK_CLIENT_APP: "admin-agent" };
4432
+ })() : { ...process.env, CLAUDE_AGENT_SDK_CLIENT_APP: "admin-agent" };
4433
+ const abortController = new AbortController();
4434
+ async function* hangingPrompt() {
4435
+ await new Promise(() => {
4436
+ });
4437
+ }
4438
+ const authQuery = (0, import_claude_agent_sdk5.query)({
4439
+ prompt: hangingPrompt(),
4440
+ options: {
4441
+ persistSession: false,
4442
+ abortController,
4443
+ env,
4444
+ ...options.loginMethod ? { forceLoginMethod: options.loginMethod } : {},
4445
+ ...options.loginOrgUUID ? { forceLoginOrgUUID: options.loginOrgUUID } : {}
4446
+ }
4447
+ });
4448
+ try {
4449
+ if (!options.forceLogin) {
4450
+ const cached = await probeCachedCredentials(authQuery);
4451
+ if (cached) {
4452
+ return { method: "oauth", email: cached.email };
4349
4453
  }
4350
4454
  }
4351
- };
4455
+ return await resolveAuthentication(authQuery, {
4456
+ forceLogin: options.forceLogin,
4457
+ loginWithClaudeAi: (options.loginMethod ?? "claudeai") === "claudeai",
4458
+ hasApiKey
4459
+ });
4460
+ } finally {
4461
+ abortController.abort();
4462
+ await authQuery.return().catch(() => {
4463
+ });
4464
+ }
4465
+ }
4466
+
4467
+ // src/auth/logout.ts
4468
+ var import_child_process2 = require("child_process");
4469
+ var import_node_module = require("module");
4470
+ var import_node_path5 = __toESM(require("path"));
4471
+ var import_util = require("util");
4472
+ var execFileAsync = (0, import_util.promisify)(import_child_process2.execFile);
4473
+ function resolveSdkCliPath() {
4474
+ const require_ = (0, import_node_module.createRequire)(__filename);
4475
+ return import_node_path5.default.join(import_node_path5.default.dirname(require_.resolve("@anthropic-ai/claude-agent-sdk")), "cli.js");
4476
+ }
4477
+ async function logout() {
4478
+ try {
4479
+ const sdkCliPath = resolveSdkCliPath();
4480
+ const { stdout, stderr } = await execFileAsync(process.execPath, [
4481
+ sdkCliPath,
4482
+ "auth",
4483
+ "logout"
4484
+ ]);
4485
+ const output = (stdout || stderr || "").trim();
4486
+ return { success: true, message: output || "Logged out successfully." };
4487
+ } catch (err) {
4488
+ const message = err instanceof Error ? err.message : String(err);
4489
+ return { success: false, message: `Logout failed: ${message}` };
4490
+ }
4352
4491
  }
4353
4492
 
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
- });
4493
+ // src/session/env-file.ts
4494
+ var import_fs2 = require("fs");
4495
+ var import_promises4 = require("fs/promises");
4496
+ var import_path2 = require("path");
4497
+ var import_prompts6 = require("@inquirer/prompts");
4498
+ var import_picocolors10 = __toESM(require("picocolors"));
4499
+ function parseEnvContent(content) {
4500
+ const entries = [];
4501
+ for (const line of content.split("\n")) {
4502
+ const trimmed = line.trim();
4503
+ if (trimmed === "" || trimmed.startsWith("#")) continue;
4504
+ const match = /^([A-Za-z_]\w*)=(.*)/.exec(trimmed);
4505
+ if (!match) continue;
4506
+ const key = match[1];
4507
+ let value = match[2];
4508
+ if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
4509
+ value = value.slice(1, -1).replace(/\\(["\\])/g, "$1");
4510
+ } else if (value.startsWith("'") && value.endsWith("'") && value.length >= 2) {
4511
+ value = value.slice(1, -1);
4370
4512
  }
4371
- });
4513
+ entries.push([key, value]);
4514
+ }
4515
+ return new Map(entries);
4372
4516
  }
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]);
4517
+ var FORBIDDEN_VALUE_CHARS = /[\n\r\0]/;
4518
+ var REQUIRES_QUOTING = /[\s"'#]/;
4519
+ function escapeEnvValue(value) {
4520
+ if (FORBIDDEN_VALUE_CHARS.test(value)) {
4521
+ throw new Error(
4522
+ "Env value contains a forbidden character (newline or null byte); refusing to write"
4523
+ );
4382
4524
  }
4383
- if (line.includes("**")) {
4384
- return line.replace(BOLD_RE, (_, text) => import_picocolors11.default.bold(text));
4525
+ if (!REQUIRES_QUOTING.test(value)) {
4526
+ return value;
4385
4527
  }
4386
- return line;
4528
+ const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
4529
+ return `"${escaped}"`;
4387
4530
  }
4531
+ var ENV_TEMPLATE = `# Anthropic API key (optional \u2014 if not set, OAuth login via browser is used)
4532
+ ANTHROPIC_API_KEY=
4388
4533
 
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("");
4534
+ # Kinetica connection details (prompted interactively if not set)
4535
+ # If password is omitted, the agent will prompt for it at startup
4536
+ KINETICA_URL={url}
4537
+ KINETICA_USER={user}
4538
+ KINETICA_PASS=
4539
+ `;
4540
+ function buildEnvContent(url, user, existingContent) {
4541
+ const safeUrl = escapeEnvValue(url);
4542
+ const safeUser = escapeEnvValue(user);
4543
+ if (!existingContent?.trim()) {
4544
+ return ENV_TEMPLATE.replace("{url}", safeUrl).replace("{user}", safeUser);
4545
+ }
4546
+ const lines = existingContent.split("\n");
4547
+ let urlReplaced = false;
4548
+ let userReplaced = false;
4549
+ const updated = lines.map((line) => {
4550
+ if (/^KINETICA_URL=/.exec(line)) {
4551
+ urlReplaced = true;
4552
+ return `KINETICA_URL=${safeUrl}`;
4411
4553
  }
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;
4554
+ if (/^KINETICA_USER=/.exec(line)) {
4555
+ userReplaced = true;
4556
+ return `KINETICA_USER=${safeUser}`;
4425
4557
  }
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(" | ")} |`;
4558
+ return line;
4432
4559
  });
4433
- return [borderRow, ...bodyRows, borderRow];
4560
+ if (!urlReplaced) updated.push(`KINETICA_URL=${safeUrl}`);
4561
+ if (!userReplaced) updated.push(`KINETICA_USER=${safeUser}`);
4562
+ return updated.join("\n");
4434
4563
  }
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";
4564
+ function loadEnvFile(dir, env = process.env) {
4565
+ try {
4566
+ const filePath = (0, import_path2.join)(dir ?? process.cwd(), ".env");
4567
+ const content = (0, import_fs2.readFileSync)(filePath, "utf8");
4568
+ const parsed = parseEnvContent(content);
4569
+ for (const [key, value] of parsed) {
4570
+ if (env[key] === void 0 && value !== "") {
4571
+ env[key] = value;
4461
4572
  }
4462
4573
  }
4463
- return output;
4464
- }
4465
- function flush() {
4466
- let output = flushTable();
4467
- if (lineBuffer) {
4468
- output += renderMarkdownLine(lineBuffer);
4469
- lineBuffer = "";
4470
- }
4471
- return output;
4574
+ } catch {
4472
4575
  }
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
4576
  }
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) {
4577
+ async function offerSaveCredentials(url, user, dir) {
4578
+ if (!process.stdin.isTTY) return;
4579
+ try {
4580
+ const shouldSave = await (0, import_prompts6.confirm)({
4581
+ message: "Save KINETICA_URL and KINETICA_USER to .env? (password is never saved)",
4582
+ default: true
4583
+ });
4584
+ if (!shouldSave) return;
4585
+ const filePath = (0, import_path2.join)(dir ?? process.cwd(), ".env");
4586
+ let existing;
4553
4587
  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);
4588
+ existing = await (0, import_promises4.readFile)(filePath, "utf8");
4565
4589
  } catch {
4566
- return;
4567
4590
  }
4591
+ const content = buildEnvContent(url, user, existing);
4592
+ await (0, import_promises4.writeFile)(filePath, content, "utf8");
4593
+ console.error(import_picocolors10.default.dim("Saved to .env"));
4594
+ } catch (err) {
4595
+ const message = err instanceof Error ? err.message : String(err);
4596
+ console.error(import_picocolors10.default.yellow(`Could not save .env file: ${message}`));
4568
4597
  }
4569
4598
  }
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
- `);
4599
+
4600
+ // src/session/verify.ts
4601
+ var import_picocolors13 = __toESM(require("picocolors"));
4602
+ var import_prompts9 = require("@inquirer/prompts");
4603
+
4604
+ // src/session/collect.ts
4605
+ var import_prompts7 = require("@inquirer/prompts");
4606
+ var import_picocolors11 = __toESM(require("picocolors"));
4607
+ async function collectCredentials() {
4608
+ const prompted = /* @__PURE__ */ new Set();
4609
+ const envUrl = process.env.KINETICA_URL;
4610
+ const envUser = process.env.KINETICA_USER;
4611
+ if (envUrl && envUser && process.stdin.isTTY) {
4612
+ console.error(import_picocolors11.default.dim(`Saved connection: ${envUrl} (${envUser})`));
4613
+ const useSaved = await (0, import_prompts7.confirm)({
4614
+ message: "Use saved connection?",
4615
+ default: true
4616
+ });
4617
+ if (!useSaved) {
4618
+ prompted.add("url");
4619
+ prompted.add("user");
4620
+ const url2 = await (0, import_prompts7.input)({ message: "Kinetica endpoint URL:" });
4621
+ const user2 = await (0, import_prompts7.input)({ message: "Admin username:" });
4622
+ const pass2 = await (0, import_prompts7.password)({ message: "Admin password:", mask: "*" });
4623
+ return { credentials: { url: url2, user: user2, pass: pass2 }, prompted };
4582
4624
  }
4583
- } else {
4584
- process.stderr.write(` ${import_picocolors13.default.red(`Error: ${statusResult.error}`)}
4585
- `);
4586
4625
  }
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
- }
4626
+ const url = envUrl ?? (prompted.add("url"), await (0, import_prompts7.input)({ message: "Kinetica endpoint URL:" }));
4627
+ const user = envUser ?? (prompted.add("user"), await (0, import_prompts7.input)({ message: "Admin username:" }));
4628
+ const pass = process.env.KINETICA_PASS ?? await (0, import_prompts7.password)({ message: "Admin password:", mask: "*" });
4629
+ return { credentials: { url, user, pass }, prompted };
4630
+ }
4631
+ async function repromptCredentials() {
4632
+ const user = await (0, import_prompts7.input)({ message: "Admin username:" });
4633
+ const pass = await (0, import_prompts7.password)({ message: "Admin password:", mask: "*" });
4634
+ return { user, pass };
4635
+ }
4636
+
4637
+ // src/session/KineticaSession.ts
4638
+ var REQUEST_TIMEOUT_MS = 3e4;
4639
+ function replacePort(baseUrl, port) {
4640
+ const parsed = new URL(baseUrl);
4641
+ parsed.port = String(port);
4642
+ return parsed.origin;
4643
+ }
4644
+ function createSession(url, user, pass) {
4645
+ const authHeader = "Basic " + Buffer.from(`${user}:${pass}`).toString("base64");
4646
+ const doFetch = async (fullUrl, body) => {
4647
+ if (process.env.DEBUG) {
4648
+ console.error(`[DEBUG] POST ${fullUrl}`);
4599
4649
  }
4600
- } else {
4601
- process.stderr.write(` ${import_picocolors13.default.dim(`Unavailable: ${alertsResult.error}`)}
4602
- `);
4603
- }
4604
- process.stderr.write("\n");
4650
+ return fetch(fullUrl, {
4651
+ method: "POST",
4652
+ headers: {
4653
+ Authorization: authHeader,
4654
+ "Content-Type": "application/json"
4655
+ },
4656
+ body: body !== void 0 ? JSON.stringify(body) : void 0,
4657
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
4658
+ });
4659
+ };
4660
+ return {
4661
+ baseUrl: url,
4662
+ makeRequest: (endpoint, body) => doFetch(`${url}${endpoint}`, body),
4663
+ makeRequestToPort: (port, endpoint, body) => doFetch(`${replacePort(url, port)}${endpoint}`, body)
4664
+ };
4605
4665
  }
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
- );
4666
+
4667
+ // src/session/resolve-url.ts
4668
+ var import_picocolors12 = __toESM(require("picocolors"));
4669
+ var import_prompts8 = require("@inquirer/prompts");
4670
+ var PROBE_TIMEOUT_MS2 = 3e3;
4671
+ var HTTP_PROTOCOL_RE = /^https?:\/\//i;
4672
+ var ANY_PROTOCOL_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
4673
+ function hasProtocol(input5) {
4674
+ return HTTP_PROTOCOL_RE.test(input5);
4675
+ }
4676
+ function stripTrailingSlashes(url) {
4677
+ return url.replace(/\/+$/, "");
4678
+ }
4679
+ async function probeProtocol(url) {
4680
+ try {
4681
+ await fetch(url, {
4682
+ method: "HEAD",
4683
+ signal: AbortSignal.timeout(PROBE_TIMEOUT_MS2)
4684
+ });
4685
+ return true;
4686
+ } catch {
4687
+ return false;
4625
4688
  }
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.
4689
+ }
4690
+ function isHttpsOnly() {
4691
+ return process.env.KINETICA_HTTPS_ONLY === "1";
4692
+ }
4693
+ function isInteractive() {
4694
+ return Boolean(process.stdin.isTTY);
4695
+ }
4696
+ async function confirmHttpFallback(host) {
4697
+ process.stderr.write(
4698
+ "\n" + import_picocolors12.default.red(
4699
+ import_picocolors12.default.bold(
4700
+ ` WARNING: HTTPS unavailable at ${host}.
4701
+ Falling back to plaintext HTTP will transmit your Kinetica credentials in the clear.
4630
4702
  `
4631
4703
  )
4632
- );
4704
+ ) + import_picocolors12.default.dim(
4705
+ ` Set KINETICA_HTTPS_ONLY=1 to refuse this fallback automatically, or pass an explicit http:// prefix to silence this prompt.
4706
+
4707
+ `
4708
+ )
4709
+ );
4710
+ try {
4711
+ return await (0, import_prompts8.confirm)({
4712
+ message: "Continue over plaintext HTTP?",
4713
+ default: false
4714
+ });
4715
+ } catch {
4716
+ return false;
4633
4717
  }
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");
4718
+ }
4719
+ async function resolveUrl(input5) {
4720
+ const trimmed = input5.trim();
4721
+ if (trimmed === "") {
4722
+ return { ok: false, error: "URL is empty" };
4723
+ }
4724
+ const normalized = stripTrailingSlashes(trimmed);
4725
+ if (hasProtocol(normalized)) {
4726
+ return { ok: true, url: normalized };
4727
+ }
4728
+ if (ANY_PROTOCOL_RE.test(normalized)) {
4729
+ const scheme = normalized.split("://")[0];
4730
+ return {
4731
+ ok: false,
4732
+ error: `Unsupported protocol: ${scheme}. Use http:// or https://`
4733
+ };
4734
+ }
4735
+ const httpsUrl = `https://${normalized}`;
4736
+ const httpUrl = `http://${normalized}`;
4737
+ console.error(import_picocolors12.default.dim("Detecting protocol..."));
4738
+ if (await probeProtocol(httpsUrl)) {
4739
+ return { ok: true, url: httpsUrl };
4740
+ }
4741
+ if (isHttpsOnly()) {
4742
+ return {
4743
+ ok: false,
4744
+ error: `HTTPS probe to ${httpsUrl} failed and KINETICA_HTTPS_ONLY=1; refusing to fall back to plaintext HTTP.`
4745
+ };
4746
+ }
4747
+ if (!await probeProtocol(httpUrl)) {
4748
+ return {
4749
+ ok: false,
4750
+ error: `Could not connect to ${normalized} via https:// or http://`
4751
+ };
4752
+ }
4753
+ if (!isInteractive()) {
4754
+ return {
4755
+ ok: false,
4756
+ 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.`
4757
+ };
4758
+ }
4759
+ const approved = await confirmHttpFallback(normalized);
4760
+ if (!approved) {
4761
+ return {
4762
+ ok: false,
4763
+ error: "User declined plaintext HTTP fallback"
4764
+ };
4765
+ }
4766
+ return { ok: true, url: httpUrl };
4767
+ }
4768
+
4769
+ // src/session/verify.ts
4770
+ var MAX_RETRIES = 3;
4771
+ var MAX_REPROMPTS = 2;
4772
+ var DEFAULT_HM_PORT2 = 9300;
4773
+ function extractVersion(responseBody) {
4774
+ try {
4775
+ const outer = JSON.parse(responseBody);
4776
+ if (typeof outer.data_str !== "string") return void 0;
4777
+ const inner = JSON.parse(outer.data_str);
4778
+ const systemStr = inner.status_map?.system;
4779
+ if (typeof systemStr !== "string") return void 0;
4780
+ const system = JSON.parse(systemStr);
4781
+ return typeof system.version === "string" ? system.version : void 0;
4782
+ } catch {
4783
+ return void 0;
4678
4784
  }
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;
4785
+ }
4786
+ function extractVersionFromHostManager(responseBody) {
4697
4787
  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
- }
4788
+ const parsed = JSON.parse(responseBody);
4789
+ return typeof parsed.version === "string" ? parsed.version : void 0;
4790
+ } catch {
4791
+ return void 0;
4792
+ }
4793
+ }
4794
+ async function probeHostManager(session2) {
4795
+ if (!session2.makeRequestToPort) {
4796
+ return { ok: false };
4797
+ }
4798
+ try {
4799
+ const response = await session2.makeRequestToPort(DEFAULT_HM_PORT2, "/", void 0);
4800
+ if (!response.ok) return { ok: false };
4801
+ const body = await response.text();
4802
+ return { ok: true, version: extractVersionFromHostManager(body) };
4803
+ } catch {
4804
+ return { ok: false };
4805
+ }
4806
+ }
4807
+ async function verifyConnectivity(session2) {
4808
+ const response = await session2.makeRequest("/show/system/status", {});
4809
+ if (!response.ok) {
4810
+ const body2 = await response.text();
4811
+ throw new Error(`HTTP ${response.status}: ${body2}`);
4812
+ }
4813
+ const body = await response.text();
4814
+ return extractVersion(body);
4815
+ }
4816
+ function isCredentialError(errorMessage) {
4817
+ return errorMessage.startsWith("HTTP 401") || errorMessage.startsWith("HTTP 403");
4818
+ }
4819
+ async function connectWithRetry() {
4820
+ const { credentials, prompted } = await collectCredentials();
4821
+ const resolved = await resolveUrl(credentials.url);
4822
+ if (!resolved.ok) {
4823
+ console.error(import_picocolors13.default.red(resolved.error));
4824
+ process.exit(1);
4825
+ }
4826
+ const resolvedUrl = resolved.url;
4827
+ let currentUser = credentials.user;
4828
+ let currentPass = credentials.pass;
4829
+ let wasReprompted = false;
4830
+ let repromptCount = 0;
4831
+ let session2 = createSession(resolvedUrl, currentUser, currentPass);
4832
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
4833
+ try {
4834
+ const kineticaVersion = await verifyConnectivity(session2);
4835
+ console.error(import_picocolors13.default.green("Connected to Kinetica successfully."));
4836
+ if (prompted.size > 0 || wasReprompted) {
4837
+ await offerSaveCredentials(resolvedUrl, currentUser);
4838
+ }
4839
+ return { session: session2, kineticaVersion, degraded: false };
4840
+ } catch (err) {
4841
+ const msg = err instanceof Error ? err.message : String(err);
4842
+ console.error(import_picocolors13.default.red(`Connection failed (attempt ${attempt}/${MAX_RETRIES}): ${msg}`));
4843
+ if (isCredentialError(msg)) {
4844
+ if (process.stdin.isTTY && repromptCount < MAX_REPROMPTS) {
4845
+ const shouldRetry = await (0, import_prompts9.confirm)({
4846
+ message: "Credentials may be incorrect. Re-enter?",
4847
+ default: true
4848
+ });
4849
+ if (shouldRetry) {
4850
+ const fresh = await repromptCredentials();
4851
+ currentUser = fresh.user;
4852
+ currentPass = fresh.pass;
4853
+ wasReprompted = true;
4854
+ repromptCount++;
4855
+ session2 = createSession(resolvedUrl, currentUser, currentPass);
4856
+ attempt = 0;
4857
+ continue;
4710
4858
  }
4711
4859
  }
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(
4860
+ console.error(import_picocolors13.default.red("Authentication failed. Exiting."));
4861
+ process.exit(1);
4862
+ }
4863
+ if (attempt === MAX_RETRIES) {
4864
+ console.error(import_picocolors13.default.yellow("DB engine unreachable. Probing host manager on port 9300..."));
4865
+ const hmResult = await probeHostManager(session2);
4866
+ if (hmResult.ok) {
4867
+ console.error(
4787
4868
  import_picocolors13.default.yellow(
4788
- `
4789
- API error${statusStr}. Retrying (attempt ${retryMsg.attempt}/${retryMsg.max_retries}) in ${delaySec}s...
4790
- `
4869
+ "Connected in DEGRADED MODE (host manager only). Most diagnostic tools will be unavailable."
4791
4870
  )
4792
4871
  );
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"));
4872
+ if (prompted.size > 0 || wasReprompted) {
4873
+ await offerSaveCredentials(resolvedUrl, currentUser);
4874
+ }
4875
+ return { session: session2, kineticaVersion: hmResult.version, degraded: true };
4817
4876
  }
4877
+ console.error(import_picocolors13.default.red("Host manager also unreachable. Exiting."));
4878
+ process.exit(1);
4818
4879
  }
4819
4880
  }
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
4881
  }
4882
+ throw new Error("unreachable");
4853
4883
  }
4854
4884
 
4855
4885
  // src/cli/index.ts
@@ -4925,8 +4955,13 @@ async function main() {
4925
4955
  }
4926
4956
  }
4927
4957
  loadEnvFile();
4958
+ printBanner();
4959
+ if (model === void 0 && process.stdin.isTTY) {
4960
+ model = await selectModel();
4961
+ }
4928
4962
  const effectiveModel = model ?? DEFAULT_AGENT_MODEL;
4929
- printBanner(effectiveModel);
4963
+ process.stderr.write(import_picocolors14.default.dim(`model: ${effectiveModel}
4964
+ `));
4930
4965
  const authResult = await authenticateAnthropic({ forceLogin, loginMethod, loginOrgUUID });
4931
4966
  if (authResult.method === "oauth") {
4932
4967
  const acctInfo = authResult.email ? ` (${authResult.email})` : "";