@kinosec/cli 0.4.0 → 0.6.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
@@ -9283,6 +9283,320 @@ Examples:
9283
9283
  });
9284
9284
  }
9285
9285
 
9286
+ // src/commands/pentest.ts
9287
+ var import_picocolors6 = __toESM(require_picocolors(), 1);
9288
+
9289
+ // src/lib/confirm.ts
9290
+ var import_picocolors3 = __toESM(require_picocolors(), 1);
9291
+ import { createInterface } from "readline";
9292
+ async function confirmDestructive(message, yes) {
9293
+ if (yes) return;
9294
+ if (!isTTY()) {
9295
+ console.error("Use --yes to confirm destructive operations in non-interactive mode.");
9296
+ process.exit(2);
9297
+ }
9298
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
9299
+ const answer = await new Promise((resolve) => {
9300
+ rl.question(`${message} (y/N) `, resolve);
9301
+ });
9302
+ rl.close();
9303
+ if (answer.toLowerCase() !== "y") {
9304
+ console.log("Cancelled.");
9305
+ process.exit(0);
9306
+ }
9307
+ }
9308
+ async function confirmAuthorization(target, yes) {
9309
+ if (yes) return;
9310
+ if (!isTTY()) {
9311
+ console.error("Use --yes to accept authorization terms in non-interactive mode.");
9312
+ process.exit(2);
9313
+ }
9314
+ const warning = import_picocolors3.default.yellow("\u26A0");
9315
+ console.error(`
9316
+ ${warning} ${import_picocolors3.default.bold("Authorization Required")}
9317
+
9318
+ Penetration testing without proper authorization is illegal
9319
+ and may result in criminal prosecution.
9320
+
9321
+ ${import_picocolors3.default.bold("Target:")} ${target}
9322
+
9323
+ By proceeding, you confirm that:
9324
+ ${import_picocolors3.default.dim("\u2022")} You own or have explicit written authorization to test this target
9325
+ ${import_picocolors3.default.dim("\u2022")} You will comply with all applicable laws and regulations
9326
+ ${import_picocolors3.default.dim("\u2022")} You will not use the results for illegal or malicious purposes
9327
+ ${import_picocolors3.default.dim("\u2022")} You accept full legal responsibility for this pentest
9328
+
9329
+ ${import_picocolors3.default.dim("Unauthorized access to computer systems is a criminal offense under")}
9330
+ ${import_picocolors3.default.dim("laws such as the CFAA (US), Computer Misuse Act (UK), and similar")}
9331
+ ${import_picocolors3.default.dim("legislation worldwide. KinoSec bears no liability for unauthorized")}
9332
+ ${import_picocolors3.default.dim("use of its services.")}
9333
+ `);
9334
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
9335
+ const answer = await new Promise((resolve) => {
9336
+ rl.question("Agree and continue? (y/N) ", resolve);
9337
+ });
9338
+ rl.close();
9339
+ if (answer.toLowerCase() !== "y") {
9340
+ console.log("Cancelled.");
9341
+ process.exit(0);
9342
+ }
9343
+ }
9344
+
9345
+ // src/commands/schedule.ts
9346
+ var import_picocolors4 = __toESM(require_picocolors(), 1);
9347
+ var VALID_PATTERNS = ["once", "daily", "weekly", "biweekly", "monthly"];
9348
+ var VALID_MODES = ["quick", "full"];
9349
+ var DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
9350
+ function extractTime(scheduledAt) {
9351
+ if (!scheduledAt) return "\u2014";
9352
+ const d = new Date(scheduledAt);
9353
+ return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", hour12: false });
9354
+ }
9355
+ function formatPattern(s) {
9356
+ const time = extractTime(s.scheduled_at);
9357
+ switch (s.recurrence_pattern) {
9358
+ case "once":
9359
+ return `Once at ${time}`;
9360
+ case "daily":
9361
+ return `Daily at ${time}`;
9362
+ case "weekly":
9363
+ return `Weekly ${s.day_of_week !== null ? DAY_NAMES[s.day_of_week] : ""} at ${time}`;
9364
+ case "biweekly":
9365
+ return `Every 2 weeks ${s.day_of_week !== null ? DAY_NAMES[s.day_of_week] : ""} at ${time}`;
9366
+ case "monthly":
9367
+ return `Monthly day ${s.day_of_month ?? "?"} at ${time}`;
9368
+ default:
9369
+ return s.recurrence_pattern;
9370
+ }
9371
+ }
9372
+ function registerScheduleCommand(pentestCmd) {
9373
+ const schedule = pentestCmd.command("schedule").description("Manage scheduled pentests");
9374
+ schedule.command("list").description("List scheduled pentests").option("--limit <n>", "Max results per page", "20").option("--cursor <cursor>", "Pagination cursor").option("--status <status>", "Filter: active, paused, or all", "all").addHelpText("after", `
9375
+ Examples:
9376
+ $ kinosec pentest schedule list
9377
+ $ kinosec pentest schedule list --status active --json`).action(async (options) => {
9378
+ try {
9379
+ const opts = schedule.parent.parent.opts();
9380
+ const { token, apiUrl } = resolveToken(opts);
9381
+ const client = new KinoSecClient({ apiUrl, token });
9382
+ const result = await client.schedules.list({
9383
+ limit: parseInt(options.limit, 10),
9384
+ cursor: options.cursor,
9385
+ status: options.status
9386
+ });
9387
+ if (opts.json || opts.output === "json") {
9388
+ printJson(result);
9389
+ return;
9390
+ }
9391
+ if (result.items.length === 0) {
9392
+ console.log("No scheduled pentests found.");
9393
+ return;
9394
+ }
9395
+ printTable(
9396
+ ["ID", "Target", "Mode", "Schedule", "Status", "Next Run"],
9397
+ result.items.map((s) => [
9398
+ s.id,
9399
+ truncate(s.domain_or_ip, 25),
9400
+ s.scan_mode,
9401
+ formatPattern(s),
9402
+ s.is_active ? import_picocolors4.default.green("active") : import_picocolors4.default.yellow("paused"),
9403
+ s.next_scheduled_run ? new Date(s.next_scheduled_run).toLocaleString() : "\u2014"
9404
+ ])
9405
+ );
9406
+ if (result.next_cursor) {
9407
+ console.log(import_picocolors4.default.dim(`
9408
+ More results available. Use --cursor ${result.next_cursor}`));
9409
+ }
9410
+ } catch (error) {
9411
+ handleError(error);
9412
+ }
9413
+ });
9414
+ schedule.command("get").description("Get schedule details").argument("<id>", "Schedule ID").addHelpText("after", `
9415
+ Examples:
9416
+ $ kinosec pentest schedule get abc123
9417
+ $ kinosec pentest schedule get abc123 --json`).action(async (id) => {
9418
+ try {
9419
+ const opts = schedule.parent.parent.opts();
9420
+ const { token, apiUrl } = resolveToken(opts);
9421
+ const client = new KinoSecClient({ apiUrl, token });
9422
+ const s = await client.schedules.get(id);
9423
+ if (opts.json || opts.output === "json") {
9424
+ printJson(s);
9425
+ return;
9426
+ }
9427
+ printTable(
9428
+ ["Field", "Value"],
9429
+ [
9430
+ ["ID", s.id],
9431
+ ["Target", s.domain_or_ip],
9432
+ ["Name", s.name],
9433
+ ["Mode", s.scan_mode],
9434
+ ["Schedule", formatPattern(s)],
9435
+ ["Timezone", s.timezone],
9436
+ ["Status", s.is_active ? "Active" : "Paused"],
9437
+ ["Next Run", s.next_scheduled_run ?? "\u2014"],
9438
+ ["Last Run", s.last_scheduled_run ?? "Never"],
9439
+ ["Created", s.created_at]
9440
+ ]
9441
+ );
9442
+ } catch (error) {
9443
+ handleError(error);
9444
+ }
9445
+ });
9446
+ schedule.command("create").description("Create a scheduled pentest").requiredOption("--asset <id>", "Asset ID to scan").requiredOption("--pattern <pattern>", "Recurrence: once, daily, weekly, biweekly, monthly").option("--name <name>", "Schedule name").option("--mode <mode>", "Scan mode: quick or full", "full").option("--time <HH:MM>", "Time of day (24h format)", "09:00").option("--day-of-week <0-6>", "Day of week (0=Sun..6=Sat) for weekly/biweekly").option("--day-of-month <1-31>", "Day of month for monthly").option("--timezone <tz>", "Timezone (e.g. America/New_York)", Intl.DateTimeFormat().resolvedOptions().timeZone).addHelpText("after", `
9447
+ Examples:
9448
+ $ kinosec pentest schedule create --asset abc123 --pattern weekly --day-of-week 1 --time 09:00
9449
+ $ kinosec pentest schedule create --asset abc123 --pattern daily --mode quick --time 14:00
9450
+ $ kinosec pentest schedule create --asset abc123 --pattern monthly --day-of-month 15 --time 03:00 --timezone UTC`).action(async (options) => {
9451
+ if (!VALID_PATTERNS.includes(options.pattern)) {
9452
+ console.error(`Invalid pattern "${options.pattern}". Must be: ${VALID_PATTERNS.join(", ")}`);
9453
+ process.exit(2);
9454
+ }
9455
+ if (!VALID_MODES.includes(options.mode)) {
9456
+ console.error(`Invalid mode "${options.mode}". Must be: ${VALID_MODES.join(", ")}`);
9457
+ process.exit(2);
9458
+ }
9459
+ if ((options.pattern === "weekly" || options.pattern === "biweekly") && options.dayOfWeek === void 0) {
9460
+ console.error(`--day-of-week is required for ${options.pattern} schedules.`);
9461
+ process.exit(2);
9462
+ }
9463
+ if (options.pattern === "monthly" && options.dayOfMonth === void 0) {
9464
+ console.error("--day-of-month is required for monthly schedules.");
9465
+ process.exit(2);
9466
+ }
9467
+ try {
9468
+ const opts = schedule.parent.parent.opts();
9469
+ const { token, apiUrl } = resolveToken(opts);
9470
+ const client = new KinoSecClient({ apiUrl, token });
9471
+ const params = {
9472
+ asset_id: options.domain_or_ip,
9473
+ name: options.name ?? `${options.pattern} ${options.mode} pentest`,
9474
+ scan_mode: options.mode,
9475
+ recurrence_pattern: options.pattern,
9476
+ time_of_day: options.time,
9477
+ timezone: options.timezone
9478
+ };
9479
+ if (options.dayOfWeek !== void 0) params.day_of_week = parseInt(options.dayOfWeek, 10);
9480
+ if (options.dayOfMonth !== void 0) params.day_of_month = parseInt(options.dayOfMonth, 10);
9481
+ const s = await client.schedules.create(params);
9482
+ if (opts.json || opts.output === "json") {
9483
+ printJson(s);
9484
+ return;
9485
+ }
9486
+ printSuccess(`Schedule created: ${s.domain_or_ip}`);
9487
+ console.log(` ID: ${s.id}`);
9488
+ console.log(` Schedule: ${formatPattern(s)}`);
9489
+ console.log(` Next run: ${s.next_scheduled_run ?? "pending"}`);
9490
+ console.log(`
9491
+ Manage: kinosec pentest schedule get ${s.id}`);
9492
+ } catch (error) {
9493
+ handleError(error);
9494
+ }
9495
+ });
9496
+ schedule.command("update").description("Update a schedule").argument("<id>", "Schedule ID").option("--pattern <pattern>", "Recurrence: once, daily, weekly, biweekly, monthly").option("--name <name>", "Schedule name").option("--mode <mode>", "Scan mode: quick or full").option("--time <HH:MM>", "Time of day (24h format)").option("--day-of-week <0-6>", "Day of week (0=Sun..6=Sat)").option("--day-of-month <1-31>", "Day of month").option("--timezone <tz>", "Timezone").addHelpText("after", `
9497
+ Examples:
9498
+ $ kinosec pentest schedule update abc123 --pattern daily --time 14:00
9499
+ $ kinosec pentest schedule update abc123 --mode quick`).action(async (id, options) => {
9500
+ const params = {};
9501
+ if (options.pattern) params.recurrence_pattern = options.pattern;
9502
+ if (options.name) params.name = options.name;
9503
+ if (options.mode) params.scan_mode = options.mode;
9504
+ if (options.time) params.time_of_day = options.time;
9505
+ if (options.dayOfWeek !== void 0) params.day_of_week = parseInt(options.dayOfWeek, 10);
9506
+ if (options.dayOfMonth !== void 0) params.day_of_month = parseInt(options.dayOfMonth, 10);
9507
+ if (options.timezone) params.timezone = options.timezone;
9508
+ if (Object.keys(params).length === 0) {
9509
+ console.error("Provide at least one field to update.");
9510
+ process.exit(2);
9511
+ }
9512
+ try {
9513
+ const opts = schedule.parent.parent.opts();
9514
+ const { token, apiUrl } = resolveToken(opts);
9515
+ const client = new KinoSecClient({ apiUrl, token });
9516
+ const s = await client.schedules.update(id, params);
9517
+ if (opts.json || opts.output === "json") {
9518
+ printJson(s);
9519
+ return;
9520
+ }
9521
+ printSuccess(`Schedule updated: ${s.domain_or_ip}`);
9522
+ console.log(` Schedule: ${formatPattern(s)}`);
9523
+ console.log(` Next run: ${s.next_scheduled_run ?? "pending"}`);
9524
+ } catch (error) {
9525
+ handleError(error);
9526
+ }
9527
+ });
9528
+ schedule.command("delete").description("Delete a schedule").argument("<id>", "Schedule ID").option("-y, --yes", "Skip confirmation prompt").addHelpText("after", `
9529
+ Examples:
9530
+ $ kinosec pentest schedule delete abc123
9531
+ $ kinosec pentest schedule delete abc123 --yes`).action(async (id, options) => {
9532
+ try {
9533
+ await confirmDestructive(`Delete schedule ${id}?`, Boolean(options.yes));
9534
+ const opts = schedule.parent.parent.opts();
9535
+ const { token, apiUrl } = resolveToken(opts);
9536
+ const client = new KinoSecClient({ apiUrl, token });
9537
+ await client.schedules.delete(id);
9538
+ if (opts.json || opts.output === "json") {
9539
+ printJson({ deleted: true, id });
9540
+ return;
9541
+ }
9542
+ printSuccess(`Schedule ${id} deleted.`);
9543
+ } catch (error) {
9544
+ handleError(error);
9545
+ }
9546
+ });
9547
+ schedule.command("pause").description("Pause a scheduled pentest").argument("<id>", "Schedule ID").addHelpText("after", `
9548
+ Examples:
9549
+ $ kinosec pentest schedule pause abc123`).action(async (id) => {
9550
+ try {
9551
+ const opts = schedule.parent.parent.opts();
9552
+ const { token, apiUrl } = resolveToken(opts);
9553
+ const client = new KinoSecClient({ apiUrl, token });
9554
+ const result = await client.schedules.pause(id);
9555
+ if (opts.json || opts.output === "json") {
9556
+ printJson(result);
9557
+ return;
9558
+ }
9559
+ printSuccess("Schedule paused.");
9560
+ } catch (error) {
9561
+ handleError(error);
9562
+ }
9563
+ });
9564
+ schedule.command("resume").description("Resume a paused schedule").argument("<id>", "Schedule ID").addHelpText("after", `
9565
+ Examples:
9566
+ $ kinosec pentest schedule resume abc123`).action(async (id) => {
9567
+ try {
9568
+ const opts = schedule.parent.parent.opts();
9569
+ const { token, apiUrl } = resolveToken(opts);
9570
+ const client = new KinoSecClient({ apiUrl, token });
9571
+ const result = await client.schedules.resume(id);
9572
+ if (opts.json || opts.output === "json") {
9573
+ printJson(result);
9574
+ return;
9575
+ }
9576
+ printSuccess("Schedule resumed.");
9577
+ if (result.next_scheduled_run) {
9578
+ console.log(` Next run: ${new Date(result.next_scheduled_run).toLocaleString()}`);
9579
+ }
9580
+ } catch (error) {
9581
+ handleError(error);
9582
+ }
9583
+ });
9584
+ }
9585
+
9586
+ // src/lib/watch.ts
9587
+ var import_picocolors5 = __toESM(require_picocolors(), 1);
9588
+
9589
+ // ../../node_modules/.pnpm/is-unicode-supported@2.1.0/node_modules/is-unicode-supported/index.js
9590
+ import process2 from "process";
9591
+ function isUnicodeSupported() {
9592
+ const { env: env2 } = process2;
9593
+ const { TERM, TERM_PROGRAM } = env2;
9594
+ if (process2.platform !== "win32") {
9595
+ return TERM !== "linux";
9596
+ }
9597
+ return Boolean(env2.WT_SESSION) || Boolean(env2.TERMINUS_SUBLIME) || env2.ConEmuTask === "{cmd::Cmder}" || TERM_PROGRAM === "Terminus-Sublime" || TERM_PROGRAM === "vscode" || TERM === "xterm-256color" || TERM === "alacritty" || TERM === "rxvt-unicode" || TERM === "rxvt-unicode-256color" || env2.TERMINAL_EMULATOR === "JetBrains-JediTerm";
9598
+ }
9599
+
9286
9600
  // ../../node_modules/.pnpm/ora@8.2.0/node_modules/ora/index.js
