@kinosec/cli 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -10894,16 +10894,89 @@ function ora(options) {
10894
10894
  return new Ora(options);
10895
10895
  }
10896
10896
 
10897
+ // src/lib/activity-filter.ts
10898
+ function normalizeActivityHistory(raw) {
10899
+ if (!raw) return [];
10900
+ return raw.map((entry) => {
10901
+ if (typeof entry === "string") return entry;
10902
+ if (entry && typeof entry === "object" && "message" in entry) {
10903
+ return String(entry.message);
10904
+ }
10905
+ return JSON.stringify(entry);
10906
+ });
10907
+ }
10908
+ function filterActivityForTier(entries, showFullDetails) {
10909
+ if (showFullDetails) return entries;
10910
+ return entries.filter((line) => !/^\s{4,}/.test(line)).map((line) => {
10911
+ if (/\] Running:/.test(line)) {
10912
+ return line.replace(
10913
+ /Running:.*/,
10914
+ "Running: [upgrade to Security Pro for full details]"
10915
+ );
10916
+ }
10917
+ return line;
10918
+ });
10919
+ }
10920
+
10921
+ // src/lib/buzzwords.ts
10922
+ var SCAN_EXECUTION_PHRASES = [
10923
+ "DEEP SCAN",
10924
+ "PROBING",
10925
+ "SWEEPING",
10926
+ "SIGNAL LOCK",
10927
+ "ENUMERATING",
10928
+ "TRAVERSING",
10929
+ "MAPPING",
10930
+ "RECON",
10931
+ "INTERCEPTING",
10932
+ "CALIBRATING",
10933
+ "FINGERPRINTING",
10934
+ "NET SWEEP",
10935
+ "FULL SPECTRUM",
10936
+ "RESOLVING",
10937
+ "HANDSHAKE",
10938
+ "DISPATCHING",
10939
+ "CORRELATING",
10940
+ "DARK TRACE",
10941
+ "ECHO RETURN",
10942
+ "MULTIPLEXING",
10943
+ "NODE ACTIVE",
10944
+ "FLUX STATE",
10945
+ "PARSING",
10946
+ "BEACON ACTIVE"
10947
+ ];
10948
+ function shuffle(arr) {
10949
+ for (let i = arr.length - 1; i > 0; i--) {
10950
+ const j = Math.floor(Math.random() * (i + 1));
10951
+ [arr[i], arr[j]] = [arr[j], arr[i]];
10952
+ }
10953
+ }
10954
+ function createBuzzwordCycler(phrases, intervalMs, onTick) {
10955
+ const pool = [...phrases];
10956
+ shuffle(pool);
10957
+ let index = 0;
10958
+ function advance() {
10959
+ index++;
10960
+ if (index >= pool.length) {
10961
+ shuffle(pool);
10962
+ index = 0;
10963
+ }
10964
+ onTick?.(pool[index]);
10965
+ }
10966
+ const timer = setInterval(advance, intervalMs);
10967
+ return {
10968
+ current: () => pool[index],
10969
+ destroy: () => clearInterval(timer)
10970
+ };
10971
+ }
10972
+
10897
10973
  // src/lib/watch.ts
10898
10974
  var UNICODE = isUnicodeSupported();
10899
10975
  var sym = {
10900
10976
  success: UNICODE ? "\u2713" : "v",
10901
10977
  fail: UNICODE ? "\u2717" : "x",
10902
- warn: UNICODE ? "\u26A0" : "!",
10903
- rule: UNICODE ? "\u2500" : "-"
10978
+ warn: UNICODE ? "\u26A0" : "!"
10904
10979
  };
