@closeup1202/klag 0.2.0 → 0.3.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.
Files changed (3) hide show
  1. package/README.md +14 -13
  2. package/dist/cli/index.js +184 -56
  3. package/package.json +4 -2
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > Know **why** your Kafka consumer lag is growing — in 5 seconds from the terminal
4
4
 
5
- [![npm version](https://badge.fury.io/js/klag.svg)](https://www.npmjs.com/package/klag)
5
+ [![npm version](https://badge.fury.io/js/%40closeup1202%2Fklag.svg)](https://www.npmjs.com/package/@closeup1202/klag)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
 
8
8
  ## Compared to existing tools
@@ -21,21 +21,21 @@ npx @closeup1202/klag --broker localhost:9092 --group my-service
21
21
 
22
22
  ## Output example
23
23
  ```
24
- ⚡ klag v0.1.0
24
+ ⚡ klag 0.3.0
25
25
 
26
26
  🔍 Consumer Group: my-service
27
27
  Broker: localhost:9092
28
- Collected At: 2026-03-26 17:27:27 (Asia/Seoul)
28
+ Collected At: 2026-03-28 17:27:27 (Asia/Seoul)
29
29
 
30
- Group Status : ⚠️ WARNING Total Lag : 1,234
30
+ Group Status : 🚨 CRITICAL Total Lag : 1,234 Drain : ∞
31
31
 
32
- ┌────────┬───────────┬──────────────────┬────────────────┬───────┬─────────┬──────────────┬──────────────┐
33
- │ Topic │ Partition │ Committed Offset │ Log-End Offset │ Lag │ Status │ Produce Rate │ Consume Rate │
34
- ├────────┼───────────┼──────────────────┼────────────────┼───────┼─────────┼──────────────┼──────────────┤
35
- │ orders │ 0 │ 8,796 │ 10,000 │ 1,204 │ 🔴 HIGH │ 40.0 msg/s │ 0.0 msg/s │
36
- │ orders │ 1 │ 9,988 │ 10,000 │ 12 │ 🟢 OK │ 0.0 msg/s │ 0.0 msg/s │
37
- │ orders │ 2 │ 9,982 │ 10,000 │ 18 │ 🟢 OK │ 0.0 msg/s │ 0.0 msg/s │
38
- └────────┴───────────┴──────────────────┴────────────────┴───────┴─────────┴──────────────┴──────────────┘
32
+ ┌────────┬───────────┬──────────────────┬────────────────┬───────┬─────────┬──────┬──────────────┬──────────────┐
33
+ │ Topic │ Partition │ Committed Offset │ Log-End Offset │ Lag │ Status │ Drain│ Produce Rate │ Consume Rate │
34
+ ├────────┼───────────┼──────────────────┼────────────────┼───────┼─────────┼──────┼──────────────┼──────────────┤
35
+ │ orders │ 0 │ 8,796 │ 10,000 │ 1,204 │ 🔴 HIGH │ ∞ │ 40.0 msg/s │ 0.0 msg/s │
36
+ │ orders │ 1 │ 9,988 │ 10,000 │ 12 │ 🟢 OK │ — │ 0.0 msg/s │ 0.0 msg/s │
37
+ │ orders │ 2 │ 9,982 │ 10,000 │ 18 │ 🟢 OK │ — │ 0.0 msg/s │ 0.0 msg/s │
38
+ └────────┴───────────┴──────────────────┴────────────────┴───────┴─────────┴──────┴──────────────┴──────────────┘
39
39
 
40
40
  🔎 Root Cause Analysis
41
41
  [PRODUCER_BURST] orders
@@ -156,8 +156,9 @@ All consumption pauses during rebalancing, which can cause a temporary lag spike
156
156
 
157
157
  - [x] v0.1.0 — lag collection, hot partition, producer burst, slow consumer, rebalancing detection, watch mode with lag trend (▲▼)
158
158
  - [x] v0.2.0 — SSL/SASL authentication, `.klagrc` config file support
159
- - [ ] v0.3.0 — multi-group monitoring
160
- - [ ] v0.4.0 — Slack alerts, Prometheus export
159
+ - [x] v0.3.0 — time-to-drain severity classification, Drain column per partition
160
+ - [ ] v0.4.0 — multi-group monitoring
161
+ - [ ] v0.5.0 — Slack alerts, Prometheus export
161
162
 
162
163
  ## License
163
164
 
package/dist/cli/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli/index.ts
4
- import chalk5 from "chalk";
4
+ import chalk6 from "chalk";
5
5
  import { Command } from "commander";
6
6
 
7
7
  // src/analyzer/burstDetector.ts
@@ -363,12 +363,30 @@ import chalk from "chalk";
363
363
  import Table from "cli-table3";
364
364
 
365
365
  // src/types/index.ts
366
- var VERSION = "0.2.0";
367
- function classifyLag(lag) {
368
- if (lag < 100n) return "OK";
369
- if (lag < 1000n) return "WARN";
366
+ var VERSION = "0.3.1";
367
+ function classifyLag(lag, consumeRate) {
368
+ if (lag === 0n) return "OK";
369
+ if (consumeRate !== void 0) {
370
+ if (consumeRate === 0) return "HIGH";
371
+ const drainSec = Number(lag) / consumeRate;
372
+ if (drainSec < 60) return "OK";
373
+ if (drainSec < 300) return "WARN";
374
+ return "HIGH";
375
+ }
376
+ if (lag < 10000n) return "OK";
377
+ if (lag < 100000n) return "WARN";
370
378
  return "HIGH";
371
379
  }
380
+ function formatDrainTime(lag, consumeRate) {
381
+ if (lag === 0n) return "\u2014";
382
+ if (consumeRate === 0) return "\u221E";
383
+ const sec = Math.ceil(Number(lag) / consumeRate);
384
+ if (sec < 60) return `${sec}s`;
385
+ const m = Math.floor(sec / 60);
386
+ const s = sec % 60;
387
+ if (sec < 3600) return s > 0 ? `${m}m${s}s` : `${m}m`;
388
+ return `>${Math.floor(sec / 3600)}h`;
389
+ }
372
390
 
373
391
  // src/reporter/tableReporter.ts
374
392
  var LEVEL_ICON = {
@@ -388,8 +406,8 @@ function formatTrend(lagDiff) {
388
406
  if (lagDiff > 0n) return chalk.red(`\u25B2 +${lagDiff.toLocaleString()}`);
389
407
  return chalk.green(`\u25BC ${lagDiff.toLocaleString()}`);
390
408
  }
391
- function groupStatus(totalLag) {
392
- const level = classifyLag(totalLag);
409
+ function groupStatus(totalLag, totalConsumeRate) {
410
+ const level = classifyLag(totalLag, totalConsumeRate);
393
411
  if (level === "OK") return chalk.green("\u2705 OK");
394
412
  if (level === "WARN") return chalk.yellow("\u26A0\uFE0F WARNING");
395
413
  return chalk.red("\u{1F6A8} CRITICAL");
@@ -418,18 +436,25 @@ function printLagTable(snapshot, rcaResults = [], rateSnapshot, watchMode = fals
418
436
  chalk.bold(" Collected At: ") + chalk.gray(`${localTime} (${tz})`)
419
437
  );
420
438
  console.log("");
421
- const status = groupStatus(totalLag);
422
- const totalStr = chalk.bold(formatLag(totalLag));
423
- console.log(` Group Status : ${status} Total Lag : ${totalStr}`);
424
- console.log("");
425
439
  const hasRate = !!rateSnapshot && rateSnapshot.partitions.length > 0;
426
440
  const hasTrend = watchMode;
427
441
  const rateMap = /* @__PURE__ */ new Map();
442
+ let totalConsumeRate;
428
443
  if (hasRate && rateSnapshot) {
444
+ let sum = 0;
429
445
  for (const r of rateSnapshot.partitions) {
430
446
  rateMap.set(`${r.topic}-${r.partition}`, r);
447
+ sum += r.consumeRate;
431
448
  }
449
+ totalConsumeRate = sum;
432
450
  }
451
+ const status = groupStatus(totalLag, totalConsumeRate);
452
+ const totalStr = chalk.bold(formatLag(totalLag));
453
+ const drainStr = totalConsumeRate !== void 0 ? ` Drain : ${chalk.cyan(formatDrainTime(totalLag, totalConsumeRate))}` : "";
454
+ console.log(
455
+ ` Group Status : ${status} Total Lag : ${totalStr}${drainStr}`
456
+ );
457
+ console.log("");
433
458
  const head = [
434
459
  chalk.bold("Topic"),
435
460
  chalk.bold("Partition"),
@@ -438,7 +463,11 @@ function printLagTable(snapshot, rcaResults = [], rateSnapshot, watchMode = fals
438
463
  chalk.bold("Lag"),
439
464
  ...hasTrend ? [chalk.bold("Trend")] : [],
440
465
  chalk.bold("Status"),
441
- ...hasRate ? [chalk.bold("Produce Rate"), chalk.bold("Consume Rate")] : []
466
+ ...hasRate ? [
467
+ chalk.bold("Drain"),
468
+ chalk.bold("Produce Rate"),
469
+ chalk.bold("Consume Rate")
470
+ ] : []
442
471
  ];
443
472
  const table = new Table({
444
473
  head,
@@ -450,16 +479,19 @@ function printLagTable(snapshot, rcaResults = [], rateSnapshot, watchMode = fals
450
479
  "right",
451
480
  ...hasTrend ? ["right"] : [],
452
481
  "center",
453
- ...hasRate ? ["right", "right"] : []
482
+ ...hasRate ? ["right", "right", "right"] : []
454
483
  ],
455
484
  style: { head: [], border: ["grey"] }
456
485
  });
457
486
  let lastTopic = "";
458
487
  for (const p of partitions) {
459
- const level = classifyLag(p.lag);
460
- const lagStr = level === "HIGH" ? chalk.red(formatLag(p.lag)) : level === "WARN" ? chalk.yellow(formatLag(p.lag)) : chalk.green(formatLag(p.lag));
461
488
  const rateEntry = rateMap.get(`${p.topic}-${p.partition}`);
489
+ const level = classifyLag(p.lag, rateEntry?.consumeRate);
490
+ const lagStr = level === "HIGH" ? chalk.red(formatLag(p.lag)) : level === "WARN" ? chalk.yellow(formatLag(p.lag)) : chalk.green(formatLag(p.lag));
462
491
  const rateColumns = hasRate ? [
492
+ chalk.cyan(
493
+ rateEntry !== void 0 ? formatDrainTime(p.lag, rateEntry.consumeRate) : "\u2014"
494
+ ),
463
495
  chalk.yellow(formatRate(rateEntry?.produceRate ?? 0)),
464
496
  chalk.cyan(formatRate(rateEntry?.consumeRate ?? 0))
465
497
  ] : [];
@@ -614,6 +646,104 @@ function parseConfig(filePath) {
614
646
  return obj;
615
647
  }
616
648
 
649
+ // src/cli/groupPicker.ts
650
+ import chalk4 from "chalk";
651
+ import prompts from "prompts";
652
+ var GROUP_STATE_ORDER = {
653
+ Stable: 0,
654
+ Empty: 1,
655
+ PreparingRebalance: 2,
656
+ CompletingRebalance: 3,
657
+ Dead: 4
658
+ };
659
+ async function pickGroup(options) {
660
+ const kafka = createKafkaClient("klag-picker", { ...options, groupId: "" });
661
+ const admin = kafka.admin();
662
+ process.stdout.write(chalk4.gray(" Fetching consumer groups..."));
663
+ let groups = [];
664
+ try {
665
+ await admin.connect();
666
+ const result = await admin.listGroups();
667
+ groups = result.groups;
668
+ } finally {
669
+ await admin.disconnect();
670
+ }
671
+ process.stdout.write(`\r${" ".repeat(40)}\r`);
672
+ if (groups.length === 0) {
673
+ console.error(chalk4.red("\n\u274C No consumer groups found on this broker\n"));
674
+ process.exit(1);
675
+ }
676
+ const kafka2 = createKafkaClient("klag-picker-desc", {
677
+ ...options,
678
+ groupId: ""
679
+ });
680
+ const admin2 = kafka2.admin();
681
+ process.stdout.write(chalk4.gray(" Loading group states... "));
682
+ const stateMap = /* @__PURE__ */ new Map();
683
+ try {
684
+ await admin2.connect();
685
+ const groupIds = groups.map((g) => g.groupId);
686
+ const described = await admin2.describeGroups(groupIds);
687
+ for (const g of described.groups) {
688
+ stateMap.set(g.groupId, g.state);
689
+ }
690
+ } catch {
691
+ } finally {
692
+ await admin2.disconnect();
693
+ }
694
+ process.stdout.write(`\r${" ".repeat(40)}\r`);
695
+ const sorted = [...groups].sort((a, b) => {
696
+ const stateA = GROUP_STATE_ORDER[stateMap.get(a.groupId) ?? ""] ?? 99;
697
+ const stateB = GROUP_STATE_ORDER[stateMap.get(b.groupId) ?? ""] ?? 99;
698
+ return stateA !== stateB ? stateA - stateB : a.groupId.localeCompare(b.groupId);
699
+ });
700
+ const choices = sorted.map((g) => {
701
+ const state = stateMap.get(g.groupId);
702
+ const stateLabel = state ? stateColor(state) : chalk4.gray("unknown");
703
+ return {
704
+ title: `${g.groupId} ${stateLabel}`,
705
+ value: g.groupId
706
+ };
707
+ });
708
+ console.log("");
709
+ const response = await prompts(
710
+ {
711
+ type: "autocomplete",
712
+ name: "groupId",
713
+ message: "Select a consumer group",
714
+ choices,
715
+ suggest: (input, choices2) => Promise.resolve(
716
+ choices2.filter(
717
+ (c) => c.value.toLowerCase().includes(input.toLowerCase())
718
+ )
719
+ )
720
+ },
721
+ {
722
+ onCancel: () => {
723
+ console.log(chalk4.gray("\n Cancelled\n"));
724
+ process.exit(0);
725
+ }
726
+ }
727
+ );
728
+ console.log("");
729
+ return response.groupId;
730
+ }
731
+ function stateColor(state) {
732
+ switch (state) {
733
+ case "Stable":
734
+ return chalk4.green(`(${state})`);
735
+ case "Empty":
736
+ return chalk4.gray(`(${state})`);
737
+ case "PreparingRebalance":
738
+ case "CompletingRebalance":
739
+ return chalk4.yellow(`(${state})`);
740
+ case "Dead":
741
+ return chalk4.red(`(${state})`);
742
+ default:
743
+ return chalk4.gray(`(${state})`);
744
+ }
745
+ }
746
+
617
747
  // src/cli/validators.ts
618
748
  import { existsSync as existsSync2 } from "fs";
619
749
  import { InvalidArgumentError } from "commander";
@@ -667,7 +797,7 @@ function parseCertPath(value) {
667
797
  }
668
798
 
669
799
  // src/cli/watcher.ts
670
- import chalk4 from "chalk";
800
+ import chalk5 from "chalk";
671
801
  var MAX_RETRIES = 3;
672
802
  function clearScreen() {
673
803
  process.stdout.write("\x1Bc");
@@ -683,31 +813,31 @@ function printWatchHeader(intervalMs, updatedAt) {
683
813
  hour12: false
684
814
  });
685
815
  console.log(
686
- chalk4.bold.cyan("\u26A1 klag") + chalk4.gray(` v${VERSION}`) + " \u2502 " + chalk4.yellow("watch mode") + " \u2502 " + chalk4.gray(`${intervalSec}s refresh`) + " \u2502 " + chalk4.gray("Ctrl+C to exit")
816
+ chalk5.bold.cyan("\u26A1 klag") + chalk5.gray(` v${VERSION}`) + " \u2502 " + chalk5.yellow("watch mode") + " \u2502 " + chalk5.gray(`${intervalSec}s refresh`) + " \u2502 " + chalk5.gray("Ctrl+C to exit")
687
817
  );
688
- console.log(chalk4.gray(` Last updated: ${timeStr} (${tz})`));
818
+ console.log(chalk5.gray(` Last updated: ${timeStr} (${tz})`));
689
819
  }
690
820
  function printWatchError(message, retryCount, retryIn) {
691
821
  clearScreen();
692
822
  console.log(
693
- chalk4.bold.cyan("\u26A1 klag") + chalk4.gray(` v${VERSION}`) + " \u2502 " + chalk4.yellow("watch mode") + " \u2502 " + chalk4.gray("Ctrl+C to exit")
823
+ chalk5.bold.cyan("\u26A1 klag") + chalk5.gray(` v${VERSION}`) + " \u2502 " + chalk5.yellow("watch mode") + " \u2502 " + chalk5.gray("Ctrl+C to exit")
694
824
  );
695
825
  console.log("");
696
- console.error(chalk4.red(` \u274C Error: ${message}`));
826
+ console.error(chalk5.red(` \u274C Error: ${message}`));
697
827
  console.log(
698
- chalk4.yellow(` Retrying ${retryCount}/${MAX_RETRIES}... in ${retryIn}s`)
828
+ chalk5.yellow(` Retrying ${retryCount}/${MAX_RETRIES}... in ${retryIn}s`)
699
829
  );
700
830
  console.log("");
701
831
  }
702
832
  function printWatchFatal(message) {
703
833
  clearScreen();
704
834
  console.log(
705
- chalk4.bold.cyan("\u26A1 klag") + chalk4.gray(` v${VERSION}`) + " \u2502 " + chalk4.yellow("watch mode")
835
+ chalk5.bold.cyan("\u26A1 klag") + chalk5.gray(` v${VERSION}`) + " \u2502 " + chalk5.yellow("watch mode")
706
836
  );
707
837
  console.log("");
708
- console.error(chalk4.red(` \u274C Error: ${message}`));
838
+ console.error(chalk5.red(` \u274C Error: ${message}`));
709
839
  console.error(
710
- chalk4.red(` All ${MAX_RETRIES} retries failed \u2014 exiting watch mode`)
840
+ chalk5.red(` All ${MAX_RETRIES} retries failed \u2014 exiting watch mode`)
711
841
  );
712
842
  console.log("");
713
843
  }
@@ -730,7 +860,7 @@ async function runOnce(options, noRate, previous) {
730
860
  const topics = [...new Set(snapshot.partitions.map((p) => p.topic))];
731
861
  const waitSec = (options.intervalMs ?? 5e3) / 1e3;
732
862
  process.stdout.write(
733
- chalk4.gray(` Sampling rates... (waiting ${waitSec}s) `)
863
+ chalk5.gray(` Sampling rates... (waiting ${waitSec}s) `)
734
864
  );
735
865
  rateSnapshot = await collectRate(options, topics);
736
866
  process.stdout.write(`\r${" ".repeat(50)}\r`);
@@ -747,7 +877,7 @@ function printCountdown(seconds) {
747
877
  let remaining = seconds;
748
878
  const tick = () => {
749
879
  process.stdout.write(
750
- `\r${chalk4.gray(` [\u25CF] Next refresh in ${remaining}s...`)} `
880
+ `\r${chalk5.gray(` [\u25CF] Next refresh in ${remaining}s...`)} `
751
881
  );
752
882
  if (remaining === 0) {
753
883
  process.stdout.write(`\r${" ".repeat(40)}\r`);
@@ -772,12 +902,12 @@ function getFriendlyMessage(err, broker) {
772
902
  }
773
903
  async function startWatch(options, noRate) {
774
904
  process.on("SIGINT", () => {
775
- console.log(chalk4.gray("\n\n Watch mode exited\n"));
905
+ console.log(chalk5.gray("\n\n Watch mode exited\n"));
776
906
  process.exit(0);
777
907
  });
778
908
  const intervalMs = options.intervalMs ?? 5e3;
779
909
  const waitSec = Math.ceil(intervalMs / 1e3);
780
- process.stdout.write(chalk4.gray(" Connecting to broker..."));
910
+ process.stdout.write(chalk5.gray(" Connecting to broker..."));
781
911
  let errorCount = 0;
782
912
  let previousSnapshot;
783
913
  while (true) {
@@ -808,7 +938,10 @@ program.name("klag").description("Kafka consumer lag root cause analyzer").versi
808
938
  "Kafka broker address",
809
939
  parseBroker,
810
940
  "localhost:9092"
811
- ).requiredOption("-g, --group <groupId>", "Consumer group ID").option(
941
+ ).option(
942
+ "-g, --group <groupId>",
943
+ "Consumer group ID (omit to pick interactively)"
944
+ ).option(
812
945
  "-i, --interval <ms>",
813
946
  "Rate sampling interval in ms",
814
947
  parseInterval,
@@ -833,12 +966,11 @@ program.name("klag").description("Kafka consumer lag root cause analyzer").versi
833
966
  const rc = loaded?.config ?? {};
834
967
  if (loaded) {
835
968
  process.stderr.write(
836
- chalk5.gray(` Using config: ${loaded.loadedFrom}
969
+ chalk6.gray(` Using config: ${loaded.loadedFrom}
837
970
  `)
838
971
  );
839
972
  }
840
973
  const broker = options.broker !== "localhost:9092" ? options.broker : rc.broker ?? options.broker;
841
- const groupId = options.group ?? rc.group;
842
974
  const intervalMs = options.interval !== 5e3 ? options.interval : rc.interval ?? options.interval;
843
975
  const timeoutMs = options.timeout !== 5e3 ? options.timeout : rc.timeout ?? options.timeout;
844
976
  const auth = buildAuthOptions({
@@ -850,18 +982,14 @@ program.name("klag").description("Kafka consumer lag root cause analyzer").versi
850
982
  saslUsername: options.saslUsername ?? rc.sasl?.username,
851
983
  saslPassword: options.saslPassword ?? rc.sasl?.password
852
984
  });
853
- const kafkaOptions = {
854
- broker,
855
- groupId,
856
- intervalMs,
857
- timeoutMs,
858
- ...auth
859
- };
985
+ const baseOptions = { broker, intervalMs, timeoutMs, ...auth };
986
+ const groupId = options.group ?? rc.group ?? await pickGroup(baseOptions);
987
+ const kafkaOptions = { ...baseOptions, groupId };
860
988
  if (options.watch) {
861
989
  await startWatch(kafkaOptions, options.rate === false);
862
990
  return;
863
991
  }
864
- process.stdout.write(chalk5.gray(" Connecting to broker..."));
992
+ process.stdout.write(chalk6.gray(" Connecting to broker..."));
865
993
  const snapshot = await collectLag(kafkaOptions);
866
994
  process.stdout.write(`\r${" ".repeat(50)}\r`);
867
995
  let rateSnapshot;
@@ -869,7 +997,7 @@ program.name("klag").description("Kafka consumer lag root cause analyzer").versi
869
997
  const topics = [...new Set(snapshot.partitions.map((p) => p.topic))];
870
998
  const waitSec = (kafkaOptions.intervalMs ?? 5e3) / 1e3;
871
999
  process.stdout.write(
872
- chalk5.gray(` Sampling rates... (waiting ${waitSec}s) `)
1000
+ chalk6.gray(` Sampling rates... (waiting ${waitSec}s) `)
873
1001
  );
874
1002
  rateSnapshot = await collectRate(kafkaOptions, topics);
875
1003
  process.stdout.write(`\r${" ".repeat(50)}\r`);
@@ -897,14 +1025,14 @@ program.name("klag").description("Kafka consumer lag root cause analyzer").versi
897
1025
  process.stdout.write(`\r${" ".repeat(50)}\r`);
898
1026
  const message = err instanceof Error ? err.message : String(err);
899
1027
  if (message.includes("ECONNREFUSED") || message.includes("ETIMEDOUT") || message.includes("Connection error") || message.includes("connect ECONNREFUSED")) {
900
- console.error(chalk5.red(`
1028
+ console.error(chalk6.red(`
901
1029
  \u274C Cannot connect to broker
902
1030
  `));
903
- console.error(chalk5.yellow(" Check the following:"));
904
- console.error(chalk5.gray(` \u2022 Is Kafka running: docker ps`));
905
- console.error(chalk5.gray(` \u2022 Broker address: ${options.broker}`));
1031
+ console.error(chalk6.yellow(" Check the following:"));
1032
+ console.error(chalk6.gray(` \u2022 Is Kafka running: docker ps`));
1033
+ console.error(chalk6.gray(` \u2022 Broker address: ${options.broker}`));
906
1034
  console.error(
907
- chalk5.gray(
1035
+ chalk6.gray(
908
1036
  ` \u2022 Port accessibility: nc -zv ${options.broker.split(":")[0]} ${options.broker.split(":")[1]}`
909
1037
  )
910
1038
  );
@@ -912,18 +1040,18 @@ program.name("klag").description("Kafka consumer lag root cause analyzer").versi
912
1040
  process.exit(1);
913
1041
  }
914
1042
  if (message.includes("SASLAuthenticationFailed") || message.includes("Authentication failed") || message.includes("SASL")) {
915
- console.error(chalk5.red(`
1043
+ console.error(chalk6.red(`
916
1044
  \u274C SASL authentication failed
917
1045
  `));
918
- console.error(chalk5.yellow(" Check the following:"));
1046
+ console.error(chalk6.yellow(" Check the following:"));
919
1047
  console.error(
920
- chalk5.gray(` \u2022 Mechanism: ${options.saslMechanism ?? "(none)"}`)
1048
+ chalk6.gray(` \u2022 Mechanism: ${options.saslMechanism ?? "(none)"}`)
921
1049
  );
922
1050
  console.error(
923
- chalk5.gray(` \u2022 Username: ${options.saslUsername ?? "(none)"}`)
1051
+ chalk6.gray(` \u2022 Username: ${options.saslUsername ?? "(none)"}`)
924
1052
  );
925
1053
  console.error(
926
- chalk5.gray(
1054
+ chalk6.gray(
927
1055
  ` \u2022 Password: set via KLAG_SASL_PASSWORD or --sasl-password`
928
1056
  )
929
1057
  );
@@ -931,21 +1059,21 @@ program.name("klag").description("Kafka consumer lag root cause analyzer").versi
931
1059
  process.exit(1);
932
1060
  }
933
1061
  if (message.includes("not found") || message.includes("Dead state")) {
934
- console.error(chalk5.red(`
1062
+ console.error(chalk6.red(`
935
1063
  \u274C Consumer group not found
936
1064
  `));
937
- console.error(chalk5.yellow(" Check the following:"));
938
- console.error(chalk5.gray(` \u2022 Group ID: ${options.group}`));
939
- console.error(chalk5.gray(` \u2022 List existing groups:`));
1065
+ console.error(chalk6.yellow(" Check the following:"));
1066
+ console.error(chalk6.gray(` \u2022 Group ID: ${options.group}`));
1067
+ console.error(chalk6.gray(` \u2022 List existing groups:`));
940
1068
  console.error(
941
- chalk5.gray(
1069
+ chalk6.gray(
942
1070
  ` kafka-consumer-groups.sh --bootstrap-server ${options.broker} --list`
943
1071
  )
944
1072
  );
945
1073
  console.error("");
946
1074
  process.exit(1);
947
1075
  }
948
- console.error(chalk5.red(`
1076
+ console.error(chalk6.red(`
949
1077
  \u274C Error: ${message}
950
1078
  `));
951
1079
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@closeup1202/klag",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Kafka consumer lag root cause analyzer",
5
5
  "type": "module",
6
6
  "bin": {
@@ -39,11 +39,13 @@
39
39
  "chalk": "^5.6.2",
40
40
  "cli-table3": "^0.6.5",
41
41
  "commander": "^14.0.3",
42
- "kafkajs": "^2.2.4"
42
+ "kafkajs": "^2.2.4",
43
+ "prompts": "^2.4.2"
43
44
  },
44
45
  "devDependencies": {
45
46
  "@biomejs/biome": "^2.4.8",
46
47
  "@types/node": "^25.5.0",
48
+ "@types/prompts": "^2.4.9",
47
49
  "tsup": "^8.5.1",
48
50
  "tsx": "^4.21.0",
49
51
  "typescript": "^6.0.2",