9287
9601
  import process9 from "process";
9288
9602
 
@@ -9473,16 +9787,16 @@ var ansiStyles2 = assembleStyles2();
9473
9787
  var ansi_styles_default2 = ansiStyles2;
9474
9788
 
9475
9789
  // ../../node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/vendor/supports-color/index.js
9476
- import process2 from "process";
9790
+ import process3 from "process";
9477
9791
  import os from "os";
9478
9792
  import tty from "tty";
9479
- function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process2.argv) {
9793
+ function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process3.argv) {
9480
9794
  const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
9481
9795
  const position = argv.indexOf(prefix + flag);
9482
9796
  const terminatorPosition = argv.indexOf("--");
9483
9797
  return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
9484
9798
  }
9485
- var { env } = process2;
9799
+ var { env } = process3;
9486
9800
  var flagForceColor;
9487
9801
  if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
9488
9802
  flagForceColor = 0;
@@ -9538,7 +9852,7 @@ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
9538
9852
  if (env.TERM === "dumb") {
9539
9853
  return min;
9540
9854
  }
9541
- if (process2.platform === "win32") {
9855
+ if (process3.platform === "win32") {
9542
9856
  const osRelease = os.release().split(".");
9543
9857
  if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
9544
9858
  return Number(osRelease[2]) >= 14931 ? 3 : 2;
@@ -9782,10 +10096,10 @@ var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
9782
10096
  var source_default = chalk;
9783
10097
 
9784
10098
  // ../../node_modules/.pnpm/cli-cursor@5.0.0/node_modules/cli-cursor/index.js
9785
- import process5 from "process";
10099
+ import process6 from "process";
9786
10100
 
9787
10101
  // ../../node_modules/.pnpm/restore-cursor@5.1.0/node_modules/restore-cursor/index.js
9788
- import process4 from "process";
10102
+ import process5 from "process";
9789
10103
 
9790
10104
  // ../../node_modules/.pnpm/mimic-function@5.0.1/node_modules/mimic-function/index.js
9791
10105
  var copyProperty = (to, from, property, ignoreNonConfigurable) => {
@@ -9974,7 +10288,7 @@ var SignalExit = class extends SignalExitBase {
9974
10288
  // "SIGHUP" throws an `ENOSYS` error on Windows,
9975
10289
  // so use a supported signal instead
9976
10290
  /* c8 ignore start */
9977
- #hupSig = process3.platform === "win32" ? "SIGINT" : "SIGHUP";
10291
+ #hupSig = process4.platform === "win32" ? "SIGINT" : "SIGHUP";
9978
10292
  /* c8 ignore stop */
9979
10293
  #emitter = new Emitter();
9980
10294
  #process;
@@ -10085,7 +10399,7 @@ var SignalExit = class extends SignalExitBase {
10085
10399
  }
10086
10400
  }
10087
10401
  };
10088
- var process3 = globalThis.process;
10402
+ var process4 = globalThis.process;
10089
10403
  var {
10090
10404
  /**
10091
10405
  * Called when the process is exiting, whether via signal, explicit
@@ -10113,10 +10427,10 @@ var {
10113
10427
  * @internal
10114
10428
  */
10115
10429
  unload
10116
- } = signalExitWrap(processOk(process3) ? new SignalExit(process3) : new SignalExitFallback());
10430
+ } = signalExitWrap(processOk(process4) ? new SignalExit(process4) : new SignalExitFallback());
10117
10431
 
10118
10432
  // ../../node_modules/.pnpm/restore-cursor@5.1.0/node_modules/restore-cursor/index.js
10119
- var terminal = process4.stderr.isTTY ? process4.stderr : process4.stdout.isTTY ? process4.stdout : void 0;
10433
+ var terminal = process5.stderr.isTTY ? process5.stderr : process5.stdout.isTTY ? process5.stdout : void 0;
10120
10434
  var restoreCursor = terminal ? onetime_default(() => {
10121
10435
  onExit(() => {
10122
10436
  terminal.write("\x1B[?25h");
@@ -10128,14 +10442,14 @@ var restore_cursor_default = restoreCursor;
10128
10442
  // ../../node_modules/.pnpm/cli-cursor@5.0.0/node_modules/cli-cursor/index.js
10129
10443
  var isHidden = false;
10130
10444
  var cliCursor = {};
10131
- cliCursor.show = (writableStream = process5.stderr) => {
10445
+ cliCursor.show = (writableStream = process6.stderr) => {
10132
10446
  if (!writableStream.isTTY) {
10133
10447
  return;
10134
10448
  }
10135
10449
  isHidden = false;
10136
10450
  writableStream.write("\x1B[?25h");
10137
10451
  };
10138
- cliCursor.hide = (writableStream = process5.stderr) => {
10452
+ cliCursor.hide = (writableStream = process6.stderr) => {
10139
10453
  if (!writableStream.isTTY) {
10140
10454
  return;
10141
10455
  }
@@ -10159,12 +10473,12 @@ var cli_cursor_default = cliCursor;
10159
10473
  var import_cli_spinners = __toESM(require_cli_spinners(), 1);
10160
10474
 
10161
10475
  // ../../node_modules/.pnpm/is-unicode-supported@1.3.0/node_modules/is-unicode-supported/index.js
10162
- import process6 from "process";
10163
- function isUnicodeSupported() {
10164
- if (process6.platform !== "win32") {
10165
- return process6.env.TERM !== "linux";
10476
+ import process7 from "process";
10477
+ function isUnicodeSupported2() {
10478
+ if (process7.platform !== "win32") {
10479
+ return process7.env.TERM !== "linux";
10166
10480
  }
10167
- return Boolean(process6.env.CI) || Boolean(process6.env.WT_SESSION) || Boolean(process6.env.TERMINUS_SUBLIME) || process6.env.ConEmuTask === "{cmd::Cmder}" || process6.env.TERM_PROGRAM === "Terminus-Sublime" || process6.env.TERM_PROGRAM === "vscode" || process6.env.TERM === "xterm-256color" || process6.env.TERM === "alacritty" || process6.env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
10481
+ return Boolean(process7.env.CI) || Boolean(process7.env.WT_SESSION) || Boolean(process7.env.TERMINUS_SUBLIME) || process7.env.ConEmuTask === "{cmd::Cmder}" || process7.env.TERM_PROGRAM === "Terminus-Sublime" || process7.env.TERM_PROGRAM === "vscode" || process7.env.TERM === "xterm-256color" || process7.env.TERM === "alacritty" || process7.env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
10168
10482
  }
10169
10483
 
10170
10484
  // ../../node_modules/.pnpm/log-symbols@6.0.0/node_modules/log-symbols/index.js
@@ -10180,7 +10494,7 @@ var fallback = {
10180
10494
  warning: source_default.yellow("\u203C"),
10181
10495
  error: source_default.red("\xD7")
10182
10496
  };
10183
- var logSymbols = isUnicodeSupported() ? main : fallback;
10497
+ var logSymbols = isUnicodeSupported2() ? main : fallback;
10184
10498
  var log_symbols_default = logSymbols;
10185
10499
 
10186
10500
  // ../../node_modules/.pnpm/emoji-regex@10.6.0/node_modules/emoji-regex/index.mjs
@@ -10243,17 +10557,6 @@ function isInteractive({ stream = process.stdout } = {}) {
10243
10557
  );
10244
10558
  }
10245
10559
 
10246
- // ../../node_modules/.pnpm/is-unicode-supported@2.1.0/node_modules/is-unicode-supported/index.js
10247
- import process7 from "process";
10248
- function isUnicodeSupported2() {
10249
- const { env: env2 } = process7;
10250
- const { TERM, TERM_PROGRAM } = env2;
10251
- if (process7.platform !== "win32") {
10252
- return TERM !== "linux";
10253
- }
10254
- return Boolean(env2.WT_SESSION) || Boolean(env2.TERMINUS_SUBLIME) || env2.ConEmuTask === "{cmd::Cmder}" || TERM_PROGRAM === "Terminus-Sublime" || TERM_PROGRAM === "vscode" || TERM === "xterm-256color" || TERM === "alacritty" || TERM === "rxvt-unicode" || TERM === "rxvt-unicode-256color" || env2.TERMINAL_EMULATOR === "JetBrains-JediTerm";
10255
- }
10256
-
10257
10560
  // ../../node_modules/.pnpm/stdin-discarder@0.2.2/node_modules/stdin-discarder/index.js
10258
10561
  import process8 from "process";
10259
10562
  var ASCII_ETX_CODE = 3;
@@ -10389,7 +10692,7 @@ var Ora = class {
10389
10692
  throw new Error("The given spinner must have a `frames` property");
10390
10693
  }
10391
10694
  this.#spinner = spinner;
10392
- } else if (!isUnicodeSupported2()) {
10695
+ } else if (!isUnicodeSupported()) {
10393
10696
  this.#spinner = import_cli_spinners.default.line;
10394
10697
  } else if (spinner === void 0) {
10395
10698
  this.#spinner = import_cli_spinners.default.dots;
@@ -10509,386 +10812,242 @@ var Ora = class {
10509
10812
  }
10510
10813
  this.clear();
10511
10814
  this.#stream.write(this.frame());
10512
- this.#linesToClear = this.#lineCount;
10513
- return this;
10514
- }
10515
- start(text) {
10516
- if (text) {
10517
- this.text = text;
10518
- }
10519
- if (this.#isSilent) {
10520
- return this;
10521
- }
10522
- if (!this.#isEnabled) {
10523
- if (this.text) {
10524
- this.#stream.write(`- ${this.text}
10525
- `);
10526
- }
10527
- return this;
10528
- }
10529
- if (this.isSpinning) {
10530
- return this;
10531
- }
10532
- if (this.#options.hideCursor) {
10533
- cli_cursor_default.hide(this.#stream);
10534
- }
10535
- if (this.#options.discardStdin && process9.stdin.isTTY) {
10536
- this.#isDiscardingStdin = true;
10537
- stdin_discarder_default.start();
10538
- }
10539
- this.render();
10540
- this.#id = setInterval(this.render.bind(this), this.interval);
10541
- return this;
10542
- }
10543
- stop() {
10544
- if (!this.#isEnabled) {
10545
- return this;
10546
- }
10547
- clearInterval(this.#id);
10548
- this.#id = void 0;
10549
- this.#frameIndex = 0;
10550
- this.clear();
10551
- if (this.#options.hideCursor) {
10552
- cli_cursor_default.show(this.#stream);
10553
- }
10554
- if (this.#options.discardStdin && process9.stdin.isTTY && this.#isDiscardingStdin) {
10555
- stdin_discarder_default.stop();
10556
- this.#isDiscardingStdin = false;
10557
- }
10558
- return this;
10559
- }
10560
- succeed(text) {
10561
- return this.stopAndPersist({ symbol: log_symbols_default.success, text });
10562
- }
10563
- fail(text) {
10564
- return this.stopAndPersist({ symbol: log_symbols_default.error, text });
10565
- }
10566
- warn(text) {
10567
- return this.stopAndPersist({ symbol: log_symbols_default.warning, text });
10568
- }
10569
- info(text) {
10570
- return this.stopAndPersist({ symbol: log_symbols_default.info, text });
10571
- }
10572
- stopAndPersist(options = {}) {
10573
- if (this.#isSilent) {
10574
- return this;
10575
- }
10576
- const prefixText = options.prefixText ?? this.#prefixText;
10577
- const fullPrefixText = this.#getFullPrefixText(prefixText, " ");
10578
- const symbolText = options.symbol ?? " ";
10579
- const text = options.text ?? this.text;
10580
- const separatorText = symbolText ? " " : "";
10581
- const fullText = typeof text === "string" ? separatorText + text : "";
10582
- const suffixText = options.suffixText ?? this.#suffixText;
10583
- const fullSuffixText = this.#getFullSuffixText(suffixText, " ");
10584
- const textToWrite = fullPrefixText + symbolText + fullText + fullSuffixText + "\n";
10585
- this.stop();
10586
- this.#stream.write(textToWrite);
10587
- return this;
10588
- }
10589
- };
10590
- function ora(options) {
10591
- return new Ora(options);
10592
- }
10593
-
10594
- // src/commands/pentest.ts
10595
- var import_picocolors5 = __toESM(require_picocolors(), 1);
10596
-
10597
- // src/lib/confirm.ts
10598
- var import_picocolors3 = __toESM(require_picocolors(), 1);
10599
- import { createInterface } from "readline";
10600
- async function confirmDestructive(message, yes) {
10601
- if (yes) return;
10602
- if (!isTTY()) {
10603
- console.error("Use --yes to confirm destructive operations in non-interactive mode.");
10604
- process.exit(2);
10605
- }
10606
- const rl = createInterface({ input: process.stdin, output: process.stderr });
10607
- const answer = await new Promise((resolve) => {
10608
- rl.question(`${message} (y/N) `, resolve);
10609
- });
10610
- rl.close();
10611
- if (answer.toLowerCase() !== "y") {
10612
- console.log("Cancelled.");
10613
- process.exit(0);
10614
- }
10615
- }
10616
- async function confirmAuthorization(target, yes) {
10617
- if (yes) return;
10618
- if (!isTTY()) {
10619
- console.error("Use --yes to accept authorization terms in non-interactive mode.");
10620
- process.exit(2);
10621
- }
10622
- const warning = import_picocolors3.default.yellow("\u26A0");
10623
- console.error(`
10624
- ${warning} ${import_picocolors3.default.bold("Authorization Required")}
10625
-
10626
- Penetration testing without proper authorization is illegal
10627
- and may result in criminal prosecution.
10628
-
10629
- ${import_picocolors3.default.bold("Target:")} ${target}
10630
-
10631
- By proceeding, you confirm that:
10632
- ${import_picocolors3.default.dim("\u2022")} You own or have explicit written authorization to test this target
10633
- ${import_picocolors3.default.dim("\u2022")} You will comply with all applicable laws and regulations
10634
- ${import_picocolors3.default.dim("\u2022")} You will not use the results for illegal or malicious purposes
10635
- ${import_picocolors3.default.dim("\u2022")} You accept full legal responsibility for this pentest
10636
-
10637
- ${import_picocolors3.default.dim("Unauthorized access to computer systems is a criminal offense under")}
10638
- ${import_picocolors3.default.dim("laws such as the CFAA (US), Computer Misuse Act (UK), and similar")}
10639
- ${import_picocolors3.default.dim("legislation worldwide. KinoSec bears no liability for unauthorized")}
10640
- ${import_picocolors3.default.dim("use of its services.")}
10641
- `);
10642
- const rl = createInterface({ input: process.stdin, output: process.stderr });
10643
- const answer = await new Promise((resolve) => {
10644
- rl.question("Agree and continue? (y/N) ", resolve);
10645
- });
10646
- rl.close();
10647
- if (answer.toLowerCase() !== "y") {
10648
- console.log("Cancelled.");
10649
- process.exit(0);
10650
- }
10651
- }
10652
-
10653
- // src/commands/schedule.ts
10654
- var import_picocolors4 = __toESM(require_picocolors(), 1);
10655
- var VALID_PATTERNS = ["once", "daily", "weekly", "biweekly", "monthly"];
10656
- var VALID_MODES = ["quick", "full"];
10657
- var DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
10658
- function extractTime(scheduledAt) {
10659
- if (!scheduledAt) return "\u2014";
10660
- const d = new Date(scheduledAt);
10661
- return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", hour12: false });
10662
- }
10663
- function formatPattern(s) {
10664
- const time = extractTime(s.scheduled_at);
10665
- switch (s.recurrence_pattern) {
10666
- case "once":
10667
- return `Once at ${time}`;
10668
- case "daily":
10669
- return `Daily at ${time}`;
10670
- case "weekly":
10671
- return `Weekly ${s.day_of_week !== null ? DAY_NAMES[s.day_of_week] : ""} at ${time}`;
10672
- case "biweekly":
10673
- return `Every 2 weeks ${s.day_of_week !== null ? DAY_NAMES[s.day_of_week] : ""} at ${time}`;
10674
- case "monthly":
10675
- return `Monthly day ${s.day_of_month ?? "?"} at ${time}`;
10676
- default:
10677
- return s.recurrence_pattern;
10678
- }
10679
- }
10680
- function registerScheduleCommand(pentestCmd) {
10681
- const schedule = pentestCmd.command("schedule").description("Manage scheduled pentests");
10682
- schedule.command("list").description("List scheduled pentests").option("--limit <n>", "Max results per page", "20").option("--cursor <cursor>", "Pagination cursor").option("--status <status>", "Filter: active, paused, or all", "all").addHelpText("after", `
10683
- Examples:
10684
- $ kinosec pentest schedule list
10685
- $ kinosec pentest schedule list --status active --json`).action(async (options) => {
10686
- try {
10687
- const opts = schedule.parent.parent.opts();
10688
- const { token, apiUrl } = resolveToken(opts);
10689
- const client = new KinoSecClient({ apiUrl, token });
10690
- const result = await client.schedules.list({
10691
- limit: parseInt(options.limit, 10),
10692
- cursor: options.cursor,
10693
- status: options.status
10694
- });
10695
- if (opts.json || opts.output === "json") {
10696
- printJson(result);
10697
- return;
10698
- }
10699
- if (result.items.length === 0) {
10700
- console.log("No scheduled pentests found.");
10701
- return;
10702
- }
10703
- printTable(
10704
- ["ID", "Target", "Mode", "Schedule", "Status", "Next Run"],
10705
- result.items.map((s) => [
10706
- s.id,
10707
- truncate(s.domain_or_ip, 25),
10708
- s.scan_mode,
10709
- formatPattern(s),
10710
- s.is_active ? import_picocolors4.default.green("active") : import_picocolors4.default.yellow("paused"),
10711
- s.next_scheduled_run ? new Date(s.next_scheduled_run).toLocaleString() : "\u2014"
10712
- ])
10713
- );
10714
- if (result.next_cursor) {
10715
- console.log(import_picocolors4.default.dim(`
10716
- More results available. Use --cursor ${result.next_cursor}`));
10717
- }
10718
- } catch (error) {
10719
- handleError(error);
10720
- }
10721
- });
10722
- schedule.command("get").description("Get schedule details").argument("<id>", "Schedule ID").addHelpText("after", `
10723
- Examples:
10724
- $ kinosec pentest schedule get abc123
10725
- $ kinosec pentest schedule get abc123 --json`).action(async (id) => {
10726
- try {
10727
- const opts = schedule.parent.parent.opts();
10728
- const { token, apiUrl } = resolveToken(opts);
10729
- const client = new KinoSecClient({ apiUrl, token });
10730
- const s = await client.schedules.get(id);
10731
- if (opts.json || opts.output === "json") {
10732
- printJson(s);
10733
- return;
10734
- }
10735
- printTable(
10736
- ["Field", "Value"],
10737
- [
10738
- ["ID", s.id],
10739
- ["Target", s.domain_or_ip],
10740
- ["Name", s.name],
10741
- ["Mode", s.scan_mode],
10742
- ["Schedule", formatPattern(s)],
10743
- ["Timezone", s.timezone],
10744
- ["Status", s.is_active ? "Active" : "Paused"],
10745
- ["Next Run", s.next_scheduled_run ?? "\u2014"],
10746
- ["Last Run", s.last_scheduled_run ?? "Never"],
10747
- ["Created", s.created_at]
10748
- ]
10749
- );
10750
- } catch (error) {
10751
- handleError(error);
10815
+ this.#linesToClear = this.#lineCount;
10816
+ return this;
10817
+ }
10818
+ start(text) {
10819
+ if (text) {
10820
+ this.text = text;
10752
10821
  }
10753
- });
10754
- schedule.command("create").description("Create a scheduled pentest").requiredOption("--asset <id>", "Asset ID to scan").requiredOption("--pattern <pattern>", "Recurrence: once, daily, weekly, biweekly, monthly").option("--name <name>", "Schedule name").option("--mode <mode>", "Scan mode: quick or full", "full").option("--time <HH:MM>", "Time of day (24h format)", "09:00").option("--day-of-week <0-6>", "Day of week (0=Sun..6=Sat) for weekly/biweekly").option("--day-of-month <1-31>", "Day of month for monthly").option("--timezone <tz>", "Timezone (e.g. America/New_York)", Intl.DateTimeFormat().resolvedOptions().timeZone).addHelpText("after", `
10755
- Examples:
10756
- $ kinosec pentest schedule create --asset abc123 --pattern weekly --day-of-week 1 --time 09:00
10757
- $ kinosec pentest schedule create --asset abc123 --pattern daily --mode quick --time 14:00
10758
- $ kinosec pentest schedule create --asset abc123 --pattern monthly --day-of-month 15 --time 03:00 --timezone UTC`).action(async (options) => {
10759
- if (!VALID_PATTERNS.includes(options.pattern)) {
10760
- console.error(`Invalid pattern "${options.pattern}". Must be: ${VALID_PATTERNS.join(", ")}`);
10761
- process.exit(2);
10822
+ if (this.#isSilent) {
10823
+ return this;
10762
10824
  }
10763
- if (!VALID_MODES.includes(options.mode)) {
10764
- console.error(`Invalid mode "${options.mode}". Must be: ${VALID_MODES.join(", ")}`);
10765
- process.exit(2);
10825
+ if (!this.#isEnabled) {
10826
+ if (this.text) {
10827
+ this.#stream.write(`- ${this.text}
10828
+ `);
10829
+ }
10830
+ return this;
10766
10831
  }
10767
- if ((options.pattern === "weekly" || options.pattern === "biweekly") && options.dayOfWeek === void 0) {
10768
- console.error(`--day-of-week is required for ${options.pattern} schedules.`);
10769
- process.exit(2);
10832
+ if (this.isSpinning) {
10833
+ return this;
10770
10834
  }
10771
- if (options.pattern === "monthly" && options.dayOfMonth === void 0) {
10772
- console.error("--day-of-month is required for monthly schedules.");
10773
- process.exit(2);
10835
+ if (this.#options.hideCursor) {
10836
+ cli_cursor_default.hide(this.#stream);
10774
10837
  }
10775
- try {
10776
- const opts = schedule.parent.parent.opts();
10777
- const { token, apiUrl } = resolveToken(opts);
10778
- const client = new KinoSecClient({ apiUrl, token });
10779
- const params = {
10780
- asset_id: options.domain_or_ip,
10781
- name: options.name ?? `${options.pattern} ${options.mode} pentest`,
10782
- scan_mode: options.mode,
10783
- recurrence_pattern: options.pattern,
10784
- time_of_day: options.time,
10785
- timezone: options.timezone
10786
- };
10787
- if (options.dayOfWeek !== void 0) params.day_of_week = parseInt(options.dayOfWeek, 10);
10788
- if (options.dayOfMonth !== void 0) params.day_of_month = parseInt(options.dayOfMonth, 10);
10789
- const s = await client.schedules.create(params);
10790
- if (opts.json || opts.output === "json") {
10791
- printJson(s);
10792
- return;
10793
- }
10794
- printSuccess(`Schedule created: ${s.domain_or_ip}`);
10795
- console.log(` ID: ${s.id}`);
10796
- console.log(` Schedule: ${formatPattern(s)}`);
10797
- console.log(` Next run: ${s.next_scheduled_run ?? "pending"}`);
10798
- console.log(`
10799
- Manage: kinosec pentest schedule get ${s.id}`);
10800
- } catch (error) {
10801
- handleError(error);
10838
+ if (this.#options.discardStdin && process9.stdin.isTTY) {
10839
+ this.#isDiscardingStdin = true;
10840
+ stdin_discarder_default.start();
10802
10841
  }
10803
- });
10804
- schedule.command("update").description("Update a schedule").argument("<id>", "Schedule ID").option("--pattern <pattern>", "Recurrence: once, daily, weekly, biweekly, monthly").option("--name <name>", "Schedule name").option("--mode <mode>", "Scan mode: quick or full").option("--time <HH:MM>", "Time of day (24h format)").option("--day-of-week <0-6>", "Day of week (0=Sun..6=Sat)").option("--day-of-month <1-31>", "Day of month").option("--timezone <tz>", "Timezone").addHelpText("after", `
10805
- Examples:
10806
- $ kinosec pentest schedule update abc123 --pattern daily --time 14:00
10807
- $ kinosec pentest schedule update abc123 --mode quick`).action(async (id, options) => {
10808
- const params = {};
10809
- if (options.pattern) params.recurrence_pattern = options.pattern;
10810
- if (options.name) params.name = options.name;
10811
- if (options.mode) params.scan_mode = options.mode;
10812
- if (options.time) params.time_of_day = options.time;
10813
- if (options.dayOfWeek !== void 0) params.day_of_week = parseInt(options.dayOfWeek, 10);
10814
- if (options.dayOfMonth !== void 0) params.day_of_month = parseInt(options.dayOfMonth, 10);
10815
- if (options.timezone) params.timezone = options.timezone;
10816
- if (Object.keys(params).length === 0) {
10817
- console.error("Provide at least one field to update.");
10818
- process.exit(2);
10842
+ this.render();
10843
+ this.#id = setInterval(this.render.bind(this), this.interval);
10844
+ return this;
10845
+ }
10846
+ stop() {
10847
+ if (!this.#isEnabled) {
10848
+ return this;
10819
10849
  }
10820
- try {
10821
- const opts = schedule.parent.parent.opts();
10822
- const { token, apiUrl } = resolveToken(opts);
10823
- const client = new KinoSecClient({ apiUrl, token });
10824
- const s = await client.schedules.update(id, params);
10825
- if (opts.json || opts.output === "json") {
10826
- printJson(s);
10827
- return;
10828
- }
10829
- printSuccess(`Schedule updated: ${s.domain_or_ip}`);
10830
- console.log(` Schedule: ${formatPattern(s)}`);
10831
- console.log(` Next run: ${s.next_scheduled_run ?? "pending"}`);
10832
- } catch (error) {
10833
- handleError(error);
10850
+ clearInterval(this.#id);
10851
+ this.#id = void 0;
10852
+ this.#frameIndex = 0;
10853
+ this.clear();
10854
+ if (this.#options.hideCursor) {
10855
+ cli_cursor_default.show(this.#stream);
10834
10856
  }
10835
- });
10836
- schedule.command("delete").description("Delete a schedule").argument("<id>", "Schedule ID").option("-y, --yes", "Skip confirmation prompt").addHelpText("after", `
10837
- Examples:
10838
- $ kinosec pentest schedule delete abc123
10839
- $ kinosec pentest schedule delete abc123 --yes`).action(async (id, options) => {
10840
- try {
10841
- await confirmDestructive(`Delete schedule ${id}?`, Boolean(options.yes));
10842
- const opts = schedule.parent.parent.opts();
10843
- const { token, apiUrl } = resolveToken(opts);
10844
- const client = new KinoSecClient({ apiUrl, token });
10845
- await client.schedules.delete(id);
10846
- if (opts.json || opts.output === "json") {
10847
- printJson({ deleted: true, id });
10848
- return;
10849
- }
10850
- printSuccess(`Schedule ${id} deleted.`);
10851
- } catch (error) {
10852
- handleError(error);
10857
+ if (this.#options.discardStdin && process9.stdin.isTTY && this.#isDiscardingStdin) {
10858
+ stdin_discarder_default.stop();
10859
+ this.#isDiscardingStdin = false;
10853
10860
  }
10854
- });
10855
- schedule.command("pause").description("Pause a scheduled pentest").argument("<id>", "Schedule ID").addHelpText("after", `
10856
- Examples:
10857
- $ kinosec pentest schedule pause abc123`).action(async (id) => {
10858
- try {
10859
- const opts = schedule.parent.parent.opts();
10860
- const { token, apiUrl } = resolveToken(opts);
10861
- const client = new KinoSecClient({ apiUrl, token });
10862
- const result = await client.schedules.pause(id);
10863
- if (opts.json || opts.output === "json") {
10864
- printJson(result);
10865
- return;
10866
- }
10867
- printSuccess("Schedule paused.");
10868
- } catch (error) {
10869
- handleError(error);
10861
+ return this;
10862
+ }
10863
+ succeed(text) {
10864
+ return this.stopAndPersist({ symbol: log_symbols_default.success, text });
10865
+ }
10866
+ fail(text) {
10867
+ return this.stopAndPersist({ symbol: log_symbols_default.error, text });
10868
+ }
10869
+ warn(text) {
10870
+ return this.stopAndPersist({ symbol: log_symbols_default.warning, text });
10871
+ }
10872
+ info(text) {
10873
+ return this.stopAndPersist({ symbol: log_symbols_default.info, text });
10874
+ }
10875
+ stopAndPersist(options = {}) {
10876
+ if (this.#isSilent) {
10877
+ return this;
10870
10878
  }
10879
+ const prefixText = options.prefixText ?? this.#prefixText;
10880
+ const fullPrefixText = this.#getFullPrefixText(prefixText, " ");
10881
+ const symbolText = options.symbol ?? " ";
10882
+ const text = options.text ?? this.text;
10883
+ const separatorText = symbolText ? " " : "";
10884
+ const fullText = typeof text === "string" ? separatorText + text : "";
10885
+ const suffixText = options.suffixText ?? this.#suffixText;
10886
+ const fullSuffixText = this.#getFullSuffixText(suffixText, " ");
10887
+ const textToWrite = fullPrefixText + symbolText + fullText + fullSuffixText + "\n";
10888
+ this.stop();
10889
+ this.#stream.write(textToWrite);
10890
+ return this;
10891
+ }
10892
+ };
10893
+ function ora(options) {
10894
+ return new Ora(options);
10895
+ }
10896
+
10897
+ // src/lib/watch.ts
10898
+ var UNICODE = isUnicodeSupported();
10899
+ var sym = {
10900
+ success: UNICODE ? "\u2713" : "v",
10901
+ fail: UNICODE ? "\u2717" : "x",
10902
+ warn: UNICODE ? "\u26A0" : "!",
10903
+ rule: UNICODE ? "\u2500" : "-"
10904
+ };
10905
+ var LABEL_WIDTH = 40;
10906
+ var TIMING_WIDTH = 8;
10907
+ function formatDuration(ms) {
10908
+ if (ms < 1e4) return `${(ms / 1e3).toFixed(1)}s`;
10909
+ if (ms < 6e4) return `${Math.round(ms / 1e3)}s`;
10910
+ const totalSec = Math.round(ms / 1e3);
10911
+ const m = Math.floor(totalSec / 60);
10912
+ const r = totalSec % 60;
10913
+ return `${m}m ${r.toString().padStart(2, "0")}s`;
10914
+ }
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
+ function renderFindingsSummary(counts) {
10924
+ const levels = [
10925
+ { key: "critical", label: "critical", color: import_picocolors5.default.red },
10926
+ { key: "high", label: "high", color: import_picocolors5.default.yellow },
10927
+ { key: "medium", label: "medium", color: (s) => import_picocolors5.default.dim(import_picocolors5.default.yellow(s)) },
10928
+ { key: "low", label: "low", color: import_picocolors5.default.green },
10929
+ { key: "info", label: "info", color: import_picocolors5.default.cyan }
10930
+ ];
10931
+ const tty2 = isTTY();
10932
+ const parts = levels.map(({ key, label, color }) => {
10933
+ const count = findCount(counts, key);
10934
+ const text = `${count} ${label}`;
10935
+ if (!tty2) return text;
10936
+ return count === 0 ? import_picocolors5.default.dim(text) : color(text);
10871
10937
  });
10872
- schedule.command("resume").description("Resume a paused schedule").argument("<id>", "Schedule ID").addHelpText("after", `
10873
- Examples:
10874
- $ kinosec pentest schedule resume abc123`).action(async (id) => {
10875
- try {
10876
- const opts = schedule.parent.parent.opts();
10877
- const { token, apiUrl } = resolveToken(opts);
10878
- const client = new KinoSecClient({ apiUrl, token });
10879
- const result = await client.schedules.resume(id);
10880
- if (opts.json || opts.output === "json") {
10881
- printJson(result);
10882
- return;
10938
+ return parts.join(" ");
10939
+ }
10940
+ function findCount(counts, level) {
10941
+ const key = Object.keys(counts).find((k) => k.toLowerCase() === level);
10942
+ return key ? counts[key] : 0;
10943
+ }
10944
+ async function watchPentest(client, profileId, options) {
10945
+ const { timeoutMs, intervalMs, useJson, showFindingsSummary } = options;
10946
+ const tty2 = isTTY();
10947
+ const loopStart = Date.now();
10948
+ let spinner = null;
10949
+ let lastActivity = "";
10950
+ let phaseStart = Date.now();
10951
+ if (!useJson && tty2) {
10952
+ spinner = ora({ text: "Fetching pentest status...", isEnabled: true }).start();
10953
+ }
10954
+ const cleanup = () => {
10955
+ if (spinner) spinner.stop();
10956
+ };
10957
+ process.once("SIGINT", () => {
10958
+ cleanup();
10959
+ console.log("\nWatch cancelled. Pentest is still running.");
10960
+ process.exit(0);
10961
+ });
10962
+ while (true) {
10963
+ const scanDetail = await client.scans.get(profileId);
10964
+ const activity = scanDetail.current_activity || "";
10965
+ const totalMs = Date.now() - loopStart;
10966
+ const currentPhaseMs = Date.now() - phaseStart;
10967
+ if (useJson) {
10968
+ const elapsed = Math.round(totalMs / 1e3);
10969
+ process.stdout.write(
10970
+ JSON.stringify({ ...scanDetail, elapsed_seconds: elapsed }) + "\n"
10971
+ );
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;
10993
+ }
10994
+ const terminalStatuses = ["Completed", "Failed", "Stopped"];
10995
+ if (terminalStatuses.some((s) => scanDetail.scan_status.includes(s))) {
10996
+ cleanup();
10997
+ if (useJson) break;
10998
+ if (scanDetail.scan_status.includes("Failed")) {
10999
+ const icon2 = tty2 ? import_picocolors5.default.red(sym.fail) : sym.fail;
11000
+ const reason = scanDetail.failure_reason || "unknown reason";
11001
+ console.log(`
11002
+ ${icon2} Pentest failed: ${reason}`);
11003
+ process.exit(1);
11004
+ }
11005
+ if (scanDetail.scan_status.includes("Stopped")) {
11006
+ const icon2 = tty2 ? import_picocolors5.default.yellow(sym.warn) : sym.warn;
11007
+ console.log(`
11008
+ ${icon2} Pentest was cancelled.`);
11009
+ break;
10883
11010
  }
10884
- printSuccess("Schedule resumed.");
10885
- if (result.next_scheduled_run) {
10886
- console.log(` Next run: ${new Date(result.next_scheduled_run).toLocaleString()}`);
11011
+ const totalDur = formatDuration(Date.now() - loopStart);
11012
+ const domain = tty2 ? import_picocolors5.default.bold(scanDetail.domain_or_ip) : scanDetail.domain_or_ip;
11013
+ const icon = tty2 ? import_picocolors5.default.green(sym.success) : sym.success;
11014
+ const dur = tty2 ? import_picocolors5.default.dim(totalDur) : totalDur;
11015
+ console.log(`
11016
+ ${icon} Pentest completed: ${domain} ${dur}`);
11017
+ if (showFindingsSummary) {
11018
+ await printFindingsSummary(client, profileId, tty2);
10887
11019
  }
10888
- } catch (error) {
10889
- handleError(error);
11020
+ const cmd = `kinosec finding list ${profileId}`;
11021
+ const viewLabel = tty2 ? import_picocolors5.default.dim("View details:") : "View details:";
11022
+ const viewCmd = tty2 ? import_picocolors5.default.cyan(cmd) : cmd;
11023
+ console.log(`
11024
+ ${viewLabel} ${viewCmd}`);
11025
+ console.log();
11026
+ break;
10890
11027
  }
10891
- });
11028
+ if (Date.now() - loopStart > timeoutMs) {
11029
+ cleanup();
11030
+ const secs = Math.round(timeoutMs / 1e3);
11031
+ console.error(`
11032
+ Timeout: pentest still running after ${secs}s.`);
11033
+ console.error(`Check status: kinosec pentest get ${profileId}`);
11034
+ process.exit(3);
11035
+ }
11036
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
11037
+ }
11038
+ }
11039
+ async function printFindingsSummary(client, profileId, tty2) {
11040
+ try {
11041
+ const reports = await client.reports.list({ limit: 20 });
11042
+ const report = reports.items.find((r) => r.scan_profile_id === profileId);
11043
+ if (report?.severity_counts) {
11044
+ const label = tty2 ? import_picocolors5.default.dim("Findings:") : "Findings:";
11045
+ const summary = renderFindingsSummary(report.severity_counts);
11046
+ console.log(`
11047
+ ${label} ${summary}`);
11048
+ }
11049
+ } catch {
11050
+ }
10892
11051
  }
10893
11052
 
10894
11053
  // src/commands/pentest.ts
@@ -10901,7 +11060,7 @@ function isValidDomain(value) {
10901
11060
  return value.includes(".") && !/\s/.test(value);
10902
11061
  }
10903
11062
  function registerPentestCommand(program3) {
10904
- const pentest = program3.command("pentest").description("Run and manage pentests").option("-t, --target <domain>", "Target domain to pentest").option("--profile <id>", "Pentest profile ID (alternative to --target)").option("--mode <mode>", "Pentest mode: quick or full", "full").option("-y, --yes", "Accept authorization terms without prompting").addHelpText("after", `
11063
+ const pentest = program3.command("pentest").description("Run and manage pentests").option("-t, --target <domain>", "Target domain to pentest").option("--profile <id>", "Pentest profile ID (alternative to --target)").option("--mode <mode>", "Pentest mode: quick or full", "full").option("-y, --yes", "Accept authorization terms without prompting").option("--no-watch", "Skip auto-watch after starting the pentest").addHelpText("after", `
10905
11064
  Examples:
10906
11065
  $ kinosec pentest -t delikado.org
10907
11066
  $ kinosec pentest --target delikado.org --mode quick
@@ -10957,9 +11116,22 @@ Examples:
10957
11116
  printSuccess(`Pentest started: ${result.domain_or_ip}`);
10958
11117
  console.log(` Profile ID: ${result.scan_profile_id}`);
10959
11118
  console.log(` Mode: ${result.scan_mode}`);
10960
- console.log(` Status: ${result.scan_status}`);
10961
- console.log(`
11119
+ if (!options.watch || !isTTY()) {
11120
+ console.log(` Status: ${result.scan_status}`);
11121
+ console.log(`
10962
11122
  Watch progress: kinosec pentest watch ${result.scan_profile_id}`);
11123
+ return;
11124
+ }
11125
+ const divider = isTTY() ? import_picocolors6.default.dim("\u2500".repeat(37)) : "-".repeat(37);
11126
+ console.log(`
11127
+ ${divider}
11128
+ `);
11129
+ await watchPentest(client, result.scan_profile_id, {
11130
+ timeoutMs: 1800 * 1e3,
11131
+ intervalMs: 5 * 1e3,
11132
+ useJson: false,
11133
+ showFindingsSummary: true
11134
+ });
10963
11135
  } catch (error) {
10964
11136
  handleError(error);
10965
11137
  }
@@ -10996,7 +11168,7 @@ Examples:
10996
11168
  ])
10997
11169
  );
10998
11170
  if (result.next_cursor) {
10999
- console.log(import_picocolors5.default.dim(`
11171
+ console.log(import_picocolors6.default.dim(`
11000
11172
  More results available. Use --cursor ${result.next_cursor}`));
11001
11173
  }
11002
11174
  } catch (error) {
@@ -11059,67 +11231,12 @@ Examples:
11059
11231
  const opts = program3.opts();
11060
11232
  const { token, apiUrl } = resolveToken(opts);
11061
11233
  const client = new KinoSecClient({ apiUrl, token });
11062
- const useJson = opts.json || opts.output === "json";
11063
- const timeoutMs = parseInt(options.timeout, 10) * 1e3;
11064
- const intervalMs = parseInt(options.interval, 10) * 1e3;
11065
- const startTime = Date.now();
11066
- let spinner = null;
11067
- let lastActivity = "";
11068
- if (!useJson && isTTY()) {
11069
- spinner = ora("Fetching pentest status...").start();
11070
- }
11071
- const cleanup = () => {
11072
- if (spinner) spinner.stop();
11073
- };
11074
- process.once("SIGINT", () => {
11075
- cleanup();
11076
- console.log("\nWatch cancelled. Pentest is still running.");
11077
- process.exit(0);
11234
+ await watchPentest(client, id, {
11235
+ timeoutMs: parseInt(options.timeout, 10) * 1e3,
11236
+ intervalMs: parseInt(options.interval, 10) * 1e3,
11237
+ useJson: Boolean(opts.json || opts.output === "json"),
11238
+ showFindingsSummary: true
11078
11239
  });
11079
- while (true) {
11080
- const scanDetail = await client.scans.get(id);
11081
- const elapsed = Math.round((Date.now() - startTime) / 1e3);
11082
- const activity = scanDetail.current_activity || "";
11083
- if (useJson) {
11084
- process.stdout.write(JSON.stringify({ ...scanDetail, elapsed_seconds: elapsed }) + "\n");
11085
- } else if (spinner) {
11086
- if (activity && activity !== lastActivity && lastActivity) {
11087
- spinner.stopAndPersist({ symbol: import_picocolors5.default.green("\u2713"), text: lastActivity });
11088
- spinner = ora().start();
11089
- }
11090
- lastActivity = activity || lastActivity;
11091
- const activitySuffix = activity ? ` \u2014 ${activity}` : "";
11092
- spinner.text = `${scanDetail.domain_or_ip}${activitySuffix} \u2014 ${elapsed}s elapsed`;
11093
- }
11094
- const terminalStatuses = ["Completed", "Failed", "Stopped"];
11095
- if (terminalStatuses.some((s) => scanDetail.scan_status.includes(s))) {
11096
- cleanup();
11097
- if (useJson) {
11098
- break;
11099
- }
11100
- if (scanDetail.scan_status.includes("Failed")) {
11101
- console.log(import_picocolors5.default.red(`
11102
- Pentest failed: ${scanDetail.failure_reason || "unknown reason"}`));
11103
- process.exit(1);
11104
- }
11105
- if (scanDetail.scan_status.includes("Stopped")) {
11106
- console.log(import_picocolors5.default.yellow("\nPentest was cancelled."));
11107
- break;
11108
- }
11109
- console.log(import_picocolors5.default.green(`
11110
- Pentest completed: ${scanDetail.domain_or_ip}`));
11111
- console.log(` View findings: kinosec finding list ${id}`);
11112
- break;
11113
- }
11114
- if (Date.now() - startTime > timeoutMs) {
11115
- cleanup();
11116
- console.error(`
11117
- Timeout: pentest still running after ${options.timeout}s.`);
11118
- console.error(`Check status: kinosec pentest get ${id}`);
11119
- process.exit(3);
11120
- }
11121
- await new Promise((resolve) => setTimeout(resolve, intervalMs));
11122
- }
11123
11240
  } catch (error) {
11124
11241
  handleError(error);
11125
11242
  }
@@ -11127,10 +11244,10 @@ Timeout: pentest still running after ${options.timeout}s.`);
11127
11244
  }
11128
11245
 
11129
11246
  // src/commands/asset.ts
11130
- var import_picocolors7 = __toESM(require_picocolors(), 1);
11247
+ var import_picocolors8 = __toESM(require_picocolors(), 1);
11131
11248
 
11132
11249
  // src/commands/asset-auth.ts
11133
- var import_picocolors6 = __toESM(require_picocolors(), 1);
11250
+ var import_picocolors7 = __toESM(require_picocolors(), 1);
11134
11251
 
11135
11252
  // src/lib/resolve-asset.ts
11136
11253
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-/i;
@@ -11387,7 +11504,7 @@ Examples:
11387
11504
  }
11388
11505
  if (!creds.has_credentials) {
11389
11506
  console.log(`Asset: ${asset.asset_value}`);
11390
- console.log(`Auth Type: ${import_picocolors6.default.dim("(none)")}`);
11507
+ console.log(`Auth Type: ${import_picocolors7.default.dim("(none)")}`);
11391
11508
  console.log(`Has Credentials: no`);
11392
11509
  return;
11393
11510
  }
@@ -11408,9 +11525,9 @@ Examples:
11408
11525
  const hasAny = ta.has_username || ta.has_cookie || ta.has_bearer_token || ta.has_apikey;
11409
11526
  if (hasAny) {
11410
11527
  const detail = ta.has_username ? "username" : ta.has_bearer_token ? "bearer token" : ta.has_cookie ? "cookie" : "API key";
11411
- console.log(`Target Account: ${import_picocolors6.default.green("configured")} (${detail})`);
11528
+ console.log(`Target Account: ${import_picocolors7.default.green("configured")} (${detail})`);
11412
11529
  } else {
11413
- console.log(`Target Account: ${import_picocolors6.default.dim("not configured")}`);
11530
+ console.log(`Target Account: ${import_picocolors7.default.dim("not configured")}`);
11414
11531
  }
11415
11532
  }
11416
11533
  } catch (error) {
@@ -11561,7 +11678,7 @@ Examples:
11561
11678
  ])
11562
11679
  );
11563
11680
  if (result.next_cursor) {
11564
- console.log(import_picocolors7.default.dim(`
11681
+ console.log(import_picocolors8.default.dim(`
11565
11682
  More results available. Use --cursor ${result.next_cursor}`));
11566
11683
  }
11567
11684
  } catch (error) {
@@ -11673,7 +11790,7 @@ Examples:
11673
11790
  }
11674
11791
 
11675
11792
  // src/commands/finding.ts
11676
- var import_picocolors8 = __toESM(require_picocolors(), 1);
11793
+ var import_picocolors9 = __toESM(require_picocolors(), 1);
11677
11794
  function formatPoc(poc) {
11678
11795
  if (poc == null) return null;
11679
11796
  if (typeof poc === "string") return poc.trim() || null;
@@ -11714,7 +11831,7 @@ Examples:
11714
11831
  ])
11715
11832
  );
11716
11833
  if (result.next_cursor) {
11717
- console.log(import_picocolors8.default.dim(`
11834
+ console.log(import_picocolors9.default.dim(`
11718
11835
  More results available. Use --cursor ${result.next_cursor}`));
11719
11836
  }
11720
11837
  } catch (error) {
@@ -11802,7 +11919,7 @@ Examples:
11802
11919
  }
11803
11920
 
11804
11921
  // src/commands/report.ts
11805
- var import_picocolors9 = __toESM(require_picocolors(), 1);
11922
+ var import_picocolors10 = __toESM(require_picocolors(), 1);
11806
11923
  function renderMarkdown(data) {
11807
11924
  const { scan, findings } = data;
11808
11925
  const lines = [];
@@ -11879,7 +11996,7 @@ Examples:
11879
11996
  ])
11880
11997
  );
11881
11998
  if (result.next_cursor) {
11882
- console.log(import_picocolors9.default.dim(`
11999
+ console.log(import_picocolors10.default.dim(`
11883
12000
  More results available. Use --cursor ${result.next_cursor}`));
11884
12001
  }
11885
12002
  } catch (error) {
@@ -11950,7 +12067,7 @@ Examples:
11950
12067
  }
11951
12068
 
11952
12069
  // src/commands/token.ts
11953
- var import_picocolors10 = __toESM(require_picocolors(), 1);
12070
+ var import_picocolors11 = __toESM(require_picocolors(), 1);
11954
12071
  function registerTokenCommand(program3) {
11955
12072
  const token = program3.command("token").description("Manage API tokens");
11956
12073
  token.command("list").description("List API tokens").option("--limit <n>", "Max results per page", "20").option("--cursor <cursor>", "Pagination cursor").addHelpText("after", `
@@ -11985,7 +12102,7 @@ Examples:
11985
12102
  ])
11986
12103
  );
11987
12104
  if (result.next_cursor) {
11988
- console.log(import_picocolors10.default.dim(`
12105
+ console.log(import_picocolors11.default.dim(`
11989
12106
  More results available. Use --cursor ${result.next_cursor}`));
11990
12107
  }
11991
12108
  } catch (error) {
@@ -12012,7 +12129,7 @@ Examples:
12012
12129
  }
12013
12130
  printSuccess(`Token created: ${result.name}`);
12014
12131
  console.log("");
12015
- console.log(import_picocolors10.default.bold(" Token: ") + result.token);
12132
+ console.log(import_picocolors11.default.bold(" Token: ") + result.token);
12016
12133
  console.log("");
12017
12134
  printWarning("Save this token \u2014 it will not be shown again.");
12018
12135
  console.log(` ID: ${result.id}`);
@@ -12045,7 +12162,7 @@ Examples:
12045
12162
 
12046
12163
  // src/cli.ts
12047
12164
  var program2 = new Command();
12048
- program2.name("kinosec").description("KinoSec CLI \u2014 run pentests from your terminal").version("0.4.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");
12165
+ program2.name("kinosec").description("KinoSec CLI \u2014 run pentests from your terminal").version("0.6.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");
12049
12166
  registerAuthCommand(program2);
12050
12167
  registerConfigCommand(program2);
12051
12168
  registerMeCommand(program2);
@@ -12054,7 +12171,7 @@ registerAssetCommand(program2);
12054
12171
  registerFindingCommand(program2);
12055
12172
  registerReportCommand(program2);
12056
12173
  registerTokenCommand(program2);
12057
- program2.parseAsync(process.argv).then(() => checkForUpdate("0.4.0")).catch(handleError);
12174
+ program2.parseAsync(process.argv).then(() => checkForUpdate("0.6.0")).catch(handleError);
12058
12175
  /*! Bundled license information:
12059
12176
 
12060
12177
  ky/distribution/index.js: