@kinetica/admin-agent 0.1.3 → 0.2.1

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.
@@ -31,15 +31,16 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  // src/cli/index.ts
32
32
  var cli_exports = {};
33
33
  __export(cli_exports, {
34
+ chooseBundleSessionVersion: () => chooseBundleSessionVersion,
34
35
  getSession: () => getSession,
35
36
  main: () => main,
36
37
  verbose: () => verbose
37
38
  });
38
39
  module.exports = __toCommonJS(cli_exports);
39
- var import_picocolors14 = __toESM(require("picocolors"));
40
+ var import_picocolors15 = __toESM(require("picocolors"));
40
41
 
41
42
  // src/cli/banner.ts
42
- var import_picocolors = __toESM(require("picocolors"));
43
+ var import_picocolors2 = __toESM(require("picocolors"));
43
44
 
44
45
  // src/cli/version.ts
45
46
  var import_fs = require("fs");
@@ -61,6 +62,29 @@ function getVersion() {
61
62
  }
62
63
  }
63
64
 
65
+ // src/output/brand-colors.ts
66
+ var import_picocolors = __toESM(require("picocolors"));
67
+ var BRAND_PURPLE = [147, 51, 234];
68
+ var BRAND_PINK = [236, 72, 153];
69
+ var SOFTEN_TARGET = [210, 210, 210];
70
+ var SOFTEN_PURPLE = 0.55;
71
+ var SOFTEN_PINK = 0.45;
72
+ function soften([r, g, b], amount) {
73
+ const [tr, tg, tb] = SOFTEN_TARGET;
74
+ return [
75
+ Math.round(r + (tr - r) * amount),
76
+ Math.round(g + (tg - g) * amount),
77
+ Math.round(b + (tb - b) * amount)
78
+ ];
79
+ }
80
+ var TEXT_PURPLE = soften(BRAND_PURPLE, SOFTEN_PURPLE);
81
+ var TEXT_PINK = soften(BRAND_PINK, SOFTEN_PINK);
82
+ function truecolor([r, g, b]) {
83
+ return (s) => import_picocolors.default.isColorSupported ? `\x1B[38;2;${r};${g};${b}m${s}\x1B[39m` : s;
84
+ }
85
+ var purple = truecolor(TEXT_PURPLE);
86
+ var pink = truecolor(TEXT_PINK);
87
+
64
88
  // src/cli/banner.ts
65
89
  var LOGO = ` \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557
66
90
  \u2588\u2588\u2551 \u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
@@ -84,14 +108,12 @@ function dimRgb(color, factor) {
84
108
  }
85
109
  var SHADOW_CHARS = /* @__PURE__ */ new Set(["\u2557", "\u2554", "\u2551", "\u255D", "\u255A", "\u2550"]);
86
110
  var DIM_FACTOR = 0.4;
87
- function gradientize(text) {
88
- const PURPLE = [147, 51, 234];
89
- const HOT_PINK = [236, 72, 153];
111
+ function gradientize(text2) {
90
112
  const RESET = "\x1B[0m";
91
- const lines = text.split("\n");
113
+ const lines = text2.split("\n");
92
114
  const maxIdx = Math.max(lines.length - 1, 1);
93
115
  return lines.map((line, i) => {
94
- const bright = lerpRgb(PURPLE, HOT_PINK, i / maxIdx);
116
+ const bright = lerpRgb(BRAND_PURPLE, BRAND_PINK, i / maxIdx);
95
117
  const dim = dimRgb(bright, DIM_FACTOR);
96
118
  let result = "";
97
119
  let currentMode = null;
@@ -109,20 +131,53 @@ function gradientize(text) {
109
131
  }
110
132
  function printBanner(model) {
111
133
  const version = getVersion();
112
- const subtitle = `admin-agent ${import_picocolors.default.dim(`v${version}`)}`;
134
+ const subtitle = `admin-agent ${import_picocolors2.default.dim(`v${version}`)}`;
113
135
  const header = model ? `${subtitle}
114
- ${import_picocolors.default.dim(`Model: ${model}`)}` : subtitle;
136
+ ${import_picocolors2.default.dim(`Model: ${model}`)}` : subtitle;
115
137
  process.stderr.write("\n\n" + gradientize(LOGO) + "\n\n" + header + "\n");
116
138
  return subtitle;
117
139
  }
118
140
 
119
- // src/cli/select-model.ts
120
- var import_prompts5 = require("@inquirer/prompts");
141
+ // src/output/themed-prompts.ts
142
+ var import_prompts = require("@inquirer/prompts");
143
+
144
+ // src/output/prompt-theme.ts
145
+ var PROMPT_THEME = {
146
+ style: {
147
+ answer: pink,
148
+ highlight: pink
149
+ }
150
+ };
151
+
152
+ // src/output/themed-prompts.ts
153
+ function input(...args) {
154
+ const [config, ...rest] = args;
155
+ return (0, import_prompts.input)({ theme: PROMPT_THEME, ...config }, ...rest);
156
+ }
157
+ function confirm(...args) {
158
+ const [config, ...rest] = args;
159
+ return (0, import_prompts.confirm)({ theme: PROMPT_THEME, ...config }, ...rest);
160
+ }
161
+ function password(...args) {
162
+ const [config, ...rest] = args;
163
+ return (0, import_prompts.password)({ theme: PROMPT_THEME, ...config }, ...rest);
164
+ }
165
+ function select(...args) {
166
+ const [config, ...rest] = args;
167
+ return (0, import_prompts.select)({ theme: PROMPT_THEME, ...config }, ...rest);
168
+ }
169
+ function search(...args) {
170
+ const [config, ...rest] = args;
171
+ return (0, import_prompts.search)({ theme: PROMPT_THEME, ...config }, ...rest);
172
+ }
173
+ function checkbox(...args) {
174
+ const [config, ...rest] = args;
175
+ return (0, import_prompts.checkbox)({ theme: PROMPT_THEME, ...config }, ...rest);
176
+ }
121
177
 
122
178
  // 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"));
179
+ var import_claude_agent_sdk5 = require("@anthropic-ai/claude-agent-sdk");
180
+ var import_picocolors9 = __toESM(require("picocolors"));
126
181
 
127
182
  // src/agent/diagnostic-sql.ts
128
183
  function has(columns, name) {
@@ -499,7 +554,7 @@ var BUILDER_REGISTRY = [
499
554
  // src/tools/index.ts
500
555
  var import_claude_agent_sdk2 = require("@anthropic-ai/claude-agent-sdk");
501
556
  var import_zod16 = require("zod");
502
- var import_picocolors4 = __toESM(require("picocolors"));
557
+ var import_picocolors5 = __toESM(require("picocolors"));
503
558
 
504
559
  // src/approval/registry.ts
505
560
  var DEFAULT_READ_ONLY_TOOLS = /* @__PURE__ */ new Set();
@@ -556,7 +611,7 @@ function formatTableArray(rows) {
556
611
  const colWidths = headers.map(
557
612
  (h, i) => Math.max(h.length, ...cells.map((row) => row[i].length), 3)
558
613
  );
559
- const pad = (text, col) => text.padEnd(colWidths[col]);
614
+ const pad = (text2, col) => text2.padEnd(colWidths[col]);
560
615
  const headerRow = `| ${headers.map((h, i) => pad(h, i)).join(" | ")} |`;
561
616
  const separatorRow = `| ${colWidths.map((w) => "-".repeat(w)).join(" | ")} |`;
562
617
  const dataRows = cells.map((row) => `| ${row.map((cell, i) => pad(cell, i)).join(" | ")} |`);
@@ -585,13 +640,13 @@ var DEFAULT_TRUNCATION = {
585
640
  };
586
641
 
587
642
  // src/output/truncate.ts
588
- function truncateOutput(text, options = DEFAULT_TRUNCATION) {
589
- if (text === "") return "";
590
- const lines = text.split("\n");
643
+ function truncateOutput(text2, options = DEFAULT_TRUNCATION) {
644
+ if (text2 === "") return "";
645
+ const lines = text2.split("\n");
591
646
  const { headLines, tailLines } = options;
592
647
  const threshold = headLines + tailLines;
593
648
  if (lines.length <= threshold) {
594
- return text;
649
+ return text2;
595
650
  }
596
651
  const truncatedCount = lines.length - headLines - tailLines;
597
652
  const head = lines.slice(0, headLines);
@@ -853,7 +908,7 @@ var GetSystemPropertiesSchema = import_zod.z.object({
853
908
  category: import_zod.z.string().optional(),
854
909
  key_pattern: import_zod.z.string().optional()
855
910
  });
856
- async function getSystemProperties(session2, input5) {
911
+ async function getSystemProperties(session2, input2) {
857
912
  try {
858
913
  const response = await session2.makeRequest("/show/system/properties", {
859
914
  options: {}
@@ -883,7 +938,7 @@ async function getSystemProperties(session2, input5) {
883
938
  const inner = parseDataStr(parsed.data_str, raw);
884
939
  if (!inner.ok) return inner;
885
940
  const propertyMap = inner.data?.property_map ?? {};
886
- const filteredMap = applyFilters(propertyMap, input5);
941
+ const filteredMap = applyFilters(propertyMap, input2);
887
942
  return {
888
943
  ok: true,
889
944
  data: flatObjectToRows(filteredMap, "property", "value"),
@@ -899,8 +954,8 @@ async function getSystemProperties(session2, input5) {
899
954
  };
900
955
  }
901
956
  }
902
- function applyFilters(propertyMap, input5) {
903
- const { category, key_pattern } = input5;
957
+ function applyFilters(propertyMap, input2) {
958
+ const { category, key_pattern } = input2;
904
959
  if (category === void 0 && key_pattern === void 0) {
905
960
  return propertyMap;
906
961
  }
@@ -1136,7 +1191,7 @@ var GetLogsSchema = import_zod2.z.object({
1136
1191
  message: "duration and start_time/end_time are mutually exclusive -- use one or the other, not both"
1137
1192
  }
1138
1193
  );
1139
- function buildStubResponse(input5) {
1194
+ function buildStubResponse(input2) {
1140
1195
  return {
1141
1196
  ok: true,
1142
1197
  data: {
@@ -1144,39 +1199,39 @@ function buildStubResponse(input5) {
1144
1199
  endpoint: "/admin/show/logs",
1145
1200
  status: "stub",
1146
1201
  requested_params: {
1147
- source: input5.source,
1148
- min_severity: input5.min_severity,
1149
- duration: input5.duration,
1150
- start_time: input5.start_time,
1151
- end_time: input5.end_time,
1152
- node_id: input5.node_id,
1153
- limit: input5.limit
1202
+ source: input2.source,
1203
+ min_severity: input2.min_severity,
1204
+ duration: input2.duration,
1205
+ start_time: input2.start_time,
1206
+ end_time: input2.end_time,
1207
+ node_id: input2.node_id,
1208
+ limit: input2.limit
1154
1209
  }
1155
1210
  }
1156
1211
  };
1157
1212
  }
1158
- async function getLogs(session2, input5) {
1213
+ async function getLogs(session2, input2) {
1159
1214
  const params = {
1160
- source: input5.source,
1161
- severity: input5.min_severity,
1162
- limit: input5.limit
1215
+ source: input2.source,
1216
+ severity: input2.min_severity,
1217
+ limit: input2.limit
1163
1218
  };
1164
- if (input5.duration !== void 0) {
1165
- params.duration = input5.duration;
1219
+ if (input2.duration !== void 0) {
1220
+ params.duration = input2.duration;
1166
1221
  }
1167
- if (input5.start_time !== void 0) {
1168
- params.start_time = input5.start_time;
1222
+ if (input2.start_time !== void 0) {
1223
+ params.start_time = input2.start_time;
1169
1224
  }
1170
- if (input5.end_time !== void 0) {
1171
- params.end_time = input5.end_time;
1225
+ if (input2.end_time !== void 0) {
1226
+ params.end_time = input2.end_time;
1172
1227
  }
1173
- if (input5.node_id !== void 0) {
1174
- params.node_id = input5.node_id;
1228
+ if (input2.node_id !== void 0) {
1229
+ params.node_id = input2.node_id;
1175
1230
  }
1176
1231
  try {
1177
1232
  const response = await session2.makeRequest("/admin/show/logs", params);
1178
1233
  if (!response.ok) {
1179
- return buildStubResponse(input5);
1234
+ return buildStubResponse(input2);
1180
1235
  }
1181
1236
  const raw = await response.text();
1182
1237
  try {
@@ -1186,10 +1241,10 @@ async function getLogs(session2, input5) {
1186
1241
  data
1187
1242
  };
1188
1243
  } catch {
1189
- return buildStubResponse(input5);
1244
+ return buildStubResponse(input2);
1190
1245
  }
1191
1246
  } catch {
1192
- return buildStubResponse(input5);
1247
+ return buildStubResponse(input2);
1193
1248
  }
1194
1249
  }
1195
1250
 
@@ -1221,7 +1276,7 @@ var DEFAULT_SCRUB_PATTERNS = [
1221
1276
  ];
1222
1277
  function scrubCredentials(content, patterns = DEFAULT_SCRUB_PATTERNS) {
1223
1278
  const configRedacted = redactConfigSecrets(content);
1224
- return patterns.reduce((text, pattern) => text.replace(pattern, "[REDACTED]"), configRedacted);
1279
+ return patterns.reduce((text2, pattern) => text2.replace(pattern, "[REDACTED]"), configRedacted);
1225
1280
  }
1226
1281
 
1227
1282
  // src/tools/rest/show-configuration.ts
@@ -1334,12 +1389,12 @@ var ResourceGroupsSchema = import_zod4.z.object({
1334
1389
  names: import_zod4.z.array(import_zod4.z.string()).optional().default([""]),
1335
1390
  show_tier_usage: import_zod4.z.boolean().optional()
1336
1391
  });
1337
- async function getResourceGroups(session2, input5) {
1392
+ async function getResourceGroups(session2, input2) {
1338
1393
  try {
1339
1394
  const response = await session2.makeRequest("/show/resourcegroups", {
1340
- names: input5.names,
1395
+ names: input2.names,
1341
1396
  options: {
1342
- show_tier_usage: String(input5.show_tier_usage ?? false),
1397
+ show_tier_usage: String(input2.show_tier_usage ?? false),
1343
1398
  show_default_values: "true",
1344
1399
  show_default_group: "true"
1345
1400
  }
@@ -1390,18 +1445,18 @@ var VerifyDbSchema = import_zod5.z.object({
1390
1445
  verify_persist: import_zod5.z.boolean().optional(),
1391
1446
  verify_rank0: import_zod5.z.boolean().optional()
1392
1447
  });
1393
- async function verifyDb(session2, input5) {
1448
+ async function verifyDb(session2, input2) {
1394
1449
  const options = {
1395
1450
  concurrent_safe: "true"
1396
1451
  };
1397
- if (input5.verify_nulls !== void 0) {
1398
- options.verify_nulls = String(input5.verify_nulls);
1452
+ if (input2.verify_nulls !== void 0) {
1453
+ options.verify_nulls = String(input2.verify_nulls);
1399
1454
  }
1400
- if (input5.verify_persist !== void 0) {
1401
- options.verify_persist = String(input5.verify_persist);
1455
+ if (input2.verify_persist !== void 0) {
1456
+ options.verify_persist = String(input2.verify_persist);
1402
1457
  }
1403
- if (input5.verify_rank0 !== void 0) {
1404
- options.verify_rank0 = String(input5.verify_rank0);
1458
+ if (input2.verify_rank0 !== void 0) {
1459
+ options.verify_rank0 = String(input2.verify_rank0);
1405
1460
  }
1406
1461
  try {
1407
1462
  const response = await session2.makeRequest("/admin/verifydb", { options });
@@ -1454,10 +1509,10 @@ var import_zod6 = require("zod");
1454
1509
  var ShowSecuritySchema = import_zod6.z.object({
1455
1510
  names: import_zod6.z.array(import_zod6.z.string()).optional().default([""])
1456
1511
  });
1457
- async function showSecurity(session2, input5) {
1512
+ async function showSecurity(session2, input2) {
1458
1513
  try {
1459
1514
  const response = await session2.makeRequest("/show/security", {
1460
- names: input5.names,
1515
+ names: input2.names,
1461
1516
  options: {}
1462
1517
  });
1463
1518
  if (!response.ok) {
@@ -1553,10 +1608,10 @@ var ShowTableSchema = import_zod7.z.object({
1553
1608
  get_access_data: import_zod7.z.boolean().optional(),
1554
1609
  get_column_info: import_zod7.z.boolean().optional()
1555
1610
  });
1556
- function resolveColumnInfoOption(input5) {
1557
- if (input5.get_column_info === true) return "true";
1558
- if (input5.get_column_info === false) return "false";
1559
- return input5.table_name !== "" ? "true" : "false";
1611
+ function resolveColumnInfoOption(input2) {
1612
+ if (input2.get_column_info === true) return "true";
1613
+ if (input2.get_column_info === false) return "false";
1614
+ return input2.table_name !== "" ? "true" : "false";
1560
1615
  }
1561
1616
  function parseColumnProperties(propertiesJson) {
1562
1617
  try {
@@ -1622,19 +1677,19 @@ async function fetchIndexes(session2, tableName) {
1622
1677
  return [];
1623
1678
  }
1624
1679
  }
1625
- async function showTable(session2, input5) {
1680
+ async function showTable(session2, input2) {
1626
1681
  const options = {
1627
- get_sizes: String(input5.get_sizes ?? true),
1682
+ get_sizes: String(input2.get_sizes ?? true),
1628
1683
  show_children: "false",
1629
1684
  no_error_if_not_exists: "true",
1630
- get_column_info: resolveColumnInfoOption(input5)
1685
+ get_column_info: resolveColumnInfoOption(input2)
1631
1686
  };
1632
- if (input5.get_access_data !== void 0) {
1633
- options.get_access_data = String(input5.get_access_data);
1687
+ if (input2.get_access_data !== void 0) {
1688
+ options.get_access_data = String(input2.get_access_data);
1634
1689
  }
1635
1690
  try {
1636
1691
  const response = await session2.makeRequest("/show/table", {
1637
- table_name: input5.table_name,
1692
+ table_name: input2.table_name,
1638
1693
  options
1639
1694
  });
1640
1695
  if (!response.ok) {
@@ -1686,7 +1741,7 @@ async function showTable(session2, input5) {
1686
1741
  properties: properties[0] ?? ""
1687
1742
  };
1688
1743
  const columns = buildColumnEntries(typeSchemas?.[0], typeSchemas ? properties[0] : void 0);
1689
- const indexes = await fetchIndexes(session2, input5.table_name);
1744
+ const indexes = await fetchIndexes(session2, input2.table_name);
1690
1745
  return {
1691
1746
  ok: true,
1692
1747
  data: { table, columns, indexes }
@@ -1721,16 +1776,16 @@ var ResourceObjectsSchema = import_zod8.z.object({
1721
1776
  order_by: import_zod8.z.string().optional(),
1722
1777
  limit: import_zod8.z.number().int().min(1).max(1e4).optional().default(100)
1723
1778
  });
1724
- async function getResourceObjects(session2, input5) {
1779
+ async function getResourceObjects(session2, input2) {
1725
1780
  const options = {
1726
- table_names: input5.table_names ?? "*",
1727
- limit: String(input5.limit ?? 100)
1781
+ table_names: input2.table_names ?? "*",
1782
+ limit: String(input2.limit ?? 100)
1728
1783
  };
1729
- if (input5.tiers !== void 0) {
1730
- options.tiers = input5.tiers;
1784
+ if (input2.tiers !== void 0) {
1785
+ options.tiers = input2.tiers;
1731
1786
  }
1732
- if (input5.order_by !== void 0) {
1733
- options.order_by = input5.order_by;
1787
+ if (input2.order_by !== void 0) {
1788
+ options.order_by = input2.order_by;
1734
1789
  }
1735
1790
  try {
1736
1791
  const response = await session2.makeRequest("/show/resource/objects", {
@@ -2134,8 +2189,8 @@ function computeVerification(requestedMap, afterState) {
2134
2189
  }
2135
2190
  return "confirmed";
2136
2191
  }
2137
- async function alterSystemProperties(session2, input5) {
2138
- const requestedKeys = Object.keys(input5.property_updates_map);
2192
+ async function alterSystemProperties(session2, input2) {
2193
+ const requestedKeys = Object.keys(input2.property_updates_map);
2139
2194
  const disallowed = findDisallowedProperties(requestedKeys);
2140
2195
  if (disallowed.length > 0) {
2141
2196
  return {
@@ -2150,7 +2205,7 @@ async function alterSystemProperties(session2, input5) {
2150
2205
  let rawText;
2151
2206
  try {
2152
2207
  mutationResponse = await session2.makeRequest("/alter/system/properties", {
2153
- property_updates_map: input5.property_updates_map
2208
+ property_updates_map: input2.property_updates_map
2154
2209
  });
2155
2210
  rawText = await mutationResponse.text();
2156
2211
  } catch (error) {
@@ -2212,7 +2267,7 @@ async function alterSystemProperties(session2, input5) {
2212
2267
  afterState = Object.fromEntries(
2213
2268
  requestedKeys.filter((key) => Object.prototype.hasOwnProperty.call(verifyPropertyMap, key)).map((key) => [key, verifyPropertyMap[key]])
2214
2269
  );
2215
- verification = computeVerification(input5.property_updates_map, afterState);
2270
+ verification = computeVerification(input2.property_updates_map, afterState);
2216
2271
  }
2217
2272
  }
2218
2273
  } catch {
@@ -2356,29 +2411,29 @@ async function readShardState(session2) {
2356
2411
  return {};
2357
2412
  }
2358
2413
  }
2359
- async function adminRebalance(session2, input5) {
2414
+ async function adminRebalance(session2, input2) {
2360
2415
  const beforeState = await readShardState(session2);
2361
2416
  const options = {};
2362
- if (input5.rebalance_sharded_data !== void 0) {
2363
- options.rebalance_sharded_data = String(input5.rebalance_sharded_data);
2417
+ if (input2.rebalance_sharded_data !== void 0) {
2418
+ options.rebalance_sharded_data = String(input2.rebalance_sharded_data);
2364
2419
  }
2365
- if (input5.rebalance_unsharded_data !== void 0) {
2366
- options.rebalance_unsharded_data = String(input5.rebalance_unsharded_data);
2420
+ if (input2.rebalance_unsharded_data !== void 0) {
2421
+ options.rebalance_unsharded_data = String(input2.rebalance_unsharded_data);
2367
2422
  }
2368
- if (input5.table_includes !== void 0) {
2369
- options.table_includes = input5.table_includes;
2423
+ if (input2.table_includes !== void 0) {
2424
+ options.table_includes = input2.table_includes;
2370
2425
  }
2371
- if (input5.table_excludes !== void 0) {
2372
- options.table_excludes = input5.table_excludes;
2426
+ if (input2.table_excludes !== void 0) {
2427
+ options.table_excludes = input2.table_excludes;
2373
2428
  }
2374
- if (input5.aggressiveness !== void 0) {
2375
- options.aggressiveness = String(input5.aggressiveness);
2429
+ if (input2.aggressiveness !== void 0) {
2430
+ options.aggressiveness = String(input2.aggressiveness);
2376
2431
  }
2377
- if (input5.compact_after_rebalance !== void 0) {
2378
- options.compact_after_rebalance = String(input5.compact_after_rebalance);
2432
+ if (input2.compact_after_rebalance !== void 0) {
2433
+ options.compact_after_rebalance = String(input2.compact_after_rebalance);
2379
2434
  }
2380
- if (input5.compact_only !== void 0) {
2381
- options.compact_only = String(input5.compact_only);
2435
+ if (input2.compact_only !== void 0) {
2436
+ options.compact_only = String(input2.compact_only);
2382
2437
  }
2383
2438
  try {
2384
2439
  const response = await session2.makeRequest("/admin/rebalance", { options });
@@ -2451,7 +2506,7 @@ async function readCurrentConfig(session2) {
2451
2506
  return void 0;
2452
2507
  }
2453
2508
  }
2454
- async function alterConfiguration(session2, input5) {
2509
+ async function alterConfiguration(session2, input2) {
2455
2510
  if (!session2.makeRequestToPort) {
2456
2511
  return {
2457
2512
  ok: false,
@@ -2467,7 +2522,7 @@ async function alterConfiguration(session2, input5) {
2467
2522
  let rawText;
2468
2523
  try {
2469
2524
  mutationResponse = await session2.makeRequestToPort(hmPort, "/admin/alter/configuration", {
2470
- config_string: input5.config_string
2525
+ config_string: input2.config_string
2471
2526
  });
2472
2527
  rawText = await mutationResponse.text();
2473
2528
  } catch (error) {
@@ -2522,28 +2577,26 @@ async function alterConfiguration(session2, input5) {
2522
2577
 
2523
2578
  // src/tools/mutation/alter-table-columns.ts
2524
2579
  var import_zod15 = require("zod");
2525
- var import_prompts2 = require("@inquirer/prompts");
2526
- var import_picocolors3 = __toESM(require("picocolors"));
2580
+ var import_picocolors4 = __toESM(require("picocolors"));
2527
2581
  var import_claude_agent_sdk = require("@anthropic-ai/claude-agent-sdk");
2528
2582
 
2529
2583
  // src/approval/checklist.ts
2530
- var import_prompts = require("@inquirer/prompts");
2531
- var import_picocolors2 = __toESM(require("picocolors"));
2532
- var DIVIDER = import_picocolors2.default.dim("\u2500".repeat(60));
2584
+ var import_picocolors3 = __toESM(require("picocolors"));
2585
+ var DIVIDER = import_picocolors3.default.dim("\u2500".repeat(60));
2533
2586
  function renderChecklist(header, summary, items) {
2534
2587
  const lines = [
2535
2588
  "",
2536
2589
  DIVIDER,
2537
- ` ${import_picocolors2.default.bold(import_picocolors2.default.yellow(header))}`,
2590
+ ` ${import_picocolors3.default.bold(import_picocolors3.default.yellow(header))}`,
2538
2591
  "",
2539
- ` ${import_picocolors2.default.dim("Summary:")} ${summary}`,
2592
+ ` ${import_picocolors3.default.dim("Summary:")} ${summary}`,
2540
2593
  "",
2541
- ` ${import_picocolors2.default.bold(`${items.length} proposed column change(s):`)}`,
2594
+ ` ${import_picocolors3.default.bold(`${items.length} proposed column change(s):`)}`,
2542
2595
  ""
2543
2596
  ];
2544
2597
  for (let i = 0; i < items.length; i++) {
2545
2598
  const item = items[i];
2546
- lines.push(` ${import_picocolors2.default.bold(`${i + 1}.`)} ${item.label}`, ` ${import_picocolors2.default.dim(item.description)}`);
2599
+ lines.push(` ${import_picocolors3.default.bold(`${i + 1}.`)} ${item.label}`, ` ${import_picocolors3.default.dim(item.description)}`);
2547
2600
  }
2548
2601
  lines.push("", DIVIDER, "");
2549
2602
  return lines.join("\n");
@@ -2552,7 +2605,7 @@ async function showChecklist(header, summary, items) {
2552
2605
  const panel = renderChecklist(header, summary, items);
2553
2606
  process.stderr.write(panel);
2554
2607
  try {
2555
- const selected = await (0, import_prompts.checkbox)({
2608
+ const selected = await checkbox({
2556
2609
  message: "Select columns to alter (space to toggle, enter to confirm):",
2557
2610
  choices: items.map((item, i) => ({
2558
2611
  value: i,
@@ -2587,12 +2640,12 @@ function buildAlterTableSql(tableName, columns) {
2587
2640
  return `ALTER TABLE ${tableName}
2588
2641
  ${clauses.join(",\n")}`;
2589
2642
  }
2590
- var SQL_DIVIDER = import_picocolors3.default.dim("\u2500".repeat(60));
2643
+ var SQL_DIVIDER = import_picocolors4.default.dim("\u2500".repeat(60));
2591
2644
  async function confirmSqlExecution(sql) {
2592
2645
  const panel = [
2593
2646
  "",
2594
2647
  SQL_DIVIDER,
2595
- ` ${import_picocolors3.default.bold(import_picocolors3.default.yellow("Generated SQL:"))}`,
2648
+ ` ${import_picocolors4.default.bold(import_picocolors4.default.yellow("Generated SQL:"))}`,
2596
2649
  "",
2597
2650
  sql.split("\n").map((line) => ` ${line}`).join("\n"),
2598
2651
  "",
@@ -2601,7 +2654,7 @@ async function confirmSqlExecution(sql) {
2601
2654
  ].join("\n");
2602
2655
  process.stderr.write(panel);
2603
2656
  try {
2604
- const response = await (0, import_prompts2.input)({ message: "Execute? (y/n):" });
2657
+ const response = await input({ message: "Execute? (y/n):" });
2605
2658
  return response.trim().toLowerCase() === "y";
2606
2659
  } catch {
2607
2660
  return false;
@@ -2714,7 +2767,7 @@ function fingerprint(value) {
2714
2767
  }
2715
2768
  function scrubCredentialPatterns(value) {
2716
2769
  return CREDENTIAL_PATTERNS.reduce(
2717
- (text, { regex, replacement }) => text.replace(regex, replacement),
2770
+ (text2, { regex, replacement }) => text2.replace(regex, replacement),
2718
2771
  value
2719
2772
  );
2720
2773
  }
@@ -2739,8 +2792,8 @@ function redactNamedField(key, value) {
2739
2792
  }
2740
2793
  return redactValue(value);
2741
2794
  }
2742
- function redactAuditInput(input5) {
2743
- return Object.fromEntries(Object.entries(input5).map(([k, v]) => [k, redactNamedField(k, v)]));
2795
+ function redactAuditInput(input2) {
2796
+ return Object.fromEntries(Object.entries(input2).map(([k, v]) => [k, redactNamedField(k, v)]));
2744
2797
  }
2745
2798
 
2746
2799
  // src/tools/index.ts
@@ -2769,24 +2822,22 @@ var DIAGNOSTIC_TOOL_NAMES = [
2769
2822
  "kinetica_resource_objects",
2770
2823
  "kinetica_host_manager_status"
2771
2824
  ];
2772
- function createDiagnosticRegistry() {
2773
- return DIAGNOSTIC_TOOL_NAMES.reduce(
2774
- (registry, name) => registry.registerReadOnlyTool(name),
2775
- createRegistry()
2776
- );
2777
- }
2778
2825
  function applyOutputPipeline(result) {
2779
2826
  const payload = result.ok ? result.data : result;
2780
- return truncateOutput(formatOutput(payload));
2827
+ const body = formatOutput(payload);
2828
+ const withNote = result.ok && result.note ? `${result.note}
2829
+
2830
+ ${body}` : body;
2831
+ return truncateOutput(withNote);
2781
2832
  }
2782
- function logMutationAudit(toolName, result, input5) {
2783
- const statusLabel = result.ok ? import_picocolors4.default.bold(import_picocolors4.default.green("EXECUTED")) : import_picocolors4.default.bold(import_picocolors4.default.red("FAILED"));
2784
- const redacted = redactAuditInput(input5);
2833
+ function logMutationAudit(toolName, result, input2) {
2834
+ const statusLabel = result.ok ? import_picocolors5.default.bold(import_picocolors5.default.green("EXECUTED")) : import_picocolors5.default.bold(import_picocolors5.default.red("FAILED"));
2835
+ const redacted = redactAuditInput(input2);
2785
2836
  const inputSummary = Object.entries(redacted).map(([k, v]) => `${k}=${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
2786
2837
  const displayName = formatToolName(toolName);
2787
2838
  process.stderr.write(
2788
- ` ${import_picocolors4.default.dim("MUTATION")} ${statusLabel} ${displayName}
2789
- ${import_picocolors4.default.dim(inputSummary)}
2839
+ ` ${import_picocolors5.default.dim("MUTATION")} ${statusLabel} ${displayName}
2840
+ ${import_picocolors5.default.dim(inputSummary)}
2790
2841
 
2791
2842
  `
2792
2843
  );
@@ -3191,6 +3242,26 @@ function buildEvidenceChecklist() {
3191
3242
  ].join("\n");
3192
3243
  }
3193
3244
 
3245
+ // src/agent/prompt-sections.ts
3246
+ function buildFailurePatternsSection(playbooks) {
3247
+ if (!playbooks || playbooks.length === 0) return "";
3248
+ const entries = playbooks.map((p) => `**${p.title}:**
3249
+
3250
+ ${p.body}`).join("\n\n");
3251
+ return `### Common Failure Patterns
3252
+
3253
+ ${entries}`;
3254
+ }
3255
+ function buildReferenceSection(references) {
3256
+ if (!references || references.length === 0) return "";
3257
+ const entries = references.map((r) => `**${r.title}:**
3258
+
3259
+ ${r.body}`).join("\n\n");
3260
+ return `### Reference Knowledge
3261
+
3262
+ ${entries}`;
3263
+ }
3264
+
3194
3265
  // src/agent/report-template.ts
3195
3266
  var import_node_fs2 = require("fs");
3196
3267
  var import_node_path2 = require("path");
@@ -3301,25 +3372,7 @@ ${sqls.join("\n\n")}
3301
3372
  }
3302
3373
  return result.trimEnd();
3303
3374
  }
3304
- function buildFailurePatternsSection(playbooks) {
3305
- if (!playbooks || playbooks.length === 0) return "";
3306
- const entries = playbooks.map((p) => `**${p.title}:**
3307
-
3308
- ${p.body}`).join("\n\n");
3309
- return `### Common Failure Patterns
3310
-
3311
- ${entries}`;
3312
- }
3313
- function buildReferenceSection(references) {
3314
- if (!references || references.length === 0) return "";
3315
- const entries = references.map((r) => `**${r.title}:**
3316
-
3317
- ${r.body}`).join("\n\n");
3318
- return `### Reference Knowledge
3319
-
3320
- ${entries}`;
3321
- }
3322
- function buildSystemPrompt(kineticaVersion, catalogSchemas, playbooks, references, degraded) {
3375
+ function buildSystemPrompt(kineticaVersion, catalogSchemas, playbooks, references, degraded, bundleCapability, bundleReferences) {
3323
3376
  const versionSection = kineticaVersion ? `**Kinetica Version:** ${kineticaVersion} (provided at session start)` : "**Kinetica Version:** Unknown \u2014 detect via kinetica_health_check as the first action of every investigation.";
3324
3377
  const t = "`";
3325
3378
  const degradedSection = degraded ? `
@@ -3352,6 +3405,21 @@ function buildSystemPrompt(kineticaVersion, catalogSchemas, playbooks, reference
3352
3405
  - Do NOT attempt Round 4 (mutations) or Round 5 (verification) \u2014 the DB engine must be running first
3353
3406
 
3354
3407
  ` : "";
3408
+ const bundleSection = bundleCapability === void 0 ? "" : `
3409
+ ---
3410
+
3411
+ ## Support Bundle Capability
3412
+
3413
+ ${bundleCapability === "attached" ? `A Kinetica support bundle (offline ${t}gpudb_sysinfo${t} snapshot) IS attached to this session. You now have **two complementary evidence sources**:
3414
+ - **The bundle** (${t}kinetica_bundle_*${t} tools) \u2014 frozen point-in-time history: per-rank logs (the incident narrative the live endpoints can't show), gpudb.conf at capture time, and host diagnostics. Call ${t}kinetica_bundle_list_files${t} first, then ${t}kinetica_bundle_log_timeline${t}.
3415
+ - **The live system** (the live diagnostic tools) \u2014 current state, right now.` : `The operator can attach an offline Kinetica support bundle for analysis. If they ask to "analyze a support bundle" (or you need historical logs the live endpoints don't expose \u2014 Kinetica has no log endpoint), call ${t}kinetica_load_bundle${t} **with no path** \u2014 they will be shown an interactive directory picker to choose the bundle. Do NOT ask for the path in chat. The ${t}kinetica_bundle_*${t} tools then read its logs/config/host-diagnostics.
3416
+
3417
+ **Attaching a bundle is SETUP, not an investigation.** After ${t}kinetica_load_bundle${t} succeeds, do NOT start gathering evidence. Confirm what the operator wants to investigate first (briefly note the bundle is ready), then wait for their answer before calling any ${t}kinetica_bundle_*${t} tools. Do not waste turns investigating something they did not ask about.`}
3418
+
3419
+ **Correlate the two:** the bundle tells you what HAPPENED (e.g. a crash, an error spike, config at capture time); the live tools tell you what is TRUE NOW (did it recover? is the config still drifted? did the issue recur?). Use the bundle for the historical narrative and the live tools to verify current state. Note in the report which findings came from the bundle (and its capture time) versus the live system.
3420
+ ${bundleReferences && bundleReferences.length > 0 ? `
3421
+ ${buildReferenceSection(bundleReferences)}
3422
+ ` : ""}`;
3355
3423
  return `You are an expert Kinetica GPU database administrator and diagnostician with deep knowledge of Kinetica's internals, system tables, REST API, and common failure patterns. Your job is to autonomously investigate database issues reported by operators, gather diagnostic evidence, reason over that evidence to identify root causes, and produce a structured diagnostic report with actionable remediation steps.
3356
3424
 
3357
3425
  ${versionSection}
@@ -3479,7 +3547,7 @@ ${t}kinetica_show_table${t} (e.g., ${t}is_shard_key${t}, ${t}is_primary_key${t},
3479
3547
  ${buildFailurePatternsSection(playbooks)}
3480
3548
 
3481
3549
  ${buildReferenceSection(references)}
3482
-
3550
+ ${bundleSection}
3483
3551
  ---
3484
3552
 
3485
3553
  ## Analysis Instructions
@@ -3536,10 +3604,14 @@ Include specific, actionable remediation steps tied to your findings. Structure
3536
3604
 
3537
3605
  ## Post-Report Behavior
3538
3606
 
3539
- 1. Call the ${t}save_report${t} tool with the complete report markdown content to save it to disk.
3540
- 2. After the report is saved, ask: "Would you like to investigate another issue, or end the session?"
3541
- 3. If the operator wants another investigation, start fresh with the same 5-round protocol.
3542
- 4. On session end: summarize all issues investigated and list the saved report file paths, then exit.
3607
+ 1. Present the finished report in your response so the operator can read it.
3608
+ 2. **Ask BEFORE saving \u2014 never save unprompted.** After presenting the report, ask exactly: "Would you like me to save this report to disk? (yes/no)" and then STOP \u2014 end your turn and wait for the operator's answer. Do NOT call ${t}save_report${t} in the same turn as the question; the question must come first.
3609
+ - If the operator answers yes \u2192 call ${t}save_report${t} with the complete report markdown content.
3610
+ - If the operator answers no \u2192 do not save; acknowledge and continue.
3611
+ - **Only exception:** when checkpointing under budget pressure (the operator warned the budget guard is approaching, or you are preserving work with a ${t}partial: true${t} report before an early cutoff), save immediately WITHOUT asking \u2014 preserving findings outweighs the prompt.
3612
+ 3. After saving (or after the operator declines), ask: "Would you like to investigate another issue, or end the session?"
3613
+ 4. If the operator wants another investigation, start fresh with the same 5-round protocol.
3614
+ 5. On session end: summarize all issues investigated and list the saved report file paths, then exit.
3543
3615
 
3544
3616
  ---
3545
3617
 
@@ -3642,9 +3714,8 @@ async function discoverCatalogSchemas(session2) {
3642
3714
  var import_promises2 = require("fs/promises");
3643
3715
  var import_node_path3 = require("path");
3644
3716
  var import_node_fs3 = require("fs");
3645
- async function loadReferences(refsDir) {
3717
+ async function loadReferencesFrom(dir) {
3646
3718
  try {
3647
- const dir = refsDir ?? (0, import_node_path3.join)(findPackageRoot(__dirname), "knowledge", "references");
3648
3719
  if (!(0, import_node_fs3.existsSync)(dir)) return [];
3649
3720
  const files = await (0, import_promises2.readdir)(dir);
3650
3721
  const mdFiles = files.filter((f) => f.endsWith(".md")).sort();
@@ -3666,13 +3737,21 @@ async function loadReferences(refsDir) {
3666
3737
  return [];
3667
3738
  }
3668
3739
  }
3740
+ function loadReferences(refsDir) {
3741
+ return loadReferencesFrom(refsDir ?? (0, import_node_path3.join)(findPackageRoot(__dirname), "knowledge", "references"));
3742
+ }
3743
+ function loadBundleReferences(refsDir) {
3744
+ return loadReferencesFrom(
3745
+ refsDir ?? (0, import_node_path3.join)(findPackageRoot(__dirname), "knowledge", "references", "bundle")
3746
+ );
3747
+ }
3669
3748
 
3670
3749
  // src/agent/prompt-budget.ts
3671
3750
  var CHARS_PER_TOKEN = 4;
3672
3751
  var DEFAULT_PROMPT_BUDGET_TOKENS = 2e4;
3673
- function estimateTokens(text) {
3674
- if (!text) return 0;
3675
- return Math.ceil(text.length / CHARS_PER_TOKEN);
3752
+ function estimateTokens(text2) {
3753
+ if (!text2) return 0;
3754
+ return Math.ceil(text2.length / CHARS_PER_TOKEN);
3676
3755
  }
3677
3756
  function checkPromptBudget(prompt, opts) {
3678
3757
  const threshold = opts?.warnAtTokens ?? DEFAULT_PROMPT_BUDGET_TOKENS;
@@ -3712,14 +3791,14 @@ function isValidBudget(value) {
3712
3791
  function estimateTurnCostUsd(usage, model) {
3713
3792
  if (!usage) return 0;
3714
3793
  const price = MODEL_PRICING[model];
3715
- const input5 = safeCount(usage.inputTokens);
3794
+ const input2 = safeCount(usage.inputTokens);
3716
3795
  const output = safeCount(usage.outputTokens);
3717
3796
  const cacheRead = safeCount(usage.cacheReadInputTokens);
3718
3797
  const cacheCreation = safeCount(usage.cacheCreationInputTokens);
3719
- return (input5 * price.inputPerMTok + output * price.outputPerMTok + cacheRead * price.cacheReadPerMTok + cacheCreation * price.cacheCreationPerMTok) / 1e6;
3798
+ return (input2 * price.inputPerMTok + output * price.outputPerMTok + cacheRead * price.cacheReadPerMTok + cacheCreation * price.cacheCreationPerMTok) / 1e6;
3720
3799
  }
3721
- function resolveMaxBudgetUsd(flagValue, env = process.env) {
3722
- if (isValidBudget(flagValue)) return flagValue;
3800
+ function resolveMaxBudgetUsd(flagValue2, env = process.env) {
3801
+ if (isValidBudget(flagValue2)) return flagValue2;
3723
3802
  const raw = env[BUDGET_ENV_VAR];
3724
3803
  if (raw !== void 0 && raw !== "") {
3725
3804
  const parsed = Number(raw);
@@ -3766,7 +3845,7 @@ function formatTimestamp(date) {
3766
3845
  function makeSaveReportTool() {
3767
3846
  return (0, import_claude_agent_sdk3.tool)(
3768
3847
  "save_report",
3769
- "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.",
3848
+ "Save a diagnostic report to disk. Call this ONLY after the operator has agreed to save (you must ask 'save this report? (yes/no)' and get a yes first) \u2014 or when checkpointing a partial report under budget pressure. Automatically scrubs credentials, creates a timestamped filename in reports/, and auto-creates the directory.",
3770
3849
  {
3771
3850
  content: import_zod17.z.string().describe("The full markdown diagnostic report content"),
3772
3851
  partial: import_zod17.z.boolean().optional().describe(
@@ -3790,179 +3869,1482 @@ function makeSaveReportTool() {
3790
3869
  );
3791
3870
  }
3792
3871
 
3793
- // src/approval/gate.ts
3794
- var import_prompts3 = require("@inquirer/prompts");
3872
+ // src/tools/bundle/index.ts
3873
+ var import_claude_agent_sdk4 = require("@anthropic-ai/claude-agent-sdk");
3795
3874
 
3796
- // src/approval/display.ts
3797
- var import_picocolors5 = __toESM(require("picocolors"));
3798
- var IMPACT_FALLBACK = "Impact unknown \u2014 review parameters carefully";
3799
- var DIVIDER2 = import_picocolors5.default.dim("\u2500".repeat(50));
3800
- var LABEL_WIDTH = 8;
3801
- function formatLabel(label) {
3802
- return ` ${label.padEnd(LABEL_WIDTH)}: `;
3875
+ // src/tools/bundle/list-files.ts
3876
+ var import_zod18 = require("zod");
3877
+
3878
+ // src/bundle/known-files.ts
3879
+ var KNOWN_BUNDLE_FILES = {
3880
+ // Host resources
3881
+ "cpu.txt": "CPU topology, NUMA, and interrupts (lscpu, numactl, /proc/cpuinfo, /proc/interrupts)",
3882
+ "mem.txt": "Memory usage, /proc/meminfo, and transparent-hugepage setting (free -m -t)",
3883
+ "disk.txt": "Filesystems, mounts, block devices, and disk stats (df, mount, lsblk, fdisk, /etc/fstab, /proc/diskstats)",
3884
+ "gpu.txt": "NVIDIA GPU inventory and state (nvidia-smi -L/-q, modinfo nvidia)",
3885
+ "net.txt": "Network interfaces, sockets, and DNS (hostname, ifconfig, netstat, /etc/resolv.conf)",
3886
+ // Processes
3887
+ "ps.txt": "Full process list (ps -auxww, ps -ejHlfww)",
3888
+ "gpudb-exe.txt": "Running gpudb processes (ps auxfwww | grep gpudb)",
3889
+ // Hardware / firmware
3890
+ "dmidecode.txt": "BIOS / DMI hardware inventory (dmidecode)",
3891
+ "lshw.txt": "Hardware listing (lshw -short -numeric)",
3892
+ "pci.txt": "PCI devices and I/O resources (lspci, /proc/ioports, /proc/iomem)",
3893
+ // Kernel / OS
3894
+ "dmesg.txt": "Kernel ring buffer \u2014 boot and runtime kernel messages (dmesg -T)",
3895
+ "dmesg-timestamp.txt": "Kernel ring buffer with human-readable timestamps",
3896
+ "sysctl.txt": "Kernel tunables (sysctl -a)",
3897
+ "sys.txt": "OS identity, uptime, ulimits, kernel cmdline, clocksource, and loaded modules (uname, ulimit, /proc/cmdline, lsmod)",
3898
+ "lsof.txt": "Open files and network sockets (lsof -n -P)",
3899
+ "lslocks.txt": "Held file locks (lslocks)",
3900
+ // Packages / linker / accounts
3901
+ "deb.txt": "Installed Debian packages and verification (dpkg -l, dpkg -V)",
3902
+ "rpm.txt": "Installed RPM packages (rpm -qa)",
3903
+ "ld.so.conf.txt": "Dynamic-linker library search paths (/etc/ld.so.conf)",
3904
+ "user.txt": "Users, groups, and the gpudb service account (whoami, id, /etc/passwd, /etc/group)",
3905
+ "sudoers.txt": "Sudo configuration (/etc/sudoers)",
3906
+ "etc_profile.txt": "Login shell profile (/etc/profile)",
3907
+ "etc_bashrc.txt": "System bashrc (/etc/bashrc)",
3908
+ "etc_host.txt": "Static hostname resolution (/etc/hosts)",
3909
+ // Kinetica-specific
3910
+ "gpudb.txt": "GPUdb version/build, binary md5 + ldd, and the captured gpudb.conf / gpudb_logger.conf ($GPUDB_EXE -v)",
3911
+ "gpudb_core_etc_gpudb.conf": "The live gpudb.conf at capture time (the database's main config)",
3912
+ "gpudb_core_etc_gpudb_logger.conf": "The logging configuration (gpudb_logger.conf)",
3913
+ "loki-info.txt": "Loki log-index stats: labels, series, and per-class volume (logcli)",
3914
+ "sql-queries.txt": "SQL query log extracted from Loki (logcli)",
3915
+ "tables.txt": "Table schemas and column types (gadmin --schema), when collected",
3916
+ "logfiles.txt": "Manifest: the log directories/files the collector enumerated",
3917
+ "errors.txt": "Collection commands that FAILED during capture (Evidence Gaps)",
3918
+ "proc-logs-erros.txt": "Per-process log-collection failures during capture (Evidence Gaps)"
3919
+ };
3920
+ var KIND_DESCRIPTIONS = {
3921
+ "core-log": "Per-rank rolling Kinetica core log (the primary incident narrative)",
3922
+ "component-log": "Component service log (sql-engine, httpd, reveal, tomcat, stats, \u2026)",
3923
+ "loki-tail": "Last-2h Loki tail for a service (small; searched only when no core logs exist)",
3924
+ "process-info": "Per-rank process snapshot: command line, PID, and environment (/proc/<pid>/environ)",
3925
+ config: "Kinetica configuration file",
3926
+ "version-info": "GPUdb version/build information",
3927
+ "collection-errors": "Collection commands that FAILED during capture (Evidence Gaps)",
3928
+ manifest: "Manifest of log directories/files the collector enumerated"
3929
+ };
3930
+ function basename(relPath) {
3931
+ const parts = relPath.split("/");
3932
+ return parts[parts.length - 1] ?? relPath;
3803
3933
  }
3804
- function renderApprovalPanel(toolName, toolInput, impact, beforeAfter, reasoningSummary) {
3805
- const header = import_picocolors5.default.bold(import_picocolors5.default.yellow(" Mutation Approval Required"));
3806
- const action = `${formatLabel("Action")}${import_picocolors5.default.bold(formatToolName(toolName))}`;
3807
- const paramEntries = Object.entries(toolInput);
3808
- const paramSection = paramEntries.length === 0 ? " (no parameters)" : paramEntries.map(([key, value]) => {
3809
- const formatted = typeof value === "string" ? value : JSON.stringify(value, null, 2);
3810
- return ` ${import_picocolors5.default.dim(key)}: ${formatted}`;
3811
- }).join("\n");
3812
- const impactLine = `${formatLabel("Impact")}${impact ?? IMPACT_FALLBACK}`;
3813
- const prompt = import_picocolors5.default.dim(
3814
- `${formatLabel("Respond")}y (proceed) | n (abort) | explain (show reasoning)`
3815
- );
3816
- const hasBeforeAfter = beforeAfter !== void 0 && beforeAfter.length > 0;
3817
- const beforeAfterSection = hasBeforeAfter ? beforeAfter.map(
3818
- (entry) => ` ${import_picocolors5.default.dim(entry.key)}: ${entry.current} ${import_picocolors5.default.yellow("->")} ${entry.proposed}`
3819
- ).join("\n") : null;
3820
- const hasReasoning = reasoningSummary !== void 0 && reasoningSummary.length > 0;
3821
- const reasoningSection = hasReasoning ? `${formatLabel("Reason")}${reasoningSummary}` : null;
3822
- const sections = ["", DIVIDER2, header, "", action, paramSection, ""];
3823
- if (beforeAfterSection !== null) {
3824
- sections.push(beforeAfterSection, "");
3825
- }
3826
- if (reasoningSection !== null) {
3827
- sections.push(reasoningSection, "");
3828
- }
3829
- sections.push(impactLine, "", prompt, DIVIDER2, "");
3830
- return sections.join("\n");
3934
+ function describeBundleFile(entry) {
3935
+ return KNOWN_BUNDLE_FILES[basename(entry.relPath)] ?? KIND_DESCRIPTIONS[entry.kind] ?? "";
3831
3936
  }
3832
3937
 
3833
- // src/approval/gate.ts
3834
- var DENY_MESSAGE = "User denied this mutation. Skip and continue with the investigation.";
3835
- var REASONING_FALLBACK = "Reasoning not available. Review the action details above before proceeding.";
3836
- function createApprovalGate(isReadOnly) {
3837
- return async (toolName, toolInput, options) => {
3838
- if (isReadOnly(toolName)) {
3839
- return {
3840
- behavior: "allow",
3841
- updatedInput: toolInput,
3842
- toolUseID: options.toolUseID
3843
- };
3844
- }
3845
- const impact = options.decisionReason;
3846
- const panel = renderApprovalPanel(toolName, toolInput, impact);
3847
- console.error(panel);
3848
- while (true) {
3849
- try {
3850
- const raw = await (0, import_prompts3.input)({ message: "Proceed? (y/n/explain):" }, { signal: options.signal });
3851
- const normalized = raw.trim().toLowerCase();
3852
- if (normalized === "y") {
3853
- process.stderr.write("\n");
3854
- return {
3855
- behavior: "allow",
3856
- updatedInput: toolInput,
3857
- toolUseID: options.toolUseID
3858
- };
3859
- }
3860
- if (normalized === "n") {
3861
- process.stderr.write("\n");
3862
- return {
3863
- behavior: "deny",
3864
- message: DENY_MESSAGE,
3865
- toolUseID: options.toolUseID
3866
- };
3867
- }
3868
- if (normalized === "explain") {
3869
- const reasoning = options.decisionReason;
3870
- if (reasoning) {
3871
- console.error(`
3872
- Agent reasoning: ${reasoning}
3873
- `);
3874
- } else {
3875
- console.error(`
3876
- ${REASONING_FALLBACK}
3877
- `);
3878
- }
3879
- }
3880
- } catch {
3881
- return {
3882
- behavior: "deny",
3883
- message: DENY_MESSAGE,
3884
- toolUseID: options.toolUseID
3885
- };
3886
- }
3938
+ // src/tools/bundle/list-files.ts
3939
+ var BundleListFilesSchema = import_zod18.z.object({
3940
+ kind: import_zod18.z.string().optional()
3941
+ });
3942
+ async function bundleListFiles(source, args = {}) {
3943
+ const all = source.listFiles();
3944
+ const filtered = args.kind ? all.filter((e) => e.kind === args.kind) : all;
3945
+ const { totalFiles, totalBytes, byKind, ranks, services } = source.inventory();
3946
+ const version = await source.detectVersion();
3947
+ const errors = await source.collectionErrors();
3948
+ const files = filtered.map((e) => ({
3949
+ file: e.relPath,
3950
+ kind: e.kind,
3951
+ rank: e.rank ?? "",
3952
+ size_kb: Math.round(e.sizeBytes / 1024),
3953
+ // What the file contains — so the agent can pick the right one without reading it.
3954
+ description: describeBundleFile(e)
3955
+ }));
3956
+ return {
3957
+ ok: true,
3958
+ data: {
3959
+ detected_version: version ?? "unknown",
3960
+ ranks_present: ranks.join(", ") || "none",
3961
+ services_present: services.join(", ") || "none",
3962
+ total_files: totalFiles,
3963
+ total_size_mb: Number((totalBytes / 1e6).toFixed(1)),
3964
+ counts_by_kind: byKind,
3965
+ failed_collections: errors.length,
3966
+ files
3887
3967
  }
3888
3968
  };
3889
3969
  }
3890
3970
 
3891
- // src/agent/turn-gate.ts
3892
- function createTurnGate() {
3893
- let resolve2 = () => {
3971
+ // src/tools/bundle/log-timeline.ts
3972
+ var import_zod19 = require("zod");
3973
+ var BundleLogTimelineSchema = import_zod19.z.object({
3974
+ min_severity: import_zod19.z.enum(["INFO", "WARN", "UERR", "ERROR", "FATAL"]).optional(),
3975
+ granularity: import_zod19.z.enum(["day", "hour", "minute"]).optional(),
3976
+ rank: import_zod19.z.string().describe('Numeric rank only, e.g. "r0"/"r1". For the host manager use host_manager.').optional(),
3977
+ host_manager: import_zod19.z.boolean().describe("Bucket the host-manager (hm) log \u2014 a singleton service, not a rank.").optional(),
3978
+ component: import_zod19.z.string().optional(),
3979
+ include_components: import_zod19.z.boolean().optional()
3980
+ });
3981
+ async function bundleLogTimeline(source, args = {}) {
3982
+ const query3 = {
3983
+ ...args.min_severity !== void 0 ? { minSeverity: args.min_severity } : {},
3984
+ ...args.granularity !== void 0 ? { granularity: args.granularity } : {},
3985
+ ...args.rank !== void 0 ? { rank: args.rank } : {},
3986
+ ...args.host_manager !== void 0 ? { hostManager: args.host_manager } : {},
3987
+ ...args.component !== void 0 ? { component: args.component } : {},
3988
+ ...args.include_components !== void 0 ? { includeComponents: args.include_components } : {}
3894
3989
  };
3895
- let promise = new Promise((r) => {
3896
- resolve2 = r;
3990
+ const result = await source.logTimeline(query3);
3991
+ const severities = [...new Set(result.buckets.flatMap((b) => Object.keys(b.counts)))];
3992
+ const order = ["FATAL", "ERROR", "UERR", "WARN", "INFO"];
3993
+ severities.sort((a, b) => order.indexOf(a) - order.indexOf(b));
3994
+ const rows = result.buckets.map((b) => {
3995
+ const row = { time_bucket: b.bucket };
3996
+ for (const sev of severities) row[sev] = b.counts[sev] ?? 0;
3997
+ row.total = b.total;
3998
+ return row;
3897
3999
  });
3898
- return Object.freeze({
3899
- wait: () => promise,
3900
- open: () => {
3901
- resolve2();
3902
- },
3903
- close: () => {
3904
- promise = new Promise((r) => {
3905
- resolve2 = r;
3906
- });
4000
+ return {
4001
+ ok: true,
4002
+ note: result.totalCounted === 0 ? "No lines at or above the severity threshold \u2014 try a lower min_severity." : `${result.totalCounted} event(s) across ${result.buckets.length} bucket(s), ${result.filesScanned.length} file(s).`,
4003
+ data: {
4004
+ lines_scanned: result.linesScanned,
4005
+ files_scanned: result.filesScanned.join(", ") || "none",
4006
+ buckets: rows
3907
4007
  }
3908
- });
4008
+ };
3909
4009
  }
3910
4010
 
3911
- // src/output/render-markdown.ts
3912
- var import_picocolors6 = __toESM(require("picocolors"));
3913
- var BOLD_RE = /\*\*(.+?)\*\*/g;
3914
- var HEADING_RE = /^(#{1,6})\s+(.+)$/;
3915
- function renderMarkdownLine(line) {
3916
- const headingMatch = HEADING_RE.exec(line);
3917
- if (headingMatch) {
3918
- return import_picocolors6.default.bold(headingMatch[2]);
4011
+ // src/tools/bundle/search-logs.ts
4012
+ var import_zod20 = require("zod");
4013
+ var BundleSearchLogsSchema = import_zod20.z.object({
4014
+ regex: import_zod20.z.string().optional(),
4015
+ min_severity: import_zod20.z.enum(["INFO", "WARN", "UERR", "ERROR", "FATAL"]).optional(),
4016
+ from_ts: import_zod20.z.string().optional(),
4017
+ to_ts: import_zod20.z.string().optional(),
4018
+ rank: import_zod20.z.string().describe('Numeric rank only, e.g. "r0"/"r1". For the host manager use host_manager.').optional(),
4019
+ host_manager: import_zod20.z.boolean().describe("Search the host-manager (hm) log \u2014 a singleton service, not a rank.").optional(),
4020
+ component: import_zod20.z.string().optional(),
4021
+ include_components: import_zod20.z.boolean().optional(),
4022
+ max_matches: import_zod20.z.number().int().min(1).max(1e3).optional()
4023
+ });
4024
+ async function bundleSearchLogs(source, args = {}) {
4025
+ const query3 = {
4026
+ ...args.regex !== void 0 ? { regex: args.regex } : {},
4027
+ ...args.min_severity !== void 0 ? { minSeverity: args.min_severity } : {},
4028
+ ...args.from_ts !== void 0 ? { fromTs: args.from_ts } : {},
4029
+ ...args.to_ts !== void 0 ? { toTs: args.to_ts } : {},
4030
+ ...args.rank !== void 0 ? { rank: args.rank } : {},
4031
+ ...args.host_manager !== void 0 ? { hostManager: args.host_manager } : {},
4032
+ ...args.component !== void 0 ? { component: args.component } : {},
4033
+ ...args.include_components !== void 0 ? { includeComponents: args.include_components } : {},
4034
+ ...args.max_matches !== void 0 ? { maxMatches: args.max_matches } : {}
4035
+ };
4036
+ const result = await source.searchLogs(query3);
4037
+ const note = result.capped ? `Showing ${result.matches.length} of ${result.totalMatched} matches across ${result.filesScanned.length} file(s) (display capped). Narrow with a tighter regex, severity, or time window to surface the specific lines.` : `${result.totalMatched} match(es) across ${result.filesScanned.length} file(s).`;
4038
+ return {
4039
+ ok: true,
4040
+ note,
4041
+ data: {
4042
+ total_matched: result.totalMatched,
4043
+ lines_scanned: result.linesScanned,
4044
+ files_scanned: result.filesScanned.join(", ") || "none",
4045
+ capped: result.capped,
4046
+ matches: result.matches.map((m) => ({
4047
+ file: m.file,
4048
+ line: m.lineNumber,
4049
+ timestamp: m.timestamp ?? "",
4050
+ severity: m.severity ?? "",
4051
+ rank: m.rank ?? "",
4052
+ message: m.message
4053
+ }))
4054
+ }
4055
+ };
4056
+ }
4057
+
4058
+ // src/tools/bundle/read-config.ts
4059
+ var import_zod21 = require("zod");
4060
+ var BundleReadConfigSchema = import_zod21.z.object({
4061
+ section: import_zod21.z.string().optional(),
4062
+ key: import_zod21.z.string().optional()
4063
+ });
4064
+ async function bundleReadConfig(source, args = {}) {
4065
+ const result = await source.readConfig({
4066
+ ...args.section !== void 0 ? { section: args.section } : {},
4067
+ ...args.key !== void 0 ? { key: args.key } : {}
4068
+ });
4069
+ if ("error" in result) {
4070
+ return { ok: false, status: 0, error: result.error, raw: "" };
3919
4071
  }
3920
- if (line.includes("**")) {
3921
- return line.replace(BOLD_RE, (_, text) => import_picocolors6.default.bold(text));
4072
+ if (result.entries.length === 0 && args.section !== void 0) {
4073
+ const all = await source.readConfig(args.key !== void 0 ? { key: args.key } : {});
4074
+ const sections = "error" in all ? [] : [...new Set(all.entries.map((e) => e.section))].sort();
4075
+ const sectionList = sections.map((s) => s === "" ? "(flat/top-level)" : s).join(", ");
4076
+ return {
4077
+ ok: true,
4078
+ note: `No entries in section "${args.section}" of ${result.file}. gpudb.conf is largely flat \u2014 retry filtering by key only. Sections present: ${sectionList || "(none)"}.`,
4079
+ data: { section_not_found: args.section, available_sections: sections }
4080
+ };
3922
4081
  }
3923
- return line;
4082
+ return {
4083
+ ok: true,
4084
+ note: `${result.entries.length} entr(y/ies) from ${result.file}.`,
4085
+ data: result.entries.map((e) => ({ section: e.section, key: e.key, value: e.value }))
4086
+ };
3924
4087
  }
3925
4088
 
3926
- // src/output/reformat-tables.ts
3927
- var SEPARATOR_CELL_RE = /^:?-+:?$/;
3928
- var BOLD_MARKERS_RE = /\*\*(.+?)\*\*/g;
3929
- function visualWidth(text) {
3930
- return text.replace(BOLD_MARKERS_RE, "$1").length;
3931
- }
3932
- function isSeparatorCell(cell) {
3933
- return SEPARATOR_CELL_RE.test(cell);
3934
- }
3935
- function isSeparatorRow(cells) {
3936
- return cells.length > 0 && cells.every(isSeparatorCell);
4089
+ // src/tools/bundle/read-sysinfo.ts
4090
+ var import_zod22 = require("zod");
4091
+ var BundleReadSysinfoSchema = import_zod22.z.object({
4092
+ name: import_zod22.z.string().min(1)
4093
+ });
4094
+ async function bundleReadSysinfo(source, args) {
4095
+ const result = await source.readSysinfo(args.name);
4096
+ if ("error" in result) {
4097
+ return { ok: false, status: 0, error: result.error, raw: "" };
4098
+ }
4099
+ return {
4100
+ ok: true,
4101
+ data: {
4102
+ ...result.header !== void 0 ? { source_file: result.header } : {},
4103
+ blocks: result.blocks.map((b) => ({
4104
+ command: b.command,
4105
+ ...b.exitCode !== void 0 ? { exit_code: b.exitCode } : {},
4106
+ output: b.output
4107
+ }))
4108
+ }
4109
+ };
3937
4110
  }
3938
- function parseCells(line) {
3939
- return line.split("|").slice(1, -1).map((c) => c.trim());
4111
+
4112
+ // src/tools/bundle/load-bundle.ts
4113
+ var import_zod23 = require("zod");
4114
+
4115
+ // src/bundle/verify-bundle.ts
4116
+ var import_promises6 = require("fs/promises");
4117
+
4118
+ // src/bundle/BundleSource.ts
4119
+ var import_promises5 = require("fs/promises");
4120
+ var import_node_path6 = require("path");
4121
+
4122
+ // src/bundle/sysinfo-block.ts
4123
+ var SEPARATOR_RE = /^-{3,}$/;
4124
+ var EXEC_CMD_RE = /^EXEC_CMD:\s?(.*)$/;
4125
+ var EXEC_END_RE = /^EXEC_END with exit code (\d+)\s*:?\s*(.*)$/;
4126
+ var SHOWING_RE = /^### Showing whole log file\s*:/;
4127
+ function trimBlankEdges(lines) {
4128
+ let start = 0;
4129
+ let end = lines.length;
4130
+ while (start < end && lines[start].trim() === "") start++;
4131
+ while (end > start && lines[end - 1].trim() === "") end--;
4132
+ return lines.slice(start, end).join("\n");
4133
+ }
4134
+ function parseSysinfo(content) {
4135
+ const lines = content.split("\n");
4136
+ let header;
4137
+ const blocks = [];
4138
+ let current;
4139
+ let sawCommand = false;
4140
+ const closeBlock = (exitCode, exitMessage) => {
4141
+ if (!current) return;
4142
+ blocks.push({
4143
+ command: current.command,
4144
+ output: trimBlankEdges(current.output),
4145
+ ...exitCode !== void 0 ? { exitCode } : {},
4146
+ ...exitMessage !== void 0 && exitMessage !== "" ? { exitMessage } : {}
4147
+ });
4148
+ current = void 0;
4149
+ };
4150
+ for (const line of lines) {
4151
+ if (SEPARATOR_RE.test(line)) continue;
4152
+ const cmdMatch = EXEC_CMD_RE.exec(line);
4153
+ if (cmdMatch) {
4154
+ closeBlock();
4155
+ current = { command: cmdMatch[1].trim(), output: [] };
4156
+ sawCommand = true;
4157
+ continue;
4158
+ }
4159
+ const endMatch = EXEC_END_RE.exec(line);
4160
+ if (endMatch && current) {
4161
+ closeBlock(Number(endMatch[1]), endMatch[2].trim());
4162
+ continue;
4163
+ }
4164
+ if (current) {
4165
+ if (SHOWING_RE.test(line)) continue;
4166
+ current.output.push(line);
4167
+ continue;
4168
+ }
4169
+ if (header === void 0 && !sawCommand && line.trim() !== "") {
4170
+ header = line.trim();
4171
+ }
4172
+ }
4173
+ closeBlock();
4174
+ return { ...header !== void 0 ? { header } : {}, blocks };
3940
4175
  }
3941
- function reformatTableBlock(lines) {
3942
- const parsed = lines.map(parseCells);
3943
- const colCount = Math.max(...parsed.map((row) => row.length));
3944
- const normalised = parsed.map((row) => {
3945
- const padded = [...row];
3946
- while (padded.length < colCount) {
3947
- padded.push("");
4176
+
4177
+ // src/bundle/parse-ini.ts
4178
+ var SECTION_RE = /^\[(.+)\]$/;
4179
+ function parseIni(content) {
4180
+ const entries = [];
4181
+ let section = "";
4182
+ for (const rawLine of content.split("\n")) {
4183
+ const line = rawLine.trim();
4184
+ if (line === "" || line.startsWith("#") || line.startsWith(";")) continue;
4185
+ const sectionMatch = SECTION_RE.exec(line);
4186
+ if (sectionMatch) {
4187
+ section = sectionMatch[1].trim();
4188
+ continue;
3948
4189
  }
3949
- return padded;
4190
+ const eq = line.indexOf("=");
4191
+ if (eq === -1) continue;
4192
+ const key = line.slice(0, eq).trim();
4193
+ const value = line.slice(eq + 1).trim();
4194
+ if (key) entries.push({ section, key, value });
4195
+ }
4196
+ return entries;
4197
+ }
4198
+ function filterIni(entries, opts = {}) {
4199
+ const section = opts.section?.toLowerCase();
4200
+ const key = opts.key?.toLowerCase();
4201
+ return entries.filter((e) => {
4202
+ if (section !== void 0 && e.section.toLowerCase() !== section) return false;
4203
+ if (key !== void 0 && !e.key.toLowerCase().includes(key)) return false;
4204
+ return true;
3950
4205
  });
3951
- const colWidths = Array.from(
3952
- { length: colCount },
3953
- (_, col) => Math.max(
3954
- 3,
3955
- ...normalised.filter((row) => !isSeparatorRow(row)).map((row) => visualWidth(row[col]))
3956
- )
3957
- );
3958
- const borderRow = `+${colWidths.map((w) => "-".repeat(w + 2)).join("+")}+`;
3959
- const bodyRows = normalised.map((row) => {
4206
+ }
4207
+
4208
+ // src/bundle/log-search.ts
4209
+ var import_node_fs4 = require("fs");
4210
+ var import_node_readline = require("readline");
4211
+
4212
+ // src/bundle/unwrap-loki-jsonl.ts
4213
+ var LEADING_TS_RE = /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+)\s+/;
4214
+ var HEADER_BODY_SEP = " : ";
4215
+ function unwrapLokiJsonl(line) {
4216
+ let i = 0;
4217
+ while (i < line.length && line.charCodeAt(i) <= 32) i++;
4218
+ if (line.charCodeAt(i) !== 123) return void 0;
4219
+ let obj;
4220
+ try {
4221
+ obj = JSON.parse(line);
4222
+ } catch {
4223
+ return void 0;
4224
+ }
4225
+ if (typeof obj !== "object" || obj === null) return void 0;
4226
+ const inner = obj.line;
4227
+ if (typeof inner !== "string") return void 0;
4228
+ const tsMatch = LEADING_TS_RE.exec(inner);
4229
+ const sepIdx = inner.indexOf(HEADER_BODY_SEP);
4230
+ if (tsMatch && sepIdx !== -1) {
4231
+ const ts = tsMatch[1];
4232
+ const body = inner.slice(sepIdx + HEADER_BODY_SEP.length).trim();
4233
+ return `${ts} ${body}`;
4234
+ }
4235
+ return inner;
4236
+ }
4237
+
4238
+ // src/bundle/parse-log-line.ts
4239
+ var PREFIX_RE = /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+)\s+([A-Z]+)\s+\(([^)]*)\)\s*(.*)$/;
4240
+ var CORE_TAIL_RE = /^(\S+)\s+(\S+:\d+)\s+-\s+(.*)$/;
4241
+ var RANK_RE = /^(r\d+)\b/;
4242
+ var SEVERITY_RANK = {
4243
+ TRACE: 0,
4244
+ DEBUG: 1,
4245
+ INFO: 2,
4246
+ WARN: 3,
4247
+ UERR: 4,
4248
+ ERROR: 5,
4249
+ FATAL: 6
4250
+ };
4251
+ function severityRank(severity) {
4252
+ if (severity === void 0) return -1;
4253
+ return SEVERITY_RANK[severity] ?? -1;
4254
+ }
4255
+ function parseLogLine(line) {
4256
+ const effective = unwrapLokiJsonl(line) ?? line;
4257
+ const match = PREFIX_RE.exec(effective);
4258
+ if (!match) {
4259
+ return { message: effective, raw: line };
4260
+ }
4261
+ const [, timestamp, severity, paren, rest] = match;
4262
+ const parts = paren.split(",");
4263
+ const pid = parts[0]?.trim() || void 0;
4264
+ const tid = parts[1]?.trim() || void 0;
4265
+ const context = parts.slice(2).join(",").trim() || void 0;
4266
+ const rank = context ? RANK_RE.exec(context)?.[1] ?? void 0 : void 0;
4267
+ const coreTail = CORE_TAIL_RE.exec(rest);
4268
+ if (coreTail) {
4269
+ const [, host, source, message] = coreTail;
4270
+ return { timestamp, severity, pid, tid, context, rank, host, source, message, raw: line };
4271
+ }
4272
+ return { timestamp, severity, pid, tid, context, rank, message: rest, raw: line };
4273
+ }
4274
+
4275
+ // src/bundle/log-search.ts
4276
+ var DEFAULT_MAX_MATCHES = 200;
4277
+ var REGEX_SCAN_MAX = 8192;
4278
+ var GRANULARITY_LEN = {
4279
+ day: 10,
4280
+ // "2026-06-11"
4281
+ hour: 13,
4282
+ // "2026-06-11 15"
4283
+ minute: 16
4284
+ // "2026-06-11 15:18"
4285
+ };
4286
+ function compileRegex(query3) {
4287
+ if (query3.regex === void 0) return void 0;
4288
+ return new RegExp(query3.regex, query3.caseSensitive ? void 0 : "i");
4289
+ }
4290
+ var TS_FLOOR = "0000-01-01 00:00:00.000";
4291
+ var TS_CEIL = "9999-12-31 23:59:59.999";
4292
+ var SAFE_PREFIX_LENS = [4, 7, 10, 13, 16, 19];
4293
+ function alignPrefixLen(len) {
4294
+ let aligned = 0;
4295
+ for (const n of SAFE_PREFIX_LENS) if (n <= len) aligned = n;
4296
+ return aligned;
4297
+ }
4298
+ function floorTimestamp(ts) {
4299
+ if (ts.length >= TS_FLOOR.length) return ts;
4300
+ const len = alignPrefixLen(ts.length);
4301
+ return ts.slice(0, len) + TS_FLOOR.slice(len);
4302
+ }
4303
+ function ceilTimestamp(ts) {
4304
+ if (ts.length >= TS_CEIL.length) return ts;
4305
+ const len = alignPrefixLen(ts.length);
4306
+ return ts.slice(0, len) + TS_CEIL.slice(len);
4307
+ }
4308
+ function matchesFilters(parsed, query3, regex, minRank) {
4309
+ if (regex && !regex.test(parsed.raw.slice(0, REGEX_SCAN_MAX))) return false;
4310
+ if (query3.minSeverity !== void 0 && severityRank(parsed.severity) < minRank) return false;
4311
+ if (query3.rank !== void 0 && parsed.rank !== query3.rank) return false;
4312
+ if (query3.fromTs !== void 0 && (parsed.timestamp === void 0 || parsed.timestamp < query3.fromTs))
4313
+ return false;
4314
+ if (query3.toTs !== void 0 && (parsed.timestamp === void 0 || parsed.timestamp > query3.toTs))
4315
+ return false;
4316
+ return true;
4317
+ }
4318
+ async function searchLogFile(filePath, query3) {
4319
+ const maxMatches = query3.maxMatches ?? DEFAULT_MAX_MATCHES;
4320
+ const minRank = query3.minSeverity !== void 0 ? severityRank(query3.minSeverity) : -Infinity;
4321
+ let regex;
4322
+ try {
4323
+ regex = compileRegex(query3);
4324
+ } catch (err) {
4325
+ const message = err instanceof Error ? err.message : String(err);
4326
+ return {
4327
+ matches: [],
4328
+ totalMatched: 0,
4329
+ linesScanned: 0,
4330
+ capped: false,
4331
+ error: `invalid regex: ${message}`
4332
+ };
4333
+ }
4334
+ const boundedQuery = {
4335
+ ...query3,
4336
+ ...query3.fromTs !== void 0 ? { fromTs: floorTimestamp(query3.fromTs) } : {},
4337
+ ...query3.toTs !== void 0 ? { toTs: ceilTimestamp(query3.toTs) } : {}
4338
+ };
4339
+ const matches = [];
4340
+ let totalMatched = 0;
4341
+ let linesScanned = 0;
4342
+ try {
4343
+ const rl = (0, import_node_readline.createInterface)({
4344
+ input: (0, import_node_fs4.createReadStream)(filePath, { encoding: "utf-8" }),
4345
+ crlfDelay: Infinity
4346
+ });
4347
+ for await (const line of rl) {
4348
+ linesScanned++;
4349
+ const parsed = parseLogLine(line);
4350
+ if (!matchesFilters(parsed, boundedQuery, regex, minRank)) continue;
4351
+ totalMatched++;
4352
+ if (matches.length < maxMatches) {
4353
+ matches.push({
4354
+ lineNumber: linesScanned,
4355
+ ...parsed.timestamp !== void 0 ? { timestamp: parsed.timestamp } : {},
4356
+ ...parsed.severity !== void 0 ? { severity: parsed.severity } : {},
4357
+ ...parsed.rank !== void 0 ? { rank: parsed.rank } : {},
4358
+ message: parsed.message,
4359
+ raw: parsed.raw
4360
+ });
4361
+ }
4362
+ }
4363
+ } catch (err) {
4364
+ const message = err instanceof Error ? err.message : String(err);
4365
+ return {
4366
+ matches,
4367
+ totalMatched,
4368
+ linesScanned,
4369
+ capped: totalMatched > matches.length,
4370
+ error: message
4371
+ };
4372
+ }
4373
+ return { matches, totalMatched, linesScanned, capped: totalMatched > matches.length };
4374
+ }
4375
+ async function aggregateTimeline(filePath, query3 = {}) {
4376
+ const granularity = query3.granularity ?? "hour";
4377
+ const prefixLen = GRANULARITY_LEN[granularity];
4378
+ const minRank = severityRank(query3.minSeverity ?? "WARN");
4379
+ const buckets = /* @__PURE__ */ new Map();
4380
+ let linesScanned = 0;
4381
+ let totalCounted = 0;
4382
+ try {
4383
+ const rl = (0, import_node_readline.createInterface)({
4384
+ input: (0, import_node_fs4.createReadStream)(filePath, { encoding: "utf-8" }),
4385
+ crlfDelay: Infinity
4386
+ });
4387
+ for await (const line of rl) {
4388
+ linesScanned++;
4389
+ const parsed = parseLogLine(line);
4390
+ if (parsed.timestamp === void 0 || parsed.severity === void 0) continue;
4391
+ if (severityRank(parsed.severity) < minRank) continue;
4392
+ if (query3.rank !== void 0 && parsed.rank !== query3.rank) continue;
4393
+ const key = parsed.timestamp.slice(0, prefixLen);
4394
+ const bucket = buckets.get(key) ?? {};
4395
+ bucket[parsed.severity] = (bucket[parsed.severity] ?? 0) + 1;
4396
+ buckets.set(key, bucket);
4397
+ totalCounted++;
4398
+ }
4399
+ } catch (err) {
4400
+ const message = err instanceof Error ? err.message : String(err);
4401
+ return { buckets: [], linesScanned, totalCounted, error: message };
4402
+ }
4403
+ const result = [];
4404
+ for (const [bucket, counts] of buckets) {
4405
+ const total = Object.values(counts).reduce((a, b) => a + b, 0);
4406
+ result.push({ bucket, counts, total });
4407
+ }
4408
+ return { buckets: result, linesScanned, totalCounted };
4409
+ }
4410
+
4411
+ // src/bundle/bundle-index.ts
4412
+ var import_promises4 = require("fs/promises");
4413
+ var import_node_path5 = require("path");
4414
+
4415
+ // src/bundle/classify-file.ts
4416
+ var ROLLING_ID_RE = /core-gpudb-rolling-(r\d+|hm)\.log(?:\.\d+)?$/;
4417
+ var EXE_ID_RE = /gpudb-exe-(r\d+|hm)-/;
4418
+ var HOST_RE = /\b(node\w+)\b/;
4419
+ var LOG_RE = /\.log(?:\.\d+)?$/;
4420
+ var LOKI_RANK_RE = /^rank(\d+)\.log$/;
4421
+ var LOKI_HM_BASE = "hostmanager.log";
4422
+ function rankOrService(id) {
4423
+ return id === "hm" ? { service: "host-manager" } : { rank: id };
4424
+ }
4425
+ function basename2(relPath) {
4426
+ const parts = relPath.split("/");
4427
+ return parts[parts.length - 1] ?? relPath;
4428
+ }
4429
+ function dirOf(relPath) {
4430
+ const parts = relPath.split("/");
4431
+ return parts.length > 1 ? parts[parts.length - 2] : "";
4432
+ }
4433
+ function inferHost(relPath) {
4434
+ return HOST_RE.exec(relPath)?.[1] ?? void 0;
4435
+ }
4436
+ function componentName(base) {
4437
+ return base.replace(/\.\d+$/, "").replace(/(\.log)+$/, "").replace(/^core-gpudb-/, "").replace(/^gpudb-/, "").replace(/-node\w+$/, "");
4438
+ }
4439
+ function classifyFile(relPath) {
4440
+ const base = basename2(relPath);
4441
+ const dir = dirOf(relPath);
4442
+ const host = inferHost(relPath);
4443
+ if (base.endsWith(".conf")) {
4444
+ return { kind: "config", ...host ? { host } : {} };
4445
+ }
4446
+ if (base === "logfiles.txt") {
4447
+ return { kind: "manifest", ...host ? { host } : {} };
4448
+ }
4449
+ if (base === "errors.txt" || base.endsWith("erros.txt")) {
4450
+ return { kind: "collection-errors", ...host ? { host } : {} };
4451
+ }
4452
+ if (base === "gpudb.txt") {
4453
+ return { kind: "version-info", ...host ? { host } : {} };
4454
+ }
4455
+ const exeId = EXE_ID_RE.exec(base);
4456
+ if (exeId) {
4457
+ return { kind: "process-info", ...rankOrService(exeId[1]), ...host ? { host } : {} };
4458
+ }
4459
+ if (LOG_RE.test(base)) {
4460
+ const rolling = ROLLING_ID_RE.exec(base);
4461
+ if (rolling) {
4462
+ return { kind: "core-log", ...rankOrService(rolling[1]), ...host ? { host } : {} };
4463
+ }
4464
+ if (dir === "logs") {
4465
+ const lokiRank = LOKI_RANK_RE.exec(base);
4466
+ const lokiId = lokiRank ? `r${lokiRank[1]}` : base === LOKI_HM_BASE ? "hm" : void 0;
4467
+ if (lokiId !== void 0) {
4468
+ return { kind: "loki-tail", ...rankOrService(lokiId), ...host ? { host } : {} };
4469
+ }
4470
+ return { kind: "loki-tail", component: componentName(base), ...host ? { host } : {} };
4471
+ }
4472
+ return { kind: "component-log", component: componentName(base), ...host ? { host } : {} };
4473
+ }
4474
+ if (base.endsWith(".txt")) {
4475
+ return { kind: "os-diag", ...host ? { host } : {} };
4476
+ }
4477
+ return { kind: "unknown", ...host ? { host } : {} };
4478
+ }
4479
+
4480
+ // src/bundle/bundle-index.ts
4481
+ async function buildIndex(rootDir) {
4482
+ let relPaths;
4483
+ try {
4484
+ relPaths = await (0, import_promises4.readdir)(rootDir, { recursive: true });
4485
+ } catch {
4486
+ return [];
4487
+ }
4488
+ const settled = await Promise.all(
4489
+ relPaths.map(async (rel) => {
4490
+ const relPath = rel.split("\\").join("/");
4491
+ const absPath = (0, import_node_path5.join)(rootDir, rel);
4492
+ try {
4493
+ const s = await (0, import_promises4.lstat)(absPath);
4494
+ if (s.isSymbolicLink() || !s.isFile()) return null;
4495
+ const c = classifyFile(relPath);
4496
+ return {
4497
+ relPath,
4498
+ absPath,
4499
+ kind: c.kind,
4500
+ ...c.rank !== void 0 ? { rank: c.rank } : {},
4501
+ ...c.service !== void 0 ? { service: c.service } : {},
4502
+ ...c.host !== void 0 ? { host: c.host } : {},
4503
+ ...c.component !== void 0 ? { component: c.component } : {},
4504
+ sizeBytes: s.size
4505
+ };
4506
+ } catch {
4507
+ return null;
4508
+ }
4509
+ })
4510
+ );
4511
+ return settled.filter((e) => e !== null).sort((a, b) => a.relPath.localeCompare(b.relPath));
4512
+ }
4513
+
4514
+ // src/bundle/BundleSource.ts
4515
+ var GPUDB_VERSION_RE = /GPUdb version\s*:\s*(\S+)/;
4516
+ function selectLogFiles(index, opts) {
4517
+ if (opts.component !== void 0) {
4518
+ return index.filter(
4519
+ (e) => (e.kind === "component-log" || e.kind === "loki-tail") && e.component === opts.component
4520
+ );
4521
+ }
4522
+ if (opts.hostManager) {
4523
+ const hmCore = index.filter((e) => e.kind === "core-log" && e.service === "host-manager");
4524
+ if (hmCore.length > 0) return hmCore;
4525
+ return index.filter((e) => e.kind === "loki-tail" && e.service === "host-manager");
4526
+ }
4527
+ const matchesRank = (e) => opts.rank === void 0 || e.rank === opts.rank;
4528
+ const coreLogs = index.filter((e) => e.kind === "core-log" && matchesRank(e));
4529
+ const ranksWithCore = new Set(
4530
+ coreLogs.map((e) => e.rank).filter((r) => r !== void 0)
4531
+ );
4532
+ const supplementalTails = index.filter(
4533
+ (e) => e.kind === "loki-tail" && e.rank !== void 0 && matchesRank(e) && !ranksWithCore.has(e.rank)
4534
+ );
4535
+ const rankBearing = [...coreLogs, ...supplementalTails];
4536
+ const core = rankBearing.length > 0 ? rankBearing : index.filter((e) => e.kind === "loki-tail" && matchesRank(e));
4537
+ if (opts.includeComponents) {
4538
+ return [...core, ...index.filter((e) => e.kind === "component-log")];
4539
+ }
4540
+ return core;
4541
+ }
4542
+ function toLineQuery(q) {
4543
+ return {
4544
+ ...q.regex !== void 0 ? { regex: q.regex } : {},
4545
+ ...q.caseSensitive !== void 0 ? { caseSensitive: q.caseSensitive } : {},
4546
+ ...q.minSeverity !== void 0 ? { minSeverity: q.minSeverity } : {},
4547
+ ...q.fromTs !== void 0 ? { fromTs: q.fromTs } : {},
4548
+ ...q.toTs !== void 0 ? { toTs: q.toTs } : {},
4549
+ ...q.maxMatches !== void 0 ? { maxMatches: q.maxMatches } : {}
4550
+ };
4551
+ }
4552
+ function toTimelineLineQuery(q) {
4553
+ return {
4554
+ ...q.minSeverity !== void 0 ? { minSeverity: q.minSeverity } : {},
4555
+ ...q.granularity !== void 0 ? { granularity: q.granularity } : {}
4556
+ };
4557
+ }
4558
+ async function createBundleSource(rootDir) {
4559
+ const root = (0, import_node_path6.resolve)(rootDir);
4560
+ const index = await buildIndex(root);
4561
+ const resolve3 = (relPath) => {
4562
+ const abs = (0, import_node_path6.resolve)(root, relPath);
4563
+ if (abs !== root && !abs.startsWith(root + import_node_path6.sep)) return void 0;
4564
+ return abs;
4565
+ };
4566
+ const findByKind = (kind) => index.find((e) => e.kind === kind);
4567
+ const inventoryValue = (() => {
4568
+ const byKind = {};
4569
+ const rankSet = /* @__PURE__ */ new Set();
4570
+ const serviceSet = /* @__PURE__ */ new Set();
4571
+ let totalBytes = 0;
4572
+ for (const e of index) {
4573
+ byKind[e.kind] = (byKind[e.kind] ?? 0) + 1;
4574
+ totalBytes += e.sizeBytes;
4575
+ if (e.rank) rankSet.add(e.rank);
4576
+ if (e.service) serviceSet.add(e.service);
4577
+ }
4578
+ return {
4579
+ totalFiles: index.length,
4580
+ totalBytes,
4581
+ byKind,
4582
+ ranks: [...rankSet].sort(),
4583
+ services: [...serviceSet].sort()
4584
+ };
4585
+ })();
4586
+ const detectVersion = async () => {
4587
+ const versionFile = findByKind("version-info");
4588
+ if (versionFile) {
4589
+ try {
4590
+ const parsed = parseSysinfo(await (0, import_promises5.readFile)(versionFile.absPath, "utf-8"));
4591
+ for (const block of parsed.blocks) {
4592
+ const m = GPUDB_VERSION_RE.exec(block.output);
4593
+ if (m) return m[1];
4594
+ }
4595
+ } catch {
4596
+ }
4597
+ }
4598
+ const configFile = findByKind("config");
4599
+ if (configFile) {
4600
+ try {
4601
+ const entries = parseIni(await (0, import_promises5.readFile)(configFile.absPath, "utf-8"));
4602
+ return entries.find((e) => e.key === "file_version")?.value;
4603
+ } catch {
4604
+ return void 0;
4605
+ }
4606
+ }
4607
+ return void 0;
4608
+ };
4609
+ const readConfig = async (opts = {}) => {
4610
+ const configFile = index.find((e) => e.kind === "config" && e.relPath.endsWith("gpudb.conf")) ?? findByKind("config");
4611
+ if (!configFile) return { error: "no gpudb.conf found in bundle" };
4612
+ try {
4613
+ const entries = parseIni(await (0, import_promises5.readFile)(configFile.absPath, "utf-8"));
4614
+ return { entries: filterIni(entries, opts), file: configFile.relPath };
4615
+ } catch (err) {
4616
+ return { error: err instanceof Error ? err.message : String(err) };
4617
+ }
4618
+ };
4619
+ const readSysinfo = async (name) => {
4620
+ const entry = index.find(
4621
+ (e) => e.relPath === name || e.relPath.endsWith("/" + name) || e.relPath.split("/").pop() === name
4622
+ );
4623
+ if (!entry) return { error: `no bundle file named "${name}"` };
4624
+ const abs = resolve3(entry.relPath);
4625
+ if (!abs) return { error: `path "${name}" escapes the bundle root` };
4626
+ try {
4627
+ return parseSysinfo(await (0, import_promises5.readFile)(abs, "utf-8"));
4628
+ } catch (err) {
4629
+ return { error: err instanceof Error ? err.message : String(err) };
4630
+ }
4631
+ };
4632
+ const searchLogs = async (query3) => {
4633
+ const files = selectLogFiles(index, query3);
4634
+ const lineQuery = toLineQuery(query3);
4635
+ const matches = [];
4636
+ const filesScanned = [];
4637
+ let totalMatched = 0;
4638
+ let linesScanned = 0;
4639
+ const maxMatches = query3.maxMatches ?? DEFAULT_MAX_MATCHES;
4640
+ for (const file of files) {
4641
+ const remaining = Math.max(0, maxMatches - matches.length);
4642
+ const r = await searchLogFile(file.absPath, { ...lineQuery, maxMatches: remaining });
4643
+ filesScanned.push(file.relPath);
4644
+ totalMatched += r.totalMatched;
4645
+ linesScanned += r.linesScanned;
4646
+ for (const m of r.matches) matches.push({ ...m, file: file.relPath });
4647
+ }
4648
+ return {
4649
+ matches,
4650
+ totalMatched,
4651
+ linesScanned,
4652
+ filesScanned,
4653
+ capped: totalMatched > matches.length
4654
+ };
4655
+ };
4656
+ const logTimeline = async (query3) => {
4657
+ const files = selectLogFiles(index, query3);
4658
+ const lineQuery = toTimelineLineQuery(query3);
4659
+ const merged = /* @__PURE__ */ new Map();
4660
+ const filesScanned = [];
4661
+ let linesScanned = 0;
4662
+ let totalCounted = 0;
4663
+ for (const file of files) {
4664
+ const r = await aggregateTimeline(file.absPath, lineQuery);
4665
+ filesScanned.push(file.relPath);
4666
+ linesScanned += r.linesScanned;
4667
+ totalCounted += r.totalCounted;
4668
+ for (const b of r.buckets) {
4669
+ const existing = merged.get(b.bucket) ?? {};
4670
+ for (const [sev, n] of Object.entries(b.counts)) existing[sev] = (existing[sev] ?? 0) + n;
4671
+ merged.set(b.bucket, existing);
4672
+ }
4673
+ }
4674
+ const buckets = [...merged.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([bucket, counts]) => ({
4675
+ bucket,
4676
+ counts,
4677
+ total: Object.values(counts).reduce((x, y) => x + y, 0)
4678
+ }));
4679
+ return { buckets, linesScanned, totalCounted, filesScanned };
4680
+ };
4681
+ const collectionErrors = async () => {
4682
+ const files = index.filter((e) => e.kind === "collection-errors");
4683
+ const lines = [];
4684
+ for (const file of files) {
4685
+ try {
4686
+ const content = await (0, import_promises5.readFile)(file.absPath, "utf-8");
4687
+ for (const line of content.split("\n")) {
4688
+ const trimmed = line.trim();
4689
+ if (trimmed !== "" && !/^-{3,}$/.test(trimmed)) lines.push(trimmed);
4690
+ }
4691
+ } catch {
4692
+ }
4693
+ }
4694
+ return lines;
4695
+ };
4696
+ return {
4697
+ root,
4698
+ listFiles: () => index,
4699
+ inventory: () => inventoryValue,
4700
+ resolve: resolve3,
4701
+ detectVersion,
4702
+ readConfig,
4703
+ readSysinfo,
4704
+ searchLogs,
4705
+ logTimeline,
4706
+ collectionErrors
4707
+ };
4708
+ }
4709
+
4710
+ // src/bundle/verify-bundle.ts
4711
+ var ARCHIVE_RE = /\.(tgz|tar\.gz|tar|gz|zip)$/i;
4712
+ var EXPECTED_KINDS = ["config", "core-log"];
4713
+ async function verifyBundle(bundlePath) {
4714
+ let info;
4715
+ try {
4716
+ info = await (0, import_promises6.stat)(bundlePath);
4717
+ } catch {
4718
+ return { ok: false, error: `bundle path does not exist: ${bundlePath}` };
4719
+ }
4720
+ if (!info.isDirectory()) {
4721
+ if (ARCHIVE_RE.test(bundlePath)) {
4722
+ return {
4723
+ ok: false,
4724
+ error: `bundle mode expects an extracted directory, not an archive. Run \`tar xzf ${bundlePath}\` and pass the resulting directory.`
4725
+ };
4726
+ }
4727
+ return { ok: false, error: `bundle path is not a directory: ${bundlePath}` };
4728
+ }
4729
+ const bundleSource = await createBundleSource(bundlePath);
4730
+ const inventory = bundleSource.inventory();
4731
+ if (inventory.totalFiles === 0) {
4732
+ return { ok: false, error: `no readable files found in bundle directory: ${bundlePath}` };
4733
+ }
4734
+ const missingExpected = EXPECTED_KINDS.filter((k) => (inventory.byKind[k] ?? 0) === 0);
4735
+ const kineticaVersion = await bundleSource.detectVersion();
4736
+ return {
4737
+ ok: true,
4738
+ bundleSource,
4739
+ ...kineticaVersion !== void 0 ? { kineticaVersion } : {},
4740
+ inventory,
4741
+ missingExpected
4742
+ };
4743
+ }
4744
+
4745
+ // src/tools/bundle/load-bundle.ts
4746
+ var BundleLoadSchema = import_zod23.z.object({
4747
+ path: import_zod23.z.string().min(1).optional()
4748
+ });
4749
+ async function bundleLoad(holder, args, promptForPath, confirmPath) {
4750
+ let path2;
4751
+ if (args.path !== void 0) {
4752
+ if (confirmPath && !await confirmPath(args.path)) {
4753
+ return {
4754
+ ok: false,
4755
+ status: 0,
4756
+ error: `Operator declined to load a bundle from "${args.path}".`,
4757
+ raw: args.path
4758
+ };
4759
+ }
4760
+ path2 = args.path;
4761
+ } else {
4762
+ path2 = promptForPath ? await promptForPath() : void 0;
4763
+ }
4764
+ if (!path2) {
4765
+ return {
4766
+ ok: false,
4767
+ status: 0,
4768
+ error: "No bundle path provided and no directory picker is available. Ask the operator for the extracted bundle directory path and pass it as `path`.",
4769
+ raw: ""
4770
+ };
4771
+ }
4772
+ const result = await verifyBundle(path2);
4773
+ if (!result.ok) {
4774
+ return { ok: false, status: 0, error: result.error, raw: path2 };
4775
+ }
4776
+ holder.set(result.bundleSource);
4777
+ const missingNote = result.missingExpected.length > 0 ? ` Missing expected artifact(s): ${result.missingExpected.join(", ")} (treat as Evidence Gaps).` : "";
4778
+ return {
4779
+ ok: true,
4780
+ // Loading a bundle is SETUP, not an investigation. Do not auto-proceed — the
4781
+ // operator hasn't said what they want yet. End the turn and ask.
4782
+ note: `Bundle attached. Do NOT start investigating yet \u2014 ask the operator what they want to investigate, then proceed.${missingNote}`,
4783
+ data: {
4784
+ loaded: true,
4785
+ path: path2,
4786
+ detected_version: result.kineticaVersion ?? "unknown",
4787
+ total_files: result.inventory.totalFiles,
4788
+ ranks_present: result.inventory.ranks.join(", ") || "none",
4789
+ counts_by_kind: result.inventory.byKind,
4790
+ missing_expected: result.missingExpected.join(", ") || "none"
4791
+ }
4792
+ };
4793
+ }
4794
+
4795
+ // src/tools/bundle/index.ts
4796
+ var BUNDLE_TOOL_NAMES = [
4797
+ "kinetica_load_bundle",
4798
+ "kinetica_bundle_list_files",
4799
+ "kinetica_bundle_log_timeline",
4800
+ "kinetica_bundle_search_logs",
4801
+ "kinetica_bundle_read_config",
4802
+ "kinetica_bundle_read_sysinfo"
4803
+ ];
4804
+ var text = (s) => ({ content: [{ type: "text", text: s }] });
4805
+ function notLoaded() {
4806
+ return {
4807
+ ok: false,
4808
+ status: 0,
4809
+ error: "No support bundle is loaded. Ask the operator for the extracted bundle directory path and call kinetica_load_bundle first.",
4810
+ raw: ""
4811
+ };
4812
+ }
4813
+ async function withSource(holder, fn) {
4814
+ const source = holder.get();
4815
+ if (!source) return applyOutputPipeline(notLoaded());
4816
+ return applyOutputPipeline(await fn(source));
4817
+ }
4818
+ function makeLoadBundleTool(holder, deps) {
4819
+ return (0, import_claude_agent_sdk4.tool)(
4820
+ "kinetica_load_bundle",
4821
+ "Attach an extracted Kinetica support bundle (gpudb_sysinfo directory) so the kinetica_bundle_* tools can read its logs/config/host-diagnostics. When the operator wants to analyze a support bundle, call this tool WITHOUT a path \u2014 they will be shown an interactive directory picker to choose it. Do NOT ask for the path in chat. (You may pass an explicit `path` if the operator already gave you one; it must be a directory, not a .tgz.)",
4822
+ BundleLoadSchema.shape,
4823
+ async (args) => text(
4824
+ applyOutputPipeline(await bundleLoad(holder, args, deps?.promptForPath, deps?.confirmPath))
4825
+ ),
4826
+ { annotations: { readOnly: true } }
4827
+ );
4828
+ }
4829
+ function makeListFilesTool(holder) {
4830
+ return (0, import_claude_agent_sdk4.tool)(
4831
+ "kinetica_bundle_list_files",
4832
+ "Inventory the attached support bundle: detected GPUdb version, ranks present (numeric ranks only), services present (e.g. host-manager \u2014 a singleton service, NOT a rank), file counts/sizes by kind, and how many collection commands failed. Each file row includes a `description` of what it contains (e.g. mem.txt \u2192 memory/THP, gpu.txt \u2192 nvidia-smi) so you can pick the right file without reading it. Call this FIRST after a bundle is attached. Optional `kind` filters the file list (e.g. core-log, component-log, config, os-diag).",
4833
+ BundleListFilesSchema.shape,
4834
+ async (args) => text(await withSource(holder, (s) => bundleListFiles(s, args))),
4835
+ { annotations: { readOnly: true } }
4836
+ );
4837
+ }
4838
+ function makeLogTimelineTool(holder) {
4839
+ return (0, import_claude_agent_sdk4.tool)(
4840
+ "kinetica_bundle_log_timeline",
4841
+ "Aggregate bundle log lines into per-time-bucket severity counts across ranks \u2014 the incident shape. Call this BEFORE search_logs to find WHEN errors spiked, then drill in with a tight time window. Defaults: min_severity=WARN, granularity=hour, core logs (all ranks AND the host manager). Narrow with rank=<r0|r1|\u2026> (numeric ranks only) or host_manager=true for the host-manager log (a service, not a rank). Set include_components=true or component=<name> to include component logs. Note severity order is WARN < UERR < ERROR < FATAL, so min_severity=ERROR EXCLUDES UERR (user-error) lines \u2014 use UERR or WARN to include them.",
4842
+ BundleLogTimelineSchema.shape,
4843
+ async (args) => text(await withSource(holder, (s) => bundleLogTimeline(s, args))),
4844
+ { annotations: { readOnly: true } }
4845
+ );
4846
+ }
4847
+ function makeSearchLogsTool(holder) {
4848
+ return (0, import_claude_agent_sdk4.tool)(
4849
+ "kinetica_bundle_search_logs",
4850
+ "Search bundle logs for matching lines by regex (case-insensitive), min_severity, time window (from_ts/to_ts as 'YYYY-MM-DD HH:MM:SS.mmm'; a partial prefix like a timeline bucket label '2026-06-11 15' also works \u2014 it is widened to cover that whole period), and rank/host_manager/component. Streamed and bounded \u2014 the default 200-match cap is shared across all files; when capped, narrow the query (the total may be a lower bound). Defaults to core logs across all ranks AND the host manager; narrow with rank=<r0|r1|\u2026> (numeric ranks only) or host_manager=true for the host-manager log (a service, not a rank); set component or include_components for component logs. Severity order is WARN < UERR < ERROR < FATAL, so min_severity=ERROR EXCLUDES UERR (user-error) lines.",
4851
+ BundleSearchLogsSchema.shape,
4852
+ async (args) => text(await withSource(holder, (s) => bundleSearchLogs(s, args))),
4853
+ { annotations: { readOnly: true } }
4854
+ );
4855
+ }
4856
+ function makeReadConfigTool(holder) {
4857
+ return (0, import_claude_agent_sdk4.tool)(
4858
+ "kinetica_bundle_read_config",
4859
+ "Read gpudb.conf from the attached bundle (the real on-disk config). Optionally filter by `section` (exact, case-insensitive) and/or `key` (substring, case-insensitive). Interpolation references like ${gaia.host0.address} are returned verbatim.",
4860
+ BundleReadConfigSchema.shape,
4861
+ async (args) => text(await withSource(holder, (s) => bundleReadConfig(s, args))),
4862
+ { annotations: { readOnly: true } }
4863
+ );
4864
+ }
4865
+ function makeReadSysinfoTool(holder) {
4866
+ return (0, import_claude_agent_sdk4.tool)(
4867
+ "kinetica_bundle_read_sysinfo",
4868
+ "Read an OS-diagnostic / process / version file's command blocks by name (e.g. mem.txt, cpu.txt, disk.txt, gpu.txt, net.txt, ps.txt, gpudb.txt, gpudb-exe-r0-*.txt). Returns each wrapped shell command and its output \u2014 host-level facts (memory, GPU, disk, THP, process args) the live endpoints never expose.",
4869
+ BundleReadSysinfoSchema.shape,
4870
+ async (args) => text(await withSource(holder, (s) => bundleReadSysinfo(s, args))),
4871
+ { annotations: { readOnly: true } }
4872
+ );
4873
+ }
4874
+ function makeBundleTools(holder, deps) {
4875
+ return [
4876
+ makeLoadBundleTool(holder, deps),
4877
+ makeListFilesTool(holder),
4878
+ makeLogTimelineTool(holder),
4879
+ makeSearchLogsTool(holder),
4880
+ makeReadConfigTool(holder),
4881
+ makeReadSysinfoTool(holder)
4882
+ ];
4883
+ }
4884
+ function createBundleRegistry() {
4885
+ return BUNDLE_TOOL_NAMES.reduce(
4886
+ (registry, name) => registry.registerReadOnlyTool(name),
4887
+ createRegistry()
4888
+ );
4889
+ }
4890
+
4891
+ // src/tools/bundle/catalog.ts
4892
+ var BUNDLE_TOOL_CATALOG = {
4893
+ kinetica_load_bundle: {
4894
+ reveals: "Attaches an extracted support bundle (directory path) for offline analysis",
4895
+ whenToUse: "When the operator wants to analyze a support bundle \u2014 ask for the path, then load"
4896
+ },
4897
+ kinetica_bundle_list_files: {
4898
+ reveals: "Bundle inventory: detected version, ranks, file kinds/sizes, failed collections",
4899
+ whenToUse: "First action of every bundle investigation (orientation)"
4900
+ },
4901
+ kinetica_bundle_log_timeline: {
4902
+ reveals: "WARN+ log lines bucketed by time + severity across ranks (incident shape)",
4903
+ whenToUse: "Right after list_files \u2014 find WHEN errors spiked before drilling in"
4904
+ },
4905
+ kinetica_bundle_search_logs: {
4906
+ reveals: "Matching log lines by regex/severity/time-window/rank (bounded, streamed)",
4907
+ whenToUse: "Drill into a time window or error pattern surfaced by the timeline"
4908
+ },
4909
+ kinetica_bundle_read_config: {
4910
+ reveals: "gpudb.conf entries (the real on-disk config), filterable by section/key",
4911
+ whenToUse: "Config drift, misconfiguration, parameter verification"
4912
+ },
4913
+ kinetica_bundle_read_sysinfo: {
4914
+ reveals: "OS-diag / process / version command blocks (mem, cpu, disk, gpu, ps, gpudb.txt)",
4915
+ whenToUse: "Host-level facts: memory pressure, GPU presence, disk, THP, process args"
4916
+ }
4917
+ };
4918
+ function buildBundleEvidenceChecklist() {
4919
+ const rows = BUNDLE_TOOL_NAMES.map((name) => {
4920
+ const entry = BUNDLE_TOOL_CATALOG[name];
4921
+ return `| ${name} | ${entry.reveals} | ${entry.whenToUse} |`;
4922
+ });
4923
+ return [
4924
+ "| Tool | What it reveals | When to use |",
4925
+ "|------|----------------|-------------|",
4926
+ ...rows
4927
+ ].join("\n");
4928
+ }
4929
+
4930
+ // src/agent/bundle-system-prompt.ts
4931
+ function buildBundleSystemPrompt(kineticaVersion, playbooks, references, bundleReferences) {
4932
+ const t = "`";
4933
+ const versionSection = kineticaVersion ? `**Kinetica Version:** ${kineticaVersion} (detected from the bundle's gpudb.txt / gpudb.conf)` : "**Kinetica Version:** Unknown \u2014 check gpudb.txt via kinetica_bundle_read_sysinfo, or file_version via kinetica_bundle_read_config.";
4934
+ return `You are an expert Kinetica GPU database administrator and diagnostician. You are operating in OFFLINE BUNDLE MODE: instead of a live database, you are investigating an extracted support bundle (gpudb_sysinfo) \u2014 a snapshot of logs, configuration, and host diagnostics captured from a node at a point in time.
4935
+
4936
+ ${versionSection}
4937
+
4938
+ ---
4939
+
4940
+ ## OFFLINE BUNDLE MODE \u2014 What This Means
4941
+
4942
+ **You are reading frozen, point-in-time evidence \u2014 not a live system.**
4943
+
4944
+ - You CANNOT run SQL, query system tables, re-probe the cluster, or apply fixes. There are no mutation tools. Your job is to diagnose from the captured files and RECOMMEND remediation for the operator to apply against the live system later.
4945
+ - The single highest-value evidence here is the **logs** \u2014 the live system exposes no log endpoint, so this is the one place the incident's narrative (the lead-up, the error cascade, the crash) is visible. Lean on them.
4946
+ - A bundle covers **one node**. It may contain multiple ranks (e.g. r0, r1) plus the host manager. The host manager is a singleton service (port 9300), **not a rank** \u2014 to search/timeline its log, pass ${t}host_manager: true${t} (NOT ${t}rank: "hm"${t}); ${t}kinetica_bundle_list_files${t} lists it under ${t}services_present${t}. Cross-node correlation is not possible from a single bundle.
4947
+ - **Do not assume cluster-wide clock synchronization.** Correlate events by message content as well as timestamp; note when timing is ambiguous.
4948
+ - Some collection commands may have FAILED (e.g. nvidia-smi on a CPU-only host). ${t}kinetica_bundle_list_files${t} reports how many \u2014 treat absent artifacts as Evidence Gaps, not as healthy.
4949
+
4950
+ ---
4951
+
4952
+ ## Investigation Protocol (read-only)
4953
+
4954
+ ### Pre-Investigation: Announce Your Plan
4955
+
4956
+ Before gathering evidence, announce a brief 2-3 line plan: restate the issue, list the first tools you'll use, then begin immediately.
4957
+
4958
+ ### Round 1 \u2014 Orient
4959
+
4960
+ - ${t}kinetica_bundle_list_files${t} \u2014 **ALWAYS FIRST.** Learn the detected version, which ranks are present, what file kinds exist, and how many collections failed.
4961
+ - ${t}kinetica_bundle_log_timeline${t} (min_severity: WARN) \u2014 get the incident shape: when did WARN/ERROR/FATAL spike, and on which rank?
4962
+
4963
+ ### Round 2 \u2014 Drill Down
4964
+
4965
+ Based on the timeline, narrow in:
4966
+ - ${t}kinetica_bundle_search_logs${t} \u2014 search the spike window by regex/severity/rank. You can pass the timeline's hot bucket label straight into from_ts/to_ts (e.g. from_ts/to_ts = ${t}2026-06-11 15${t} searches that whole hour). Look for FATAL/ERROR clusters, stack traces, OOM, segfaults, failed allocations, stale-rank/heartbeat loss, rebalance failures. Remember UERR (user errors) rank below ERROR \u2014 use min_severity=WARN or UERR to include them.
4967
+ - ${t}kinetica_bundle_read_sysinfo${t} \u2014 corroborate with host facts: mem.txt (memory pressure, swap, THP), gpu.txt (GPU presence/OOM), disk.txt (disk full), cpu.txt, ps.txt, gpudb-exe-*.txt (process args/limits).
4968
+
4969
+ ### Round 3 \u2014 Confirm
4970
+
4971
+ - ${t}kinetica_bundle_read_config${t} \u2014 check gpudb.conf for misconfiguration / config-drift relevant to your hypothesis (tier limits, thread pools, ports, HA).
4972
+ - Re-search logs to confirm the root-cause sequence.
4973
+
4974
+ After Round 3 you MUST write the report \u2014 even if uncertainty remains. There are no mutation or verification rounds in bundle mode: you recommend, you do not apply.
4975
+
4976
+ ### Parallel Tool Calls
4977
+
4978
+ Issue independent reads together where possible (e.g. timeline + list_files, or a log search alongside a sysinfo read).
4979
+
4980
+ ---
4981
+
4982
+ ## Evidence Checklist \u2014 Bundle Tools
4983
+
4984
+ ${buildBundleEvidenceChecklist()}
4985
+
4986
+ ---
4987
+
4988
+ ${buildFailurePatternsSection(playbooks)}
4989
+
4990
+ ${buildReferenceSection([...bundleReferences ?? [], ...references ?? []])}
4991
+
4992
+ ---
4993
+
4994
+ ## Analysis Instructions
4995
+
4996
+ ### Commit to the Best Hypothesis
4997
+
4998
+ After gathering evidence, name specific root causes \u2014 no generic hedging.
4999
+
5000
+ **DO:**
5001
+ - "Root cause: rank 0 crashed with a segmentation fault (signal 11) at 15:18:52 (core-gpudb-rolling-r0.log:Job.cpp:9), preceded by 57 ERROR lines in the 15:00 hour."
5002
+ - "If uncertain, rank top 2-3 hypotheses by likelihood: Primary (70%): X; Secondary (25%): Y."
5003
+
5004
+ **DO NOT:**
5005
+ - "There could be various reasons..." / "Further investigation may be needed..."
5006
+
5007
+ ### Tie Evidence to Conclusions
5008
+
5009
+ Every conclusion must cite specific evidence \u2014 a file, a timestamp, a log line, a config key. Example: "GPU OOM is unlikely: gpu.txt shows nvidia-smi FAILED (exit 127) and gpudb-exe shows no --gpu rank args \u2014 this is a CPU-only host."
5010
+
5011
+ ### Evidence Gap Handling
5012
+
5013
+ Note gaps and continue \u2014 never halt on a missing artifact:
5014
+ - "Host memory at crash: unavailable (mem.txt is a point-in-time snapshot taken during collection, not at crash time)."
5015
+ - "GPU metrics: unavailable (nvidia-smi collection FAILED \u2014 CPU-only host)."
5016
+
5017
+ ---
5018
+
5019
+ ## Fix Instructions
5020
+
5021
+ Provide specific, actionable remediation tied to your findings, as a numbered list. Because you cannot act on the bundle, frame everything as recommendations for the operator to apply to the live system:
5022
+
5023
+ 1. Immediate manual actions the operator should take on the live system
5024
+ 2. Configuration changes to prevent recurrence (cite the gpudb.conf key + value)
5025
+ 3. Monitoring/alerting improvements
5026
+ 4. What to capture or verify on the live system to close remaining Evidence Gaps
5027
+
5028
+ ---
5029
+
5030
+ ## Post-Report Behavior
5031
+
5032
+ 1. Present the finished report in your response so the operator can read it.
5033
+ 2. **Ask BEFORE saving \u2014 never save unprompted.** After presenting the report, ask exactly: "Would you like me to save this report to disk? (yes/no)" and then STOP \u2014 end your turn and wait for the operator's answer. Do NOT call ${t}save_report${t} in the same turn as the question; the question must come first. Save only if they answer yes. (Exception: if checkpointing under budget pressure with a ${t}partial: true${t} report, save immediately without asking \u2014 preserving findings beats the prompt.)
5034
+ 3. After saving (or after the operator declines), ask: "Would you like to investigate another issue in this bundle, or end the session?"
5035
+ 4. On session end: summarize issues investigated and list saved report paths, then exit.
5036
+
5037
+ ---
5038
+
5039
+ ## Budget & Length Awareness
5040
+
5041
+ The session has a per-session budget guard that can end the run early. If the operator warns that the guard is approaching, STOP gathering evidence, call ${t}save_report${t} with ${t}partial: true${t}, state your best current hypothesis, and wind down. A partial report beats none. Treat the guard as a normal limit, never an error.
5042
+
5043
+ ---
5044
+
5045
+ ## Output Formatting
5046
+
5047
+ Synthesize findings into clean markdown tables (3-6 columns, **bold** key identifiers, consistent ${t}OK${t}/${t}WARN${t}/${t}ERROR${t} indicators). Do NOT dump raw log output \u2014 extract the most relevant lines.
5048
+
5049
+ ---
5050
+
5051
+ ## REPORT TEMPLATE
5052
+
5053
+ At the end of each investigation, generate a structured markdown report using this EXACT template and section order:
5054
+
5055
+ \`\`\`markdown
5056
+ ` + REPORT_TEMPLATE + `\`\`\`
5057
+
5058
+ **CRITICAL:** Use this exact section order. The metadata table comes first. Summary before Remediation. Evidence Collected before Evidence Gaps.
5059
+
5060
+ **Bundle-mode report notes:**
5061
+ - In the metadata, make clear this diagnosis is from an offline support bundle (note the node and detected version).
5062
+ - "Mutations Applied" will always be "None (offline bundle \u2014 read-only)". "Post-Remediation Verification" should state that verification requires re-running diagnostics against the live system.
5063
+ - "Evidence Collected" \u2014 cite specific files, timestamps, and log lines (no raw dumps; the 3-10 most relevant findings).
5064
+ `;
5065
+ }
5066
+
5067
+ // src/bundle/bundle-holder.ts
5068
+ function createBundleHolder(initial) {
5069
+ let current = initial;
5070
+ return {
5071
+ get: () => current,
5072
+ set: (source) => {
5073
+ current = source;
5074
+ },
5075
+ isLoaded: () => current !== void 0
5076
+ };
5077
+ }
5078
+
5079
+ // src/cli/pick-bundle-path.ts
5080
+ var import_promises7 = require("fs/promises");
5081
+ var import_node_path7 = require("path");
5082
+ function isPermissionError(err) {
5083
+ if (typeof err !== "object" || err === null || !("code" in err)) return false;
5084
+ const code = err.code;
5085
+ return code === "EACCES" || code === "EPERM";
5086
+ }
5087
+ async function listDirectoryCandidates(term) {
5088
+ const input2 = term.trim() === "" ? "." : term;
5089
+ const endsWithSep = input2.endsWith("/");
5090
+ const baseDir = endsWithSep ? input2 : (0, import_node_path7.dirname)(input2) || ".";
5091
+ const prefix = endsWithSep ? "" : (0, import_node_path7.basename)(input2);
5092
+ const resolved = (0, import_node_path7.resolve)(baseDir);
5093
+ let entries;
5094
+ try {
5095
+ entries = await (0, import_promises7.readdir)(resolved, { withFileTypes: true });
5096
+ } catch (err) {
5097
+ if (isPermissionError(err)) return { kind: "denied", dir: resolved };
5098
+ return { kind: "ok", candidates: [] };
5099
+ }
5100
+ const candidates = entries.filter((e) => e.isDirectory() && e.name.startsWith(prefix)).map((e) => {
5101
+ const value = (0, import_node_path7.join)(baseDir, e.name);
5102
+ return { name: `${value}/`, value };
5103
+ }).sort((a, b) => a.value.localeCompare(b.value));
5104
+ return { kind: "ok", candidates };
5105
+ }
5106
+ function listingToChoices(listing) {
5107
+ if (listing.kind === "denied") {
5108
+ return [
5109
+ {
5110
+ name: `Permission denied reading ${listing.dir} \u2014 grant your terminal access in System Settings \u203A Privacy & Security \u203A Files & Folders (or Full Disk Access), then retry`,
5111
+ value: "",
5112
+ disabled: true
5113
+ }
5114
+ ];
5115
+ }
5116
+ return listing.candidates.map((c) => ({ name: c.name, value: c.value }));
5117
+ }
5118
+ async function promptBundleDirectory() {
5119
+ if (!process.stdin.isTTY) return void 0;
5120
+ try {
5121
+ return await search({
5122
+ message: "Select the support bundle directory (type to filter):",
5123
+ source: async (term) => listingToChoices(await listDirectoryCandidates(term ?? ""))
5124
+ });
5125
+ } catch {
5126
+ return void 0;
5127
+ }
5128
+ }
5129
+
5130
+ // src/approval/display.ts
5131
+ var import_picocolors6 = __toESM(require("picocolors"));
5132
+ var IMPACT_FALLBACK = "Impact unknown \u2014 review parameters carefully";
5133
+ var DIVIDER2 = import_picocolors6.default.dim("\u2500".repeat(50));
5134
+ var LABEL_WIDTH = 8;
5135
+ function formatLabel(label) {
5136
+ return ` ${label.padEnd(LABEL_WIDTH)}: `;
5137
+ }
5138
+ function renderApprovalPanel(toolName, toolInput, impact, beforeAfter, reasoningSummary) {
5139
+ const header = import_picocolors6.default.bold(import_picocolors6.default.yellow(" Mutation Approval Required"));
5140
+ const action = `${formatLabel("Action")}${import_picocolors6.default.bold(formatToolName(toolName))}`;
5141
+ const paramEntries = Object.entries(toolInput);
5142
+ const paramSection = paramEntries.length === 0 ? " (no parameters)" : paramEntries.map(([key, value]) => {
5143
+ const formatted = typeof value === "string" ? value : JSON.stringify(value, null, 2);
5144
+ return ` ${import_picocolors6.default.dim(key)}: ${formatted}`;
5145
+ }).join("\n");
5146
+ const impactLine = `${formatLabel("Impact")}${impact ?? IMPACT_FALLBACK}`;
5147
+ const prompt = import_picocolors6.default.dim(
5148
+ `${formatLabel("Respond")}y (proceed) | n (abort) | explain (show reasoning)`
5149
+ );
5150
+ const hasBeforeAfter = beforeAfter !== void 0 && beforeAfter.length > 0;
5151
+ const beforeAfterSection = hasBeforeAfter ? beforeAfter.map(
5152
+ (entry) => ` ${import_picocolors6.default.dim(entry.key)}: ${entry.current} ${import_picocolors6.default.yellow("->")} ${entry.proposed}`
5153
+ ).join("\n") : null;
5154
+ const hasReasoning = reasoningSummary !== void 0 && reasoningSummary.length > 0;
5155
+ const reasoningSection = hasReasoning ? `${formatLabel("Reason")}${reasoningSummary}` : null;
5156
+ const sections = ["", DIVIDER2, header, "", action, paramSection, ""];
5157
+ if (beforeAfterSection !== null) {
5158
+ sections.push(beforeAfterSection, "");
5159
+ }
5160
+ if (reasoningSection !== null) {
5161
+ sections.push(reasoningSection, "");
5162
+ }
5163
+ sections.push(impactLine, "", prompt, DIVIDER2, "");
5164
+ return sections.join("\n");
5165
+ }
5166
+
5167
+ // src/approval/gate.ts
5168
+ var DENY_MESSAGE = "User denied this mutation. Skip and continue with the investigation.";
5169
+ var REASONING_FALLBACK = "Reasoning not available. Review the action details above before proceeding.";
5170
+ function createApprovalGate(isReadOnly) {
5171
+ return async (toolName, toolInput, options) => {
5172
+ if (isReadOnly(toolName)) {
5173
+ return {
5174
+ behavior: "allow",
5175
+ updatedInput: toolInput,
5176
+ toolUseID: options.toolUseID
5177
+ };
5178
+ }
5179
+ const impact = options.decisionReason;
5180
+ const panel = renderApprovalPanel(toolName, toolInput, impact);
5181
+ console.error(panel);
5182
+ while (true) {
5183
+ try {
5184
+ const raw = await input({ message: "Proceed? (y/n/explain):" }, { signal: options.signal });
5185
+ const normalized = raw.trim().toLowerCase();
5186
+ if (normalized === "y") {
5187
+ process.stderr.write("\n");
5188
+ return {
5189
+ behavior: "allow",
5190
+ updatedInput: toolInput,
5191
+ toolUseID: options.toolUseID
5192
+ };
5193
+ }
5194
+ if (normalized === "n") {
5195
+ process.stderr.write("\n");
5196
+ return {
5197
+ behavior: "deny",
5198
+ message: DENY_MESSAGE,
5199
+ toolUseID: options.toolUseID
5200
+ };
5201
+ }
5202
+ if (normalized === "explain") {
5203
+ const reasoning = options.decisionReason;
5204
+ if (reasoning) {
5205
+ console.error(`
5206
+ Agent reasoning: ${reasoning}
5207
+ `);
5208
+ } else {
5209
+ console.error(`
5210
+ ${REASONING_FALLBACK}
5211
+ `);
5212
+ }
5213
+ }
5214
+ } catch {
5215
+ return {
5216
+ behavior: "deny",
5217
+ message: DENY_MESSAGE,
5218
+ toolUseID: options.toolUseID
5219
+ };
5220
+ }
5221
+ }
5222
+ };
5223
+ }
5224
+
5225
+ // src/agent/turn-gate.ts
5226
+ function createTurnGate() {
5227
+ let resolve3 = () => {
5228
+ };
5229
+ let promise = new Promise((r) => {
5230
+ resolve3 = r;
5231
+ });
5232
+ return Object.freeze({
5233
+ wait: () => promise,
5234
+ open: () => {
5235
+ resolve3();
5236
+ },
5237
+ close: () => {
5238
+ promise = new Promise((r) => {
5239
+ resolve3 = r;
5240
+ });
5241
+ }
5242
+ });
5243
+ }
5244
+
5245
+ // src/output/render-markdown.ts
5246
+ var import_picocolors7 = __toESM(require("picocolors"));
5247
+ var HEADING_RE = /^(#{1,6})\s+(.+)$/;
5248
+ var HRULE_RE = /^\s*([-*_])\1{2,}\s*$/;
5249
+ var BULLET_RE = /^(\s*)[-*+]\s+(.*)$/;
5250
+ var BOLD_RE = /\*\*(.+?)\*\*/g;
5251
+ var INLINE_CODE_RE = /`([^`]+)`/g;
5252
+ var UPPERWORD_RE = /[A-Z]{2,}/;
5253
+ var ANSI_RE = /\x1b\[[0-9;]*m/g;
5254
+ var DEFAULT_WIDTH = 80;
5255
+ function terminalWidth() {
5256
+ const cols = process.stderr.columns;
5257
+ return typeof cols === "number" && cols > 0 ? cols : DEFAULT_WIDTH;
5258
+ }
5259
+ var SEVERITY_RULES = [
5260
+ { re: /\b(FATAL|ERROR|UERR|CRITICAL|SEGV|SIGSEGV)\b/, color: import_picocolors7.default.red, glyph: "\u2717" },
5261
+ { re: /\b(WARN|WARNING)\b/, color: import_picocolors7.default.yellow, glyph: "\u26A0" },
5262
+ { re: /\b(OK|PASS|PASSED|HEALTHY)\b/, color: import_picocolors7.default.green, glyph: "\u2713" }
5263
+ ];
5264
+ function firstSeverityRule(text2) {
5265
+ return SEVERITY_RULES.find((rule) => rule.re.test(text2));
5266
+ }
5267
+ function styleInline(text2) {
5268
+ let out = text2;
5269
+ if (out.includes("`")) out = out.replace(INLINE_CODE_RE, (_, code) => purple(code));
5270
+ if (out.includes("**")) out = out.replace(BOLD_RE, (_, b) => import_picocolors7.default.bold(b));
5271
+ if (UPPERWORD_RE.test(out)) {
5272
+ for (const rule of SEVERITY_RULES) {
5273
+ out = out.replace(rule.re, (token) => rule.color(token));
5274
+ }
5275
+ }
5276
+ return out;
5277
+ }
5278
+ function bulletFor(itemText) {
5279
+ const rule = firstSeverityRule(itemText);
5280
+ return rule ? rule.color(rule.glyph) : purple("\u2022");
5281
+ }
5282
+ function visibleWidth(text2) {
5283
+ return styleInline(text2).replace(ANSI_RE, "").length;
5284
+ }
5285
+ function renderMarkdownLine(line) {
5286
+ if (HRULE_RE.test(line)) {
5287
+ return import_picocolors7.default.dim("\u2500".repeat(terminalWidth()));
5288
+ }
5289
+ const heading = HEADING_RE.exec(line);
5290
+ if (heading) {
5291
+ const level = heading[1].length;
5292
+ const text2 = heading[2];
5293
+ if (level === 1) {
5294
+ return `
5295
+ ${import_picocolors7.default.bold(pink(text2))}
5296
+ ${import_picocolors7.default.dim("\u2500".repeat(terminalWidth()))}`;
5297
+ }
5298
+ if (level === 2) {
5299
+ return `
5300
+ ${import_picocolors7.default.bold(pink(`\u258C ${text2}`))}`;
5301
+ }
5302
+ return import_picocolors7.default.bold(purple(text2));
5303
+ }
5304
+ const bullet = BULLET_RE.exec(line);
5305
+ if (bullet) {
5306
+ const [, indent, item] = bullet;
5307
+ return `${indent}${bulletFor(item)} ${styleInline(item)}`;
5308
+ }
5309
+ return styleInline(line);
5310
+ }
5311
+
5312
+ // src/output/reformat-tables.ts
5313
+ var SEPARATOR_CELL_RE = /^:?-+:?$/;
5314
+ function isSeparatorCell(cell) {
5315
+ return SEPARATOR_CELL_RE.test(cell);
5316
+ }
5317
+ function isSeparatorRow(cells) {
5318
+ return cells.length > 0 && cells.every(isSeparatorCell);
5319
+ }
5320
+ function parseCells(line) {
5321
+ return line.split("|").slice(1, -1).map((c) => c.trim());
5322
+ }
5323
+ function reformatTableBlock(lines) {
5324
+ const parsed = lines.map(parseCells);
5325
+ const colCount = Math.max(...parsed.map((row) => row.length));
5326
+ const normalised = parsed.map((row) => {
5327
+ const padded = [...row];
5328
+ while (padded.length < colCount) {
5329
+ padded.push("");
5330
+ }
5331
+ return padded;
5332
+ });
5333
+ const colWidths = Array.from(
5334
+ { length: colCount },
5335
+ (_, col) => Math.max(
5336
+ 3,
5337
+ ...normalised.filter((row) => !isSeparatorRow(row)).map((row) => visibleWidth(row[col]))
5338
+ )
5339
+ );
5340
+ const borderRow = `+${colWidths.map((w) => "-".repeat(w + 2)).join("+")}+`;
5341
+ const bodyRows = normalised.map((row) => {
3960
5342
  if (isSeparatorRow(row)) {
3961
5343
  return borderRow;
3962
5344
  }
3963
5345
  const cells = row.map((cell, col) => {
3964
- const rendered = renderMarkdownLine(cell);
3965
- const pad = colWidths[col] - visualWidth(cell);
5346
+ const rendered = styleInline(cell);
5347
+ const pad = colWidths[col] - visibleWidth(cell);
3966
5348
  return rendered + " ".repeat(Math.max(0, pad));
3967
5349
  });
3968
5350
  return `| ${cells.join(" | ")} |`;
@@ -3981,9 +5363,9 @@ function createStreamingTableAligner() {
3981
5363
  tableLines = [];
3982
5364
  return aligned.join("\n") + "\n";
3983
5365
  }
3984
- function push(text) {
3985
- if (!text) return "";
3986
- const combined = lineBuffer + text;
5366
+ function push(text2) {
5367
+ if (!text2) return "";
5368
+ const combined = lineBuffer + text2;
3987
5369
  const segments = combined.split("\n");
3988
5370
  lineBuffer = segments[segments.length - 1];
3989
5371
  const completeLines = segments.slice(0, -1);
@@ -4011,7 +5393,7 @@ function createStreamingTableAligner() {
4011
5393
  }
4012
5394
 
4013
5395
  // src/output/spinner.ts
4014
- var import_picocolors7 = __toESM(require("picocolors"));
5396
+ var import_picocolors8 = __toESM(require("picocolors"));
4015
5397
  var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
4016
5398
  var FRAME_INTERVAL_MS = 80;
4017
5399
  var DEFAULT_LABEL = "Thinking";
@@ -4023,7 +5405,7 @@ function createSpinner() {
4023
5405
  frameIndex = 0;
4024
5406
  timer = setInterval(() => {
4025
5407
  const frame = FRAMES[frameIndex % FRAMES.length];
4026
- process.stderr.write(`\r${import_picocolors7.default.dim(`${frame} ${label}...`)}`);
5408
+ process.stderr.write(`\r${import_picocolors8.default.dim(`${frame} ${label}...`)}`);
4027
5409
  frameIndex += 1;
4028
5410
  }, FRAME_INTERVAL_MS);
4029
5411
  timer.unref();
@@ -4060,11 +5442,17 @@ function formatMetricsLine(turns, durationMs, durationApiMs, costUsd) {
4060
5442
  var EXIT_COMMANDS = /* @__PURE__ */ new Set(["exit", "quit", "end", "q"]);
4061
5443
  var SUPPORTED_MODELS = ["sonnet", "haiku", "opus"];
4062
5444
  var DEFAULT_AGENT_MODEL = "sonnet";
5445
+ var LIVE_MAX_TURNS = 100;
5446
+ var BUNDLE_MAX_TURNS = 40;
4063
5447
  var ALLOWED_TOOL_NAMES = [
4064
5448
  ...DIAGNOSTIC_TOOL_NAMES.map((name) => `mcp__${MCP_SERVER_NAME}__${name}`),
4065
5449
  SAVE_REPORT_TOOL_NAME,
4066
5450
  `mcp__${MCP_SERVER_NAME}__${ALTER_TABLE_COLUMNS_TOOL_NAME}`
4067
5451
  ];
5452
+ var BUNDLE_ALLOWED_TOOL_NAMES = [
5453
+ ...BUNDLE_TOOL_NAMES.map((name) => `mcp__${MCP_SERVER_NAME}__${name}`),
5454
+ SAVE_REPORT_TOOL_NAME
5455
+ ];
4068
5456
  var DISALLOWED_TOOLS = ["Bash", "Edit", "Write", "MultiEdit"];
4069
5457
  var ERROR_LABELS = {
4070
5458
  authentication_failed: "Authentication failed \u2014 check your API key or re-run with --login",
@@ -4075,8 +5463,8 @@ var ERROR_LABELS = {
4075
5463
  max_output_tokens: "Response exceeded maximum output length",
4076
5464
  unknown: "Unknown API error"
4077
5465
  };
4078
- function isExitCommand(text) {
4079
- return EXIT_COMMANDS.has(text.trim().toLowerCase());
5466
+ function isExitCommand(text2) {
5467
+ return EXIT_COMMANDS.has(text2.trim().toLowerCase());
4080
5468
  }
4081
5469
  function makeUserMessage(content) {
4082
5470
  return {
@@ -4090,7 +5478,9 @@ async function* makeInteractivePrompt(abortController, turnGate, spinner) {
4090
5478
  while (!abortController.signal.aborted) {
4091
5479
  try {
4092
5480
  process.stderr.write("\n");
4093
- const issue = await (0, import_prompts4.input)({ message: "Describe the issue to investigate:" });
5481
+ const issue = await input({
5482
+ message: "Describe the issue to investigate:"
5483
+ });
4094
5484
  process.stderr.write("\n");
4095
5485
  const trimmed = issue.trim();
4096
5486
  if (!trimmed) continue;
@@ -4107,7 +5497,7 @@ async function* makeInteractivePrompt(abortController, turnGate, spinner) {
4107
5497
  await turnGate.wait();
4108
5498
  if (abortController.signal.aborted) break;
4109
5499
  process.stderr.write("\n");
4110
- const response = await (0, import_prompts4.input)({ message: "You:" });
5500
+ const response = await input({ message: "You:" });
4111
5501
  process.stderr.write("\n");
4112
5502
  const trimmed = response.trim();
4113
5503
  if (!trimmed) continue;
@@ -4125,79 +5515,116 @@ async function displayDegradedStatus(session2) {
4125
5515
  hostManagerStatus(session2),
4126
5516
  hostManagerAlerts(session2)
4127
5517
  ]);
4128
- 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"));
5518
+ process.stderr.write(import_picocolors9.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"));
4129
5519
  if (statusResult.ok) {
4130
5520
  const rows = statusResult.data;
4131
5521
  const maxKeyLen = rows.reduce((max, r) => Math.max(max, r.key.length), 0);
4132
5522
  for (const row of rows) {
4133
- process.stderr.write(` ${import_picocolors8.default.dim(row.key.padEnd(maxKeyLen))} ${row.value}
5523
+ process.stderr.write(` ${import_picocolors9.default.dim(row.key.padEnd(maxKeyLen))} ${row.value}
4134
5524
  `);
4135
5525
  }
4136
5526
  } else {
4137
- process.stderr.write(` ${import_picocolors8.default.red(`Error: ${statusResult.error}`)}
5527
+ process.stderr.write(` ${import_picocolors9.default.red(`Error: ${statusResult.error}`)}
4138
5528
  `);
4139
5529
  }
4140
5530
  process.stderr.write("\n");
4141
- 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"));
5531
+ process.stderr.write(import_picocolors9.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"));
4142
5532
  if (alertsResult.ok) {
4143
5533
  const alerts = alertsResult.data;
4144
5534
  if (alerts.length === 0) {
4145
- process.stderr.write(` ${import_picocolors8.default.dim("No recent alerts.")}
5535
+ process.stderr.write(` ${import_picocolors9.default.dim("No recent alerts.")}
4146
5536
  `);
4147
5537
  } else {
4148
5538
  for (const alert of alerts) {
4149
- process.stderr.write(` ${import_picocolors8.default.dim(alert.timestamp)} ${alert.type} ${alert.params}
5539
+ process.stderr.write(` ${import_picocolors9.default.dim(alert.timestamp)} ${alert.type} ${alert.params}
4150
5540
  `);
4151
5541
  }
4152
5542
  }
4153
5543
  } else {
4154
- process.stderr.write(` ${import_picocolors8.default.dim(`Unavailable: ${alertsResult.error}`)}
5544
+ process.stderr.write(` ${import_picocolors9.default.dim(`Unavailable: ${alertsResult.error}`)}
4155
5545
  `);
4156
5546
  }
4157
5547
  process.stderr.write("\n");
4158
5548
  }
4159
5549
  async function runAgent(session2, kineticaVersion, degraded, model, runOptions) {
5550
+ const bundleSource = runOptions?.bundleSource;
4160
5551
  const authMethod = runOptions?.authMethod ?? "api_key";
4161
5552
  const dollarCapped = authMethod === "api_key";
4162
5553
  const resolvedBudgetUsd = runOptions?.maxBudgetUsd ?? DEFAULT_MAX_BUDGET_USD;
4163
- const [catalogSchemas, playbooks, references] = await Promise.all([
4164
- degraded ? Promise.resolve(void 0) : discoverCatalogSchemas(session2),
5554
+ const [catalogSchemas, playbooks, references, bundleReferences] = await Promise.all([
5555
+ degraded || !session2 ? Promise.resolve(void 0) : discoverCatalogSchemas(session2),
4165
5556
  loadPlaybooks(),
4166
- loadReferences()
5557
+ loadReferences(),
5558
+ loadBundleReferences()
4167
5559
  ]);
4168
- const systemPrompt = buildSystemPrompt(
5560
+ const bundleHolder = createBundleHolder(bundleSource);
5561
+ const systemPrompt = session2 ? buildSystemPrompt(
4169
5562
  kineticaVersion,
4170
5563
  catalogSchemas,
4171
5564
  playbooks,
4172
5565
  references,
4173
- degraded
4174
- );
5566
+ degraded,
5567
+ bundleHolder.isLoaded() ? "attached" : "available",
5568
+ bundleReferences
5569
+ ) : buildBundleSystemPrompt(kineticaVersion, playbooks, references, bundleReferences);
4175
5570
  const budget = checkPromptBudget(systemPrompt);
4176
5571
  if (process.env.DEBUG) {
4177
5572
  process.stderr.write(
4178
- import_picocolors8.default.dim(`System prompt: ~${budget.tokens} tokens (${budget.chars} chars)
5573
+ import_picocolors9.default.dim(`System prompt: ~${budget.tokens} tokens (${budget.chars} chars)
4179
5574
  `)
4180
5575
  );
4181
5576
  }
4182
5577
  if (budget.overBudget) {
4183
5578
  process.stderr.write(
4184
- import_picocolors8.default.yellow(
5579
+ import_picocolors9.default.yellow(
4185
5580
  `\u26A0 system prompt is ~${budget.tokens} tokens (threshold ${budget.threshold}) \u2014 knowledge corpus is getting expensive; consider keyword-based playbook selection.
4186
5581
  `
4187
5582
  )
4188
5583
  );
4189
5584
  }
4190
- const diagnosticTools = makeDiagnosticTools(session2, catalogSchemas);
4191
- const mutationTools = makeMutationTools(session2);
4192
5585
  const saveReportTool = makeSaveReportTool();
4193
- const alterTableColumnsTool = makeAlterTableColumnsToolWithDeps(session2);
4194
- const server = (0, import_claude_agent_sdk4.createSdkMcpServer)({
5586
+ if (!session2 && !bundleSource) {
5587
+ throw new Error("runAgent requires a Kinetica session, a bundleSource, or both.");
5588
+ }
5589
+ const spinner = createSpinner();
5590
+ const promptForPath = async () => {
5591
+ spinner.stop();
5592
+ return promptBundleDirectory();
5593
+ };
5594
+ const confirmPath = async (path2) => {
5595
+ spinner.stop();
5596
+ try {
5597
+ const answer = await input({
5598
+ message: `Load support bundle from "${path2}"? The agent will be able to read files under that directory. (y/n):`
5599
+ });
5600
+ return answer.trim().toLowerCase() === "y";
5601
+ } catch {
5602
+ return false;
5603
+ }
5604
+ };
5605
+ const bundleTools = makeBundleTools(bundleHolder, { promptForPath, confirmPath });
5606
+ const liveTools = session2 ? [
5607
+ ...makeDiagnosticTools(session2, catalogSchemas),
5608
+ ...makeMutationTools(session2),
5609
+ makeAlterTableColumnsToolWithDeps(session2)
5610
+ ] : [];
5611
+ const serverTools = [...liveTools, ...bundleTools, saveReportTool];
5612
+ const allowedTools = [
5613
+ .../* @__PURE__ */ new Set([...session2 ? ALLOWED_TOOL_NAMES : [], ...BUNDLE_ALLOWED_TOOL_NAMES])
5614
+ ];
5615
+ let registry = createBundleRegistry();
5616
+ if (session2) {
5617
+ registry = DIAGNOSTIC_TOOL_NAMES.reduce(
5618
+ (reg, name) => reg.registerReadOnlyTool(name),
5619
+ registry
5620
+ );
5621
+ }
5622
+ const maxTurns = session2 ? LIVE_MAX_TURNS : BUNDLE_MAX_TURNS;
5623
+ const server = (0, import_claude_agent_sdk5.createSdkMcpServer)({
4195
5624
  name: MCP_SERVER_NAME,
4196
5625
  version: "1.0.0",
4197
- tools: [...diagnosticTools, ...mutationTools, saveReportTool, alterTableColumnsTool]
5626
+ tools: serverTools
4198
5627
  });
4199
- const spinner = createSpinner();
4200
- const registry = createDiagnosticRegistry();
4201
5628
  const approvalGate = createApprovalGate(registry.isReadOnlyTool);
4202
5629
  const canUseTool = async (toolName, toolInput, options2) => {
4203
5630
  spinner.stop();
@@ -4207,14 +5634,14 @@ async function runAgent(session2, kineticaVersion, degraded, model, runOptions)
4207
5634
  const effectiveModel = model ?? DEFAULT_AGENT_MODEL;
4208
5635
  const options = {
4209
5636
  mcpServers: { [MCP_SERVER_NAME]: server },
4210
- allowedTools: ALLOWED_TOOL_NAMES,
5637
+ allowedTools,
4211
5638
  disallowedTools: [...DISALLOWED_TOOLS],
4212
5639
  canUseTool,
4213
5640
  systemPrompt,
4214
5641
  model: effectiveModel,
4215
5642
  fallbackModel: "haiku",
4216
5643
  thinking: { type: "adaptive" },
4217
- maxTurns: 100,
5644
+ maxTurns,
4218
5645
  // Only impose a dollar cap for per-token billing. For OAuth subscription users
4219
5646
  // the SDK would otherwise cut them off at a notional dollar figure they never pay;
4220
5647
  // omitting it leaves the turn limit (maxTurns) as their guard.
@@ -4224,21 +5651,47 @@ async function runAgent(session2, kineticaVersion, degraded, model, runOptions)
4224
5651
  abortController,
4225
5652
  env: { ...process.env, CLAUDE_AGENT_SDK_CLIENT_APP: "admin-agent" }
4226
5653
  };
4227
- const guardLine = dollarCapped ? import_picocolors8.default.dim(`Budget guard: $${resolvedBudgetUsd.toFixed(2)} (raise with --max-budget)
4228
- `) : import_picocolors8.default.dim("Budget guard: subscription (Pro/Max) \u2014 turn-limited\n");
4229
- if (degraded) {
4230
- process.stderr.write("\nKinetica Diagnostic Session Ready (DEGRADED MODE)\n");
5654
+ const guardLine = dollarCapped ? import_picocolors9.default.dim(`Budget guard: $${resolvedBudgetUsd.toFixed(2)} (raise with --max-budget)
5655
+ `) : import_picocolors9.default.dim("Budget guard: subscription (Pro/Max) \u2014 turn-limited\n");
5656
+ const bundleSummary = () => {
5657
+ const src = bundleHolder.get();
5658
+ if (!src) return "";
5659
+ const { totalFiles, ranks } = src.inventory();
5660
+ const versionStr = kineticaVersion ? ` (version ${kineticaVersion})` : "";
5661
+ return `${totalFiles} files, ranks: ${ranks.join(", ") || "none"}${versionStr}`;
5662
+ };
5663
+ if (session2) {
5664
+ if (degraded) {
5665
+ process.stderr.write("\nKinetica Diagnostic Session Ready (DEGRADED MODE)\n");
5666
+ process.stderr.write(
5667
+ "DB engine (port 9191) is unreachable. Only host manager tools are available.\n\n"
5668
+ );
5669
+ await displayDegradedStatus(session2);
5670
+ } else {
5671
+ process.stderr.write("\nKinetica Diagnostic Session Ready\n");
5672
+ }
5673
+ if (bundleHolder.isLoaded()) {
5674
+ process.stderr.write(
5675
+ `Support bundle attached: ${bundleSummary()}. Live + bundle tools available.
5676
+ `
5677
+ );
5678
+ } else {
5679
+ process.stderr.write(
5680
+ import_picocolors9.default.dim("Tip: ask me to analyze a support bundle to add offline log/config analysis.\n")
5681
+ );
5682
+ }
5683
+ } else {
5684
+ process.stderr.write("\nKinetica Diagnostic Session Ready (OFFLINE BUNDLE MODE)\n");
5685
+ process.stderr.write(`Analyzing extracted support bundle: ${bundleSummary()}.
5686
+ `);
4231
5687
  process.stderr.write(
4232
- "DB engine (port 9191) is unreachable. Only host manager tools are available.\n\n"
5688
+ "Read-only \u2014 diagnoses from captured logs/config; live verification unavailable (no DB connection).\n"
4233
5689
  );
4234
- await displayDegradedStatus(session2);
4235
- } else {
4236
- process.stderr.write("\nKinetica Diagnostic Session Ready\n");
4237
5690
  }
4238
5691
  process.stderr.write(guardLine);
4239
5692
  process.stderr.write("Type 'exit' to end the session.\n\n");
4240
5693
  const turnGate = createTurnGate();
4241
- const agentQuery = (0, import_claude_agent_sdk4.query)({
5694
+ const agentQuery = (0, import_claude_agent_sdk5.query)({
4242
5695
  prompt: makeInteractivePrompt(abortController, turnGate, spinner),
4243
5696
  options
4244
5697
  });
@@ -4265,10 +5718,10 @@ async function runAgent(session2, kineticaVersion, degraded, model, runOptions)
4265
5718
  if (message.type === "stream_event") {
4266
5719
  const { event: evt } = message;
4267
5720
  if (evt.type === "content_block_delta" && evt.delta.type === "text_delta") {
4268
- const text = evt.delta.text ?? "";
4269
- if (text) {
5721
+ const text2 = evt.delta.text ?? "";
5722
+ if (text2) {
4270
5723
  spinner.stop();
4271
- const output = tableAligner.push(text);
5724
+ const output = tableAligner.push(text2);
4272
5725
  if (output) {
4273
5726
  process.stderr.write(output);
4274
5727
  lastStreamCharWasNewline = output.endsWith("\n");
@@ -4291,7 +5744,7 @@ async function runAgent(session2, kineticaVersion, degraded, model, runOptions)
4291
5744
  if (budgetTracker.shouldWarn()) {
4292
5745
  spinner.stop();
4293
5746
  process.stderr.write(
4294
- import_picocolors8.default.yellow(
5747
+ import_picocolors9.default.yellow(
4295
5748
  `
4296
5749
  \u26A0 Approaching budget guard (~$${budgetTracker.spentUsd().toFixed(2)} / $${resolvedBudgetUsd.toFixed(2)}) \u2014 wrapping up soon. Save a partial report now if you want to preserve findings.
4297
5750
  `
@@ -4314,7 +5767,7 @@ async function runAgent(session2, kineticaVersion, degraded, model, runOptions)
4314
5767
  if (assistantMsg.error) {
4315
5768
  spinner.stop();
4316
5769
  const label = ERROR_LABELS[assistantMsg.error] ?? assistantMsg.error;
4317
- process.stderr.write(import_picocolors8.default.yellow(`
5770
+ process.stderr.write(import_picocolors9.default.yellow(`
4318
5771
  API error: ${label}
4319
5772
  `));
4320
5773
  turnGate.open();
@@ -4331,7 +5784,7 @@ API error: ${label}
4331
5784
  cacheCreationTokens = usages.reduce((sum, u) => sum + (u.cacheCreationInputTokens ?? 0), 0);
4332
5785
  if (resultMsg.subtype === "error_max_turns") {
4333
5786
  process.stderr.write(
4334
- import_picocolors8.default.yellow(
5787
+ import_picocolors9.default.yellow(
4335
5788
  `
4336
5789
  Reached the turn limit (${numTurns} turns) \u2014 a safety guard, not an error. Any report the agent saved is in reports/. Start a fresh session to continue.
4337
5790
  `
@@ -4340,7 +5793,7 @@ Reached the turn limit (${numTurns} turns) \u2014 a safety guard, not an error.
4340
5793
  } else if (resultMsg.subtype === "error_max_budget_usd") {
4341
5794
  const spentStr = totalCostUsd > 0 ? ` ($${totalCostUsd.toFixed(2)} spent)` : "";
4342
5795
  process.stderr.write(
4343
- import_picocolors8.default.yellow(
5796
+ import_picocolors9.default.yellow(
4344
5797
  `
4345
5798
  Reached the $${resolvedBudgetUsd.toFixed(2)} budget guard${spentStr} \u2014 a safety limit, not an error. Re-run with --max-budget=<amount> (or set ADMIN_AGENT_MAX_BUDGET) for more headroom. Any report the agent saved is in reports/.
4346
5799
  `
@@ -4399,7 +5852,7 @@ Warning: MCP server "${s.name}" failed to connect (${s.status})
4399
5852
  const statusStr = retryMsg.error_status !== null ? ` (HTTP ${retryMsg.error_status})` : "";
4400
5853
  const delaySec = Math.round(retryMsg.retry_delay_ms / 1e3);
4401
5854
  process.stderr.write(
4402
- import_picocolors8.default.yellow(
5855
+ import_picocolors9.default.yellow(
4403
5856
  `
4404
5857
  API error${statusStr}. Retrying (attempt ${retryMsg.attempt}/${retryMsg.max_retries}) in ${delaySec}s...
4405
5858
  `
@@ -4428,18 +5881,18 @@ Rate limited \u2014 requests rejected.${resetStr}
4428
5881
  } else if (message.type === "control_request") {
4429
5882
  const controlMsg = message;
4430
5883
  if (controlMsg.request.subtype === "claude_authenticate") {
4431
- process.stderr.write(import_picocolors8.default.yellow("\nRe-authentication requested by SDK...\n"));
5884
+ process.stderr.write(import_picocolors9.default.yellow("\nRe-authentication requested by SDK...\n"));
4432
5885
  }
4433
5886
  }
4434
5887
  }
4435
5888
  } catch (error) {
4436
5889
  spinner.stop();
4437
- if (error instanceof import_claude_agent_sdk4.AbortError) {
5890
+ if (error instanceof import_claude_agent_sdk5.AbortError) {
4438
5891
  hadNonAbortError = false;
4439
5892
  } else {
4440
5893
  hadNonAbortError = true;
4441
5894
  const message = error instanceof Error ? error.message : String(error);
4442
- process.stderr.write(import_picocolors8.default.red(`
5895
+ process.stderr.write(import_picocolors9.default.red(`
4443
5896
  Agent error: ${message}
4444
5897
  `));
4445
5898
  }
@@ -4453,7 +5906,7 @@ Agent error: ${message}
4453
5906
  const sessionCost = dollarCapped ? totalCostUsd : void 0;
4454
5907
  if (process.env.DEBUG && (cacheReadTokens > 0 || cacheCreationTokens > 0)) {
4455
5908
  process.stderr.write(
4456
- import_picocolors8.default.dim(
5909
+ import_picocolors9.default.dim(
4457
5910
  `Cache: ${cacheReadTokens} read / ${cacheCreationTokens} created input tokens (read > 0 confirms the system prompt is served from cache)
4458
5911
  `
4459
5912
  )
@@ -4481,7 +5934,7 @@ var MODEL_LABELS = {
4481
5934
  opus: "Opus \u2014 deepest reasoning, slower & pricier"
4482
5935
  };
4483
5936
  async function selectModel() {
4484
- return (0, import_prompts5.select)({
5937
+ return select({
4485
5938
  message: "Select model for this session:",
4486
5939
  default: DEFAULT_AGENT_MODEL,
4487
5940
  choices: SUPPORTED_MODELS.map((value) => ({ value, name: MODEL_LABELS[value] }))
@@ -4489,10 +5942,10 @@ async function selectModel() {
4489
5942
  }
4490
5943
 
4491
5944
  // src/auth/preflight.ts
4492
- var import_claude_agent_sdk5 = require("@anthropic-ai/claude-agent-sdk");
5945
+ var import_claude_agent_sdk6 = require("@anthropic-ai/claude-agent-sdk");
4493
5946
 
4494
5947
  // src/auth/oauth-flow.ts
4495
- var import_picocolors9 = __toESM(require("picocolors"));
5948
+ var import_picocolors10 = __toESM(require("picocolors"));
4496
5949
 
4497
5950
  // src/auth/open-browser.ts
4498
5951
  var import_child_process = require("child_process");
@@ -4518,21 +5971,21 @@ async function resolveAuthentication(agentQuery, options) {
4518
5971
  const { manualUrl, automaticUrl } = await query3.claudeAuthenticate(options.loginWithClaudeAi);
4519
5972
  const opened = openBrowser(automaticUrl);
4520
5973
  if (opened) {
4521
- process.stderr.write(import_picocolors9.default.dim("Browser opened for login. Waiting for authentication...\n"));
5974
+ process.stderr.write(import_picocolors10.default.dim("Browser opened for login. Waiting for authentication...\n"));
4522
5975
  } else {
4523
5976
  process.stderr.write(`
4524
5977
  Open this URL in your browser to log in:
4525
- ${import_picocolors9.default.bold(manualUrl)}
5978
+ ${import_picocolors10.default.bold(manualUrl)}
4526
5979
 
4527
5980
  `);
4528
- process.stderr.write(import_picocolors9.default.dim("Waiting for browser login to complete...\n"));
5981
+ process.stderr.write(import_picocolors10.default.dim("Waiting for browser login to complete...\n"));
4529
5982
  }
4530
5983
  await query3.claudeOAuthWaitForCompletion();
4531
5984
  return { method: "oauth" };
4532
5985
  } catch (err) {
4533
5986
  const message = err instanceof Error ? err.message : String(err);
4534
5987
  process.stderr.write(
4535
- import_picocolors9.default.yellow(`
5988
+ import_picocolors10.default.yellow(`
4536
5989
  Warning: OAuth login failed (${message}). SDK may retry automatically.
4537
5990
  `)
4538
5991
  );
@@ -4577,7 +6030,7 @@ async function authenticateAnthropic(options) {
4577
6030
  await new Promise(() => {
4578
6031
  });
4579
6032
  }
4580
- const authQuery = (0, import_claude_agent_sdk5.query)({
6033
+ const authQuery = (0, import_claude_agent_sdk6.query)({
4581
6034
  prompt: hangingPrompt(),
4582
6035
  options: {
4583
6036
  persistSession: false,
@@ -4609,12 +6062,12 @@ async function authenticateAnthropic(options) {
4609
6062
  // src/auth/logout.ts
4610
6063
  var import_child_process2 = require("child_process");
4611
6064
  var import_node_module = require("module");
4612
- var import_node_path5 = __toESM(require("path"));
6065
+ var import_node_path8 = __toESM(require("path"));
4613
6066
  var import_util = require("util");
4614
6067
  var execFileAsync = (0, import_util.promisify)(import_child_process2.execFile);
4615
6068
  function resolveSdkCliPath() {
4616
6069
  const require_ = (0, import_node_module.createRequire)(__filename);
4617
- return import_node_path5.default.join(import_node_path5.default.dirname(require_.resolve("@anthropic-ai/claude-agent-sdk")), "cli.js");
6070
+ return import_node_path8.default.join(import_node_path8.default.dirname(require_.resolve("@anthropic-ai/claude-agent-sdk")), "cli.js");
4618
6071
  }
4619
6072
  async function logout() {
4620
6073
  try {
@@ -4634,10 +6087,9 @@ async function logout() {
4634
6087
 
4635
6088
  // src/session/env-file.ts
4636
6089
  var import_fs2 = require("fs");
4637
- var import_promises4 = require("fs/promises");
6090
+ var import_promises8 = require("fs/promises");
4638
6091
  var import_path2 = require("path");
4639
- var import_prompts6 = require("@inquirer/prompts");
4640
- var import_picocolors10 = __toESM(require("picocolors"));
6092
+ var import_picocolors11 = __toESM(require("picocolors"));
4641
6093
  function parseEnvContent(content) {
4642
6094
  const entries = [];
4643
6095
  for (const line of content.split("\n")) {
@@ -4719,7 +6171,7 @@ function loadEnvFile(dir, env = process.env) {
4719
6171
  async function offerSaveCredentials(url, user, dir) {
4720
6172
  if (!process.stdin.isTTY) return;
4721
6173
  try {
4722
- const shouldSave = await (0, import_prompts6.confirm)({
6174
+ const shouldSave = await confirm({
4723
6175
  message: "Save KINETICA_URL and KINETICA_USER to .env? (password is never saved)",
4724
6176
  default: true
4725
6177
  });
@@ -4727,52 +6179,50 @@ async function offerSaveCredentials(url, user, dir) {
4727
6179
  const filePath = (0, import_path2.join)(dir ?? process.cwd(), ".env");
4728
6180
  let existing;
4729
6181
  try {
4730
- existing = await (0, import_promises4.readFile)(filePath, "utf8");
6182
+ existing = await (0, import_promises8.readFile)(filePath, "utf8");
4731
6183
  } catch {
4732
6184
  }
4733
6185
  const content = buildEnvContent(url, user, existing);
4734
- await (0, import_promises4.writeFile)(filePath, content, "utf8");
4735
- console.error(import_picocolors10.default.dim("Saved to .env"));
6186
+ await (0, import_promises8.writeFile)(filePath, content, "utf8");
6187
+ console.error(import_picocolors11.default.dim("Saved to .env"));
4736
6188
  } catch (err) {
4737
6189
  const message = err instanceof Error ? err.message : String(err);
4738
- console.error(import_picocolors10.default.yellow(`Could not save .env file: ${message}`));
6190
+ console.error(import_picocolors11.default.yellow(`Could not save .env file: ${message}`));
4739
6191
  }
4740
6192
  }
4741
6193
 
4742
6194
  // src/session/verify.ts
4743
- var import_picocolors13 = __toESM(require("picocolors"));
4744
- var import_prompts9 = require("@inquirer/prompts");
6195
+ var import_picocolors14 = __toESM(require("picocolors"));
4745
6196
 
4746
6197
  // src/session/collect.ts
4747
- var import_prompts7 = require("@inquirer/prompts");
4748
- var import_picocolors11 = __toESM(require("picocolors"));
6198
+ var import_picocolors12 = __toESM(require("picocolors"));
4749
6199
  async function collectCredentials() {
4750
6200
  const prompted = /* @__PURE__ */ new Set();
4751
6201
  const envUrl = process.env.KINETICA_URL;
4752
6202
  const envUser = process.env.KINETICA_USER;
4753
6203
  if (envUrl && envUser && process.stdin.isTTY) {
4754
- console.error(import_picocolors11.default.dim(`Saved connection: ${envUrl} (${envUser})`));
4755
- const useSaved = await (0, import_prompts7.confirm)({
6204
+ console.error(import_picocolors12.default.dim(`Saved connection: ${envUrl} (${envUser})`));
6205
+ const useSaved = await confirm({
4756
6206
  message: "Use saved connection?",
4757
6207
  default: true
4758
6208
  });
4759
6209
  if (!useSaved) {
4760
6210
  prompted.add("url");
4761
6211
  prompted.add("user");
4762
- const url2 = await (0, import_prompts7.input)({ message: "Kinetica endpoint URL:" });
4763
- const user2 = await (0, import_prompts7.input)({ message: "Admin username:" });
4764
- const pass2 = await (0, import_prompts7.password)({ message: "Admin password:", mask: "*" });
6212
+ const url2 = await input({ message: "Kinetica endpoint URL:" });
6213
+ const user2 = await input({ message: "Admin username:" });
6214
+ const pass2 = await password({ message: "Admin password:", mask: "*" });
4765
6215
  return { credentials: { url: url2, user: user2, pass: pass2 }, prompted };
4766
6216
  }
4767
6217
  }
4768
- const url = envUrl ?? (prompted.add("url"), await (0, import_prompts7.input)({ message: "Kinetica endpoint URL:" }));
4769
- const user = envUser ?? (prompted.add("user"), await (0, import_prompts7.input)({ message: "Admin username:" }));
4770
- const pass = process.env.KINETICA_PASS ?? await (0, import_prompts7.password)({ message: "Admin password:", mask: "*" });
6218
+ const url = envUrl ?? (prompted.add("url"), await input({ message: "Kinetica endpoint URL:" }));
6219
+ const user = envUser ?? (prompted.add("user"), await input({ message: "Admin username:" }));
6220
+ const pass = process.env.KINETICA_PASS ?? await password({ message: "Admin password:", mask: "*" });
4771
6221
  return { credentials: { url, user, pass }, prompted };
4772
6222
  }
4773
6223
  async function repromptCredentials() {
4774
- const user = await (0, import_prompts7.input)({ message: "Admin username:" });
4775
- const pass = await (0, import_prompts7.password)({ message: "Admin password:", mask: "*" });
6224
+ const user = await input({ message: "Admin username:" });
6225
+ const pass = await password({ message: "Admin password:", mask: "*" });
4776
6226
  return { user, pass };
4777
6227
  }
4778
6228
 
@@ -4783,8 +6233,9 @@ function replacePort(baseUrl, port) {
4783
6233
  parsed.port = String(port);
4784
6234
  return parsed.origin;
4785
6235
  }
4786
- function createSession(url, user, pass) {
6236
+ function createSession(url, user, pass, options) {
4787
6237
  const authHeader = "Basic " + Buffer.from(`${user}:${pass}`).toString("base64");
6238
+ const timeoutMs = options?.timeoutMs ?? REQUEST_TIMEOUT_MS;
4788
6239
  const doFetch = async (fullUrl, body) => {
4789
6240
  if (process.env.DEBUG) {
4790
6241
  console.error(`[DEBUG] POST ${fullUrl}`);
@@ -4796,7 +6247,7 @@ function createSession(url, user, pass) {
4796
6247
  "Content-Type": "application/json"
4797
6248
  },
4798
6249
  body: body !== void 0 ? JSON.stringify(body) : void 0,
4799
- signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
6250
+ signal: AbortSignal.timeout(timeoutMs)
4800
6251
  });
4801
6252
  };
4802
6253
  return {
@@ -4807,13 +6258,12 @@ function createSession(url, user, pass) {
4807
6258
  }
4808
6259
 
4809
6260
  // src/session/resolve-url.ts
4810
- var import_picocolors12 = __toESM(require("picocolors"));
4811
- var import_prompts8 = require("@inquirer/prompts");
6261
+ var import_picocolors13 = __toESM(require("picocolors"));
4812
6262
  var PROBE_TIMEOUT_MS2 = 3e3;
4813
6263
  var HTTP_PROTOCOL_RE = /^https?:\/\//i;
4814
6264
  var ANY_PROTOCOL_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
4815
- function hasProtocol(input5) {
4816
- return HTTP_PROTOCOL_RE.test(input5);
6265
+ function hasProtocol(input2) {
6266
+ return HTTP_PROTOCOL_RE.test(input2);
4817
6267
  }
4818
6268
  function stripTrailingSlashes(url) {
4819
6269
  return url.replace(/\/+$/, "");
@@ -4837,20 +6287,20 @@ function isInteractive() {
4837
6287
  }
4838
6288
  async function confirmHttpFallback(host) {
4839
6289
  process.stderr.write(
4840
- "\n" + import_picocolors12.default.red(
4841
- import_picocolors12.default.bold(
6290
+ "\n" + import_picocolors13.default.red(
6291
+ import_picocolors13.default.bold(
4842
6292
  ` WARNING: HTTPS unavailable at ${host}.
4843
6293
  Falling back to plaintext HTTP will transmit your Kinetica credentials in the clear.
4844
6294
  `
4845
6295
  )
4846
- ) + import_picocolors12.default.dim(
6296
+ ) + import_picocolors13.default.dim(
4847
6297
  ` Set KINETICA_HTTPS_ONLY=1 to refuse this fallback automatically, or pass an explicit http:// prefix to silence this prompt.
4848
6298
 
4849
6299
  `
4850
6300
  )
4851
6301
  );
4852
6302
  try {
4853
- return await (0, import_prompts8.confirm)({
6303
+ return await confirm({
4854
6304
  message: "Continue over plaintext HTTP?",
4855
6305
  default: false
4856
6306
  });
@@ -4858,8 +6308,8 @@ async function confirmHttpFallback(host) {
4858
6308
  return false;
4859
6309
  }
4860
6310
  }
4861
- async function resolveUrl(input5) {
4862
- const trimmed = input5.trim();
6311
+ async function resolveUrl(input2, options = {}) {
6312
+ const trimmed = input2.trim();
4863
6313
  if (trimmed === "") {
4864
6314
  return { ok: false, error: "URL is empty" };
4865
6315
  }
@@ -4876,7 +6326,7 @@ async function resolveUrl(input5) {
4876
6326
  }
4877
6327
  const httpsUrl = `https://${normalized}`;
4878
6328
  const httpUrl = `http://${normalized}`;
4879
- console.error(import_picocolors12.default.dim("Detecting protocol..."));
6329
+ console.error(import_picocolors13.default.dim("Detecting protocol..."));
4880
6330
  if (await probeProtocol(httpsUrl)) {
4881
6331
  return { ok: true, url: httpsUrl };
4882
6332
  }
@@ -4892,7 +6342,7 @@ async function resolveUrl(input5) {
4892
6342
  error: `Could not connect to ${normalized} via https:// or http://`
4893
6343
  };
4894
6344
  }
4895
- if (!isInteractive()) {
6345
+ if (options.nonInteractive || !isInteractive()) {
4896
6346
  return {
4897
6347
  ok: false,
4898
6348
  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.`
@@ -4912,6 +6362,7 @@ async function resolveUrl(input5) {
4912
6362
  var MAX_RETRIES = 3;
4913
6363
  var MAX_REPROMPTS = 2;
4914
6364
  var DEFAULT_HM_PORT2 = 9300;
6365
+ var BEST_EFFORT_PROBE_TIMEOUT_MS = 5e3;
4915
6366
  function extractVersion(responseBody) {
4916
6367
  try {
4917
6368
  const outer = JSON.parse(responseBody);
@@ -4958,11 +6409,29 @@ async function verifyConnectivity(session2) {
4958
6409
  function isCredentialError(errorMessage) {
4959
6410
  return errorMessage.startsWith("HTTP 401") || errorMessage.startsWith("HTTP 403");
4960
6411
  }
6412
+ async function connectBestEffort() {
6413
+ const url = process.env.KINETICA_URL;
6414
+ const user = process.env.KINETICA_USER;
6415
+ const pass = process.env.KINETICA_PASS;
6416
+ if (!url || !user || !pass) return void 0;
6417
+ try {
6418
+ const resolved = await resolveUrl(url, { nonInteractive: true });
6419
+ if (!resolved.ok) return void 0;
6420
+ const probe = createSession(resolved.url, user, pass, {
6421
+ timeoutMs: BEST_EFFORT_PROBE_TIMEOUT_MS
6422
+ });
6423
+ const kineticaVersion = await verifyConnectivity(probe);
6424
+ const session2 = createSession(resolved.url, user, pass);
6425
+ return { session: session2, kineticaVersion, degraded: false };
6426
+ } catch {
6427
+ return void 0;
6428
+ }
6429
+ }
4961
6430
  async function connectWithRetry() {
4962
6431
  const { credentials, prompted } = await collectCredentials();
4963
6432
  const resolved = await resolveUrl(credentials.url);
4964
6433
  if (!resolved.ok) {
4965
- console.error(import_picocolors13.default.red(resolved.error));
6434
+ console.error(import_picocolors14.default.red(resolved.error));
4966
6435
  process.exit(1);
4967
6436
  }
4968
6437
  const resolvedUrl = resolved.url;
@@ -4974,17 +6443,17 @@ async function connectWithRetry() {
4974
6443
  for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
4975
6444
  try {
4976
6445
  const kineticaVersion = await verifyConnectivity(session2);
4977
- console.error(import_picocolors13.default.green("Connected to Kinetica successfully."));
6446
+ console.error(import_picocolors14.default.green("Connected to Kinetica successfully."));
4978
6447
  if (prompted.size > 0 || wasReprompted) {
4979
6448
  await offerSaveCredentials(resolvedUrl, currentUser);
4980
6449
  }
4981
6450
  return { session: session2, kineticaVersion, degraded: false };
4982
6451
  } catch (err) {
4983
6452
  const msg = err instanceof Error ? err.message : String(err);
4984
- console.error(import_picocolors13.default.red(`Connection failed (attempt ${attempt}/${MAX_RETRIES}): ${msg}`));
6453
+ console.error(import_picocolors14.default.red(`Connection failed (attempt ${attempt}/${MAX_RETRIES}): ${msg}`));
4985
6454
  if (isCredentialError(msg)) {
4986
6455
  if (process.stdin.isTTY && repromptCount < MAX_REPROMPTS) {
4987
- const shouldRetry = await (0, import_prompts9.confirm)({
6456
+ const shouldRetry = await confirm({
4988
6457
  message: "Credentials may be incorrect. Re-enter?",
4989
6458
  default: true
4990
6459
  });
@@ -4999,15 +6468,15 @@ async function connectWithRetry() {
4999
6468
  continue;
5000
6469
  }
5001
6470
  }
5002
- console.error(import_picocolors13.default.red("Authentication failed. Exiting."));
6471
+ console.error(import_picocolors14.default.red("Authentication failed. Exiting."));
5003
6472
  process.exit(1);
5004
6473
  }
5005
6474
  if (attempt === MAX_RETRIES) {
5006
- console.error(import_picocolors13.default.yellow("DB engine unreachable. Probing host manager on port 9300..."));
6475
+ console.error(import_picocolors14.default.yellow("DB engine unreachable. Probing host manager on port 9300..."));
5007
6476
  const hmResult = await probeHostManager(session2);
5008
6477
  if (hmResult.ok) {
5009
6478
  console.error(
5010
- import_picocolors13.default.yellow(
6479
+ import_picocolors14.default.yellow(
5011
6480
  "Connected in DEGRADED MODE (host manager only). Most diagnostic tools will be unavailable."
5012
6481
  )
5013
6482
  );
@@ -5016,7 +6485,7 @@ async function connectWithRetry() {
5016
6485
  }
5017
6486
  return { session: session2, kineticaVersion: hmResult.version, degraded: true };
5018
6487
  }
5019
- console.error(import_picocolors13.default.red("Host manager also unreachable. Exiting."));
6488
+ console.error(import_picocolors14.default.red("Host manager also unreachable. Exiting."));
5020
6489
  process.exit(1);
5021
6490
  }
5022
6491
  }
@@ -5027,6 +6496,14 @@ async function connectWithRetry() {
5027
6496
  // src/cli/index.ts
5028
6497
  var verbose = false;
5029
6498
  var session;
6499
+ function flagValue(args, name) {
6500
+ const prefix = `${name}=`;
6501
+ const arg = args.find((a) => a.startsWith(prefix));
6502
+ return arg === void 0 ? void 0 : arg.slice(prefix.length);
6503
+ }
6504
+ function chooseBundleSessionVersion(bundleVersion, liveVersion) {
6505
+ return bundleVersion ?? liveVersion;
6506
+ }
5030
6507
  function printHelp() {
5031
6508
  const lines = [
5032
6509
  "",
@@ -5047,6 +6524,7 @@ function printHelp() {
5047
6524
  " --logout Log out from Anthropic account and exit",
5048
6525
  ` --model=NAME Override agent model (${SUPPORTED_MODELS.join(" | ")}); default: sonnet`,
5049
6526
  " --max-budget=USD Per-session budget cap in USD (API-key billing only); default: 5.00",
6527
+ " --bundle=PATH Offline mode: diagnose from an extracted support bundle directory",
5050
6528
  "",
5051
6529
  " Environment variables:",
5052
6530
  " ANTHROPIC_API_KEY Anthropic API key (if not set, OAuth login via browser is used)",
@@ -5078,12 +6556,9 @@ async function main() {
5078
6556
  return;
5079
6557
  }
5080
6558
  const forceLogin = args.includes("--login");
5081
- const loginMethodArg = args.find((a) => a.startsWith("--login-method="));
5082
- const loginMethod = loginMethodArg?.split("=")[1];
5083
- const loginOrgArg = args.find((a) => a.startsWith("--login-org="));
5084
- const loginOrgUUID = loginOrgArg?.split("=")[1];
5085
- const modelArg = args.find((a) => a.startsWith("--model="));
5086
- const modelValue = modelArg?.split("=")[1];
6559
+ const loginMethod = flagValue(args, "--login-method");
6560
+ const loginOrgUUID = flagValue(args, "--login-org");
6561
+ const modelValue = flagValue(args, "--model");
5087
6562
  let model;
5088
6563
  if (modelValue !== void 0) {
5089
6564
  if (SUPPORTED_MODELS.includes(modelValue)) {
@@ -5091,21 +6566,20 @@ async function main() {
5091
6566
  } else {
5092
6567
  const valid = SUPPORTED_MODELS.join(", ");
5093
6568
  process.stderr.write(
5094
- import_picocolors14.default.red(`Error: unknown --model value "${modelValue}". Valid models: ${valid}
6569
+ import_picocolors15.default.red(`Error: unknown --model value "${modelValue}". Valid models: ${valid}
5095
6570
  `)
5096
6571
  );
5097
6572
  process.exitCode = 1;
5098
6573
  return;
5099
6574
  }
5100
6575
  }
5101
- const budgetArg = args.find((a) => a.startsWith("--max-budget="));
5102
- const budgetValue = budgetArg?.split("=")[1];
6576
+ const budgetValue = flagValue(args, "--max-budget");
5103
6577
  let maxBudgetFlag;
5104
6578
  if (budgetValue !== void 0) {
5105
6579
  const parsed = Number(budgetValue);
5106
6580
  if (!isValidBudget(parsed)) {
5107
6581
  process.stderr.write(
5108
- import_picocolors14.default.red(
6582
+ import_picocolors15.default.red(
5109
6583
  `Error: invalid --max-budget value "${budgetValue}". Use a positive number, e.g. --max-budget=10
5110
6584
  `
5111
6585
  )
@@ -5115,23 +6589,74 @@ async function main() {
5115
6589
  }
5116
6590
  maxBudgetFlag = parsed;
5117
6591
  }
6592
+ const bundlePath = flagValue(args, "--bundle");
6593
+ if (bundlePath?.trim() === "") {
6594
+ process.stderr.write(
6595
+ import_picocolors15.default.red(
6596
+ "Error: --bundle requires a directory path, e.g. --bundle=/path/to/extracted-bundle\n"
6597
+ )
6598
+ );
6599
+ process.exitCode = 1;
6600
+ return;
6601
+ }
5118
6602
  loadEnvFile();
5119
6603
  printBanner();
5120
6604
  if (model === void 0 && process.stdin.isTTY) {
5121
6605
  model = await selectModel();
5122
6606
  }
5123
6607
  const effectiveModel = model ?? DEFAULT_AGENT_MODEL;
5124
- process.stderr.write(import_picocolors14.default.dim(`Model: ${effectiveModel}
6608
+ process.stderr.write(import_picocolors15.default.dim(`Model: ${effectiveModel}
5125
6609
  `));
5126
6610
  const authResult = await authenticateAnthropic({ forceLogin, loginMethod, loginOrgUUID });
5127
6611
  if (authResult.method === "oauth") {
5128
6612
  const acctInfo = authResult.email ? ` (${authResult.email})` : "";
5129
- process.stderr.write(import_picocolors14.default.dim(`Authenticated via OAuth${acctInfo}
6613
+ process.stderr.write(import_picocolors15.default.dim(`Authenticated via OAuth${acctInfo}
5130
6614
  `));
5131
6615
  } else {
5132
- process.stderr.write(import_picocolors14.default.dim("Authenticated via API key\n"));
6616
+ process.stderr.write(import_picocolors15.default.dim("Authenticated via API key\n"));
5133
6617
  }
5134
6618
  const maxBudgetUsd = resolveMaxBudgetUsd(maxBudgetFlag);
6619
+ if (bundlePath !== void 0) {
6620
+ const result = await verifyBundle(bundlePath);
6621
+ if (!result.ok) {
6622
+ process.stderr.write(import_picocolors15.default.red(`Error: ${result.error}
6623
+ `));
6624
+ process.exitCode = 1;
6625
+ return;
6626
+ }
6627
+ if (result.missingExpected.length > 0) {
6628
+ process.stderr.write(
6629
+ import_picocolors15.default.yellow(
6630
+ `Warning: bundle is missing expected artifact(s): ${result.missingExpected.join(", ")}. Diagnosing with what is present.
6631
+ `
6632
+ )
6633
+ );
6634
+ }
6635
+ const live = await connectBestEffort();
6636
+ process.stderr.write(
6637
+ live ? import_picocolors15.default.dim("Live connection available \u2014 bundle + live verification enabled.\n") : import_picocolors15.default.dim("No reachable live connection \u2014 offline bundle analysis only.\n")
6638
+ );
6639
+ if (live?.kineticaVersion && result.kineticaVersion && live.kineticaVersion !== result.kineticaVersion) {
6640
+ process.stderr.write(
6641
+ import_picocolors15.default.yellow(
6642
+ `Warning: live cluster version (${live.kineticaVersion}) differs from the bundle's captured version (${result.kineticaVersion}). Reasoning over the bundle uses the captured version; the live cluster may have been upgraded since capture.
6643
+ `
6644
+ )
6645
+ );
6646
+ }
6647
+ await runAgent(
6648
+ live?.session,
6649
+ chooseBundleSessionVersion(result.kineticaVersion, live?.kineticaVersion),
6650
+ false,
6651
+ model,
6652
+ {
6653
+ authMethod: authResult.method,
6654
+ maxBudgetUsd,
6655
+ bundleSource: result.bundleSource
6656
+ }
6657
+ );
6658
+ return;
6659
+ }
5135
6660
  const { session: connectedSession, kineticaVersion, degraded } = await connectWithRetry();
5136
6661
  session = connectedSession;
5137
6662
  await runAgent(session, kineticaVersion, degraded, model, {
@@ -5145,7 +6670,7 @@ function getSession() {
5145
6670
  if (process.env.NODE_ENV !== "test") {
5146
6671
  main().catch((err) => {
5147
6672
  const message = err instanceof Error ? err.message : String(err);
5148
- process.stderr.write(import_picocolors14.default.red(`Error: ${message}
6673
+ process.stderr.write(import_picocolors15.default.red(`Error: ${message}
5149
6674
  `));
5150
6675
  if (verbose && err instanceof Error && err.stack) {
5151
6676
  process.stderr.write(err.stack + "\n");
@@ -5155,6 +6680,7 @@ if (process.env.NODE_ENV !== "test") {
5155
6680
  }
5156
6681
  // Annotate the CommonJS export names for ESM import in node:
5157
6682
  0 && (module.exports = {
6683
+ chooseBundleSessionVersion,
5158
6684
  getSession,
5159
6685
  main,
5160
6686
  verbose