10905
- var LABEL_WIDTH = 40;
10906
- var TIMING_WIDTH = 8;
10907
10980
  function formatDuration(ms) {
10908
10981
  if (ms < 1e4) return `${(ms / 1e3).toFixed(1)}s`;
10909
10982
  if (ms < 6e4) return `${Math.round(ms / 1e3)}s`;
@@ -10912,14 +10985,6 @@ function formatDuration(ms) {
10912
10985
  const r = totalSec % 60;
10913
10986
  return `${m}m ${r.toString().padStart(2, "0")}s`;
10914
10987
  }
10915
- function completedStepLine(label, phaseMs, totalMs) {
10916
- const lbl = label.padEnd(LABEL_WIDTH);
10917
- const dur = formatDuration(phaseMs).padStart(TIMING_WIDTH);
10918
- const tty2 = isTTY();
10919
- const total = tty2 ? import_picocolors5.default.dim(`(total ${formatDuration(totalMs)})`) : `(total ${formatDuration(totalMs)})`;
10920
- const icon = tty2 ? import_picocolors5.default.green(sym.success) : sym.success;
10921
- return `${icon} ${lbl}${dur} ${total}`;
10922
- }
10923
10988
  function renderFindingsSummary(counts) {
10924
10989
  const levels = [
10925
10990
  { key: "critical", label: "critical", color: import_picocolors5.default.red },
@@ -10941,55 +11006,80 @@ function findCount(counts, level) {
10941
11006
  const key = Object.keys(counts).find((k) => k.toLowerCase() === level);
10942
11007
  return key ? counts[key] : 0;
10943
11008
  }
11009
+ function extractPhase(entries) {
11010
+ for (let i = entries.length - 1; i >= 0; i--) {
11011
+ const m = entries[i].match(/^\[([^\]]+)\]/);
11012
+ if (m) return m[1].toUpperCase();
11013
+ }
11014
+ return null;
11015
+ }
11016
+ function printActivityLines(entries, tty2) {
11017
+ for (let i = 0; i < entries.length; i++) {
11018
+ const isLastEntry = i === entries.length - 1;
11019
+ const prompt = tty2 ? import_picocolors5.default.green(">") : ">";
11020
+ const text = tty2 ? isLastEntry ? import_picocolors5.default.green(entries[i]) : import_picocolors5.default.dim(import_picocolors5.default.green(entries[i])) : entries[i];
11021
+ console.log(`${prompt} ${text}`);
11022
+ }
11023
+ }
11024
+ async function resolveShowFullDetails(client) {
11025
+ try {
11026
+ const me = await client.me.get();
11027
+ const slug = me.plan.slug;
11028
+ return slug === "security_pro" || slug === "enterprise";
11029
+ } catch {
11030
+ return false;
11031
+ }
11032
+ }
10944
11033
  async function watchPentest(client, profileId, options) {
10945
11034
  const { timeoutMs, intervalMs, useJson, showFindingsSummary } = options;
10946
11035
  const tty2 = isTTY();
10947
11036
  const loopStart = Date.now();
10948
11037
  let spinner = null;
10949
- let lastActivity = "";
10950
- let phaseStart = Date.now();
11038
+ let printedCount = 0;
11039
+ let currentPhase = null;
11040
+ let cycler = null;
11041
+ function updateSpinnerText() {
11042
+ if (!spinner || !cycler) return;
11043
+ const buzz = cycler.current();
11044
+ spinner.text = import_picocolors5.default.green(currentPhase ? `[${currentPhase}] ${buzz}...` : `${buzz}...`);
11045
+ }
10951
11046
  if (!useJson && tty2) {
10952
- spinner = ora({ text: "Fetching pentest status...", isEnabled: true }).start();
11047
+ spinner = ora({ text: "Fetching pentest status...", color: "green", isEnabled: true }).start();
11048
+ cycler = createBuzzwordCycler(SCAN_EXECUTION_PHRASES, 2500, () => {
11049
+ updateSpinnerText();
11050
+ });
10953
11051
  }
10954
11052
  const cleanup = () => {
10955
11053
  if (spinner) spinner.stop();
11054
+ if (cycler) cycler.destroy();
10956
11055
  };
10957
11056
  process.once("SIGINT", () => {
10958
11057
  cleanup();
10959
11058
  console.log("\nWatch cancelled. Pentest is still running.");
10960
11059
  process.exit(0);
10961
11060
  });
11061
+ const showFullDetails = await resolveShowFullDetails(client);
10962
11062
  while (true) {
10963
11063
  const scanDetail = await client.scans.get(profileId);
10964
- const activity = scanDetail.current_activity || "";
10965
11064
  const totalMs = Date.now() - loopStart;
10966
- const currentPhaseMs = Date.now() - phaseStart;
10967
11065
  if (useJson) {
10968
11066
  const elapsed = Math.round(totalMs / 1e3);
10969
11067
  process.stdout.write(
10970
11068
  JSON.stringify({ ...scanDetail, elapsed_seconds: elapsed }) + "\n"
10971
11069
  );
10972
- } else if (spinner) {
10973
- if (activity && activity !== lastActivity && lastActivity) {
10974
- const phaseDuration = Date.now() - phaseStart;
10975
- const line = completedStepLine(lastActivity, phaseDuration, totalMs);
10976
- spinner.stopAndPersist({ symbol: "", text: line });
10977
- spinner = ora({ isEnabled: true }).start();
10978
- phaseStart = Date.now();
10979
- }
10980
- lastActivity = activity || lastActivity;
10981
- const dur = formatDuration(currentPhaseMs);
10982
- spinner.text = `${activity || lastActivity} ${import_picocolors5.default.dim(dur)}`;
10983
- } else if (!useJson) {
10984
- if (activity && activity !== lastActivity && lastActivity) {
10985
- const phaseDuration = Date.now() - phaseStart;
10986
- console.log(`${sym.success} ${lastActivity} ${formatDuration(phaseDuration)}`);
10987
- phaseStart = Date.now();
10988
- }
10989
- if (activity && activity !== lastActivity) {
10990
- console.log(`\u2022 ${activity}`);
10991
- }
10992
- lastActivity = activity || lastActivity;
11070
+ } else {
11071
+ const normalized = normalizeActivityHistory(scanDetail.activity_history);
11072
+ const filtered = filterActivityForTier(normalized, showFullDetails);
11073
+ if (printedCount > filtered.length) printedCount = 0;
11074
+ const newEntries = filtered.slice(printedCount);
11075
+ if (newEntries.length > 0) {
11076
+ if (spinner) spinner.stop();
11077
+ printActivityLines(newEntries, tty2);
11078
+ printedCount += newEntries.length;
11079
+ if (spinner) spinner = ora({ color: "green", isEnabled: true }).start();
11080
+ }
11081
+ currentPhase = extractPhase(filtered);
11082
+ updateSpinnerText();
10993
11083
  }
10994
11084
  const terminalStatuses = ["Completed", "Failed", "Stopped"];
10995
11085
  if (terminalStatuses.some((s) => scanDetail.scan_status.includes(s))) {
@@ -12162,7 +12252,7 @@ Examples:
12162
12252
 
12163
12253
  // src/cli.ts
12164
12254
  var program2 = new Command();
12165
- program2.name("kinosec").description("KinoSec CLI \u2014 run pentests from your terminal").version("0.6.2", "-v, --version").option("--token <pat>", "API token (overrides KINOSEC_TOKEN and config)").option("--api-url <url>", "API base URL (overrides config)").option("--output <format>", "Output format: text or json", "text").option("--json", "Shorthand for --output json").option("--no-color", "Disable colors");
12255
+ program2.name("kinosec").description("KinoSec CLI \u2014 run pentests from your terminal").version("0.7.0", "-v, --version").option("--token <pat>", "API token (overrides KINOSEC_TOKEN and config)").option("--api-url <url>", "API base URL (overrides config)").option("--output <format>", "Output format: text or json", "text").option("--json", "Shorthand for --output json").option("--no-color", "Disable colors");
12166
12256
  registerAuthCommand(program2);
12167
12257
  registerConfigCommand(program2);
12168
12258
  registerMeCommand(program2);
@@ -12171,7 +12261,7 @@ registerAssetCommand(program2);
12171
12261
  registerFindingCommand(program2);
12172
12262
  registerReportCommand(program2);
12173
12263
  registerTokenCommand(program2);
12174
- program2.parseAsync(process.argv).then(() => checkForUpdate("0.6.2")).catch(handleError);
12264
+ program2.parseAsync(process.argv).then(() => checkForUpdate("0.7.0")).catch(handleError);
12175
12265
  /*! Bundled license information:
12176
12266
 
12177
12267
  ky/distribution/index.js: