@kinosec/cli 0.3.3 → 0.5.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.
Files changed (3) hide show
  1. package/dist/cli.js +1376 -835
  2. package/dist/cli.js.map +1 -1
  3. package/package.json +2 -1
package/dist/cli.js CHANGED
@@ -7750,6 +7750,27 @@ function parseApiError(body, status) {
7750
7750
  requestId: typeof b.request_id === "string" ? b.request_id : "unknown"
7751
7751
  });
7752
7752
  }
7753
+ var CredentialsResource = class {
7754
+ constructor(api) {
7755
+ this.api = api;
7756
+ }
7757
+ api;
7758
+ async set(assetId, params) {
7759
+ return this.api.put(`v1/assets/${assetId}/credentials`, { json: params }).json();
7760
+ }
7761
+ async get(assetId) {
7762
+ return this.api.get(`v1/assets/${assetId}/credentials`).json();
7763
+ }
7764
+ async remove(assetId) {
7765
+ return this.api.delete(`v1/assets/${assetId}/credentials`).json();
7766
+ }
7767
+ async setTarget(assetId, params) {
7768
+ return this.api.put(`v1/assets/${assetId}/credentials/target`, { json: params }).json();
7769
+ }
7770
+ async removeTarget(assetId) {
7771
+ return this.api.delete(`v1/assets/${assetId}/credentials/target`).json();
7772
+ }
7773
+ };
7753
7774
  var IDEMPOTENT_EXACT_PATHS = ["/v1/scans", "/v1/tokens"];
7754
7775
  var IDEMPOTENT_SUFFIXES = ["/cancel", "/retest"];
7755
7776
  function needsIdempotencyKey(method, path) {
@@ -7831,8 +7852,10 @@ var KinoSecClient = class {
7831
7852
  var AssetsResource = class {
7832
7853
  constructor(api) {
7833
7854
  this.api = api;
7855
+ this.credentials = new CredentialsResource(api);
7834
7856
  }
7835
7857
  api;
7858
+ credentials;
7836
7859
  async list(params = {}) {
7837
7860
  return this.api.get("v1/assets", { searchParams: buildSearchParams(params) }).json();
7838
7861
  }
@@ -9260,238 +9283,552 @@ Examples:
9260
9283
  });
9261
9284
  }
9262
9285
 
9263
- // ../../node_modules/.pnpm/ora@8.2.0/node_modules/ora/index.js
9264
- import process9 from "process";
9286
+ // src/commands/pentest.ts
9287
+ var import_picocolors6 = __toESM(require_picocolors(), 1);
9265
9288
 
9266
- // ../../node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/vendor/ansi-styles/index.js
9267
- var ANSI_BACKGROUND_OFFSET2 = 10;
9268
- var wrapAnsi162 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
9269
- var wrapAnsi2562 = (offset = 0) => (code) => `\x1B[${38 + offset};5;${code}m`;
9270
- var wrapAnsi16m2 = (offset = 0) => (red, green, blue) => `\x1B[${38 + offset};2;${red};${green};${blue}m`;
9271
- var styles2 = {
9272
- modifier: {
9273
- reset: [0, 0],
9274
- // 21 isn't widely supported and 22 does the same thing
9275
- bold: [1, 22],
9276
- dim: [2, 22],
9277
- italic: [3, 23],
9278
- underline: [4, 24],
9279
- overline: [53, 55],
9280
- inverse: [7, 27],
9281
- hidden: [8, 28],
9282
- strikethrough: [9, 29]
9283
- },
9284
- color: {
9285
- black: [30, 39],
9286
- red: [31, 39],
9287
- green: [32, 39],
9288
- yellow: [33, 39],
9289
- blue: [34, 39],
9290
- magenta: [35, 39],
9291
- cyan: [36, 39],
9292
- white: [37, 39],
9293
- // Bright color
9294
- blackBright: [90, 39],
9295
- gray: [90, 39],
9296
- // Alias of `blackBright`
9297
- grey: [90, 39],
9298
- // Alias of `blackBright`
9299
- redBright: [91, 39],
9300
- greenBright: [92, 39],
9301
- yellowBright: [93, 39],
9302
- blueBright: [94, 39],
9303
- magentaBright: [95, 39],
9304
- cyanBright: [96, 39],
9305
- whiteBright: [97, 39]
9306
- },
9307
- bgColor: {
9308
- bgBlack: [40, 49],
9309
- bgRed: [41, 49],
9310
- bgGreen: [42, 49],
9311
- bgYellow: [43, 49],
9312
- bgBlue: [44, 49],
9313
- bgMagenta: [45, 49],
9314
- bgCyan: [46, 49],
9315
- bgWhite: [47, 49],
9316
- // Bright color
9317
- bgBlackBright: [100, 49],
9318
- bgGray: [100, 49],
9319
- // Alias of `bgBlackBright`
9320
- bgGrey: [100, 49],
9321
- // Alias of `bgBlackBright`
9322
- bgRedBright: [101, 49],
9323
- bgGreenBright: [102, 49],
9324
- bgYellowBright: [103, 49],
9325
- bgBlueBright: [104, 49],
9326
- bgMagentaBright: [105, 49],
9327
- bgCyanBright: [106, 49],
9328
- bgWhiteBright: [107, 49]
9329
- }
9330
- };
9331
- var modifierNames2 = Object.keys(styles2.modifier);
9332
- var foregroundColorNames2 = Object.keys(styles2.color);
9333
- var backgroundColorNames2 = Object.keys(styles2.bgColor);
9334
- var colorNames2 = [...foregroundColorNames2, ...backgroundColorNames2];
9335
- function assembleStyles2() {
9336
- const codes = /* @__PURE__ */ new Map();
9337
- for (const [groupName, group] of Object.entries(styles2)) {
9338
- for (const [styleName, style] of Object.entries(group)) {
9339
- styles2[styleName] = {
9340
- open: `\x1B[${style[0]}m`,
9341
- close: `\x1B[${style[1]}m`
9342
- };
9343
- group[styleName] = styles2[styleName];
9344
- codes.set(style[0], style[1]);
9345
- }
9346
- Object.defineProperty(styles2, groupName, {
9347
- value: group,
9348
- enumerable: false
9349
- });
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);
9350
9297
  }
9351
- Object.defineProperty(styles2, "codes", {
9352
- value: codes,
9353
- enumerable: false
9354
- });
9355
- styles2.color.close = "\x1B[39m";
9356
- styles2.bgColor.close = "\x1B[49m";
9357
- styles2.color.ansi = wrapAnsi162();
9358
- styles2.color.ansi256 = wrapAnsi2562();
9359
- styles2.color.ansi16m = wrapAnsi16m2();
9360
- styles2.bgColor.ansi = wrapAnsi162(ANSI_BACKGROUND_OFFSET2);
9361
- styles2.bgColor.ansi256 = wrapAnsi2562(ANSI_BACKGROUND_OFFSET2);
9362
- styles2.bgColor.ansi16m = wrapAnsi16m2(ANSI_BACKGROUND_OFFSET2);
9363
- Object.defineProperties(styles2, {
9364
- rgbToAnsi256: {
9365
- value(red, green, blue) {
9366
- if (red === green && green === blue) {
9367
- if (red < 8) {
9368
- return 16;
9369
- }
9370
- if (red > 248) {
9371
- return 231;
9372
- }
9373
- return Math.round((red - 8) / 247 * 24) + 232;
9374
- }
9375
- return 16 + 36 * Math.round(red / 255 * 5) + 6 * Math.round(green / 255 * 5) + Math.round(blue / 255 * 5);
9376
- },
9377
- enumerable: false
9378
- },
9379
- hexToRgb: {
9380
- value(hex) {
9381
- const matches = /[a-f\d]{6}|[a-f\d]{3}/i.exec(hex.toString(16));
9382
- if (!matches) {
9383
- return [0, 0, 0];
9384
- }
9385
- let [colorString] = matches;
9386
- if (colorString.length === 3) {
9387
- colorString = [...colorString].map((character) => character + character).join("");
9388
- }
9389
- const integer = Number.parseInt(colorString, 16);
9390
- return [
9391
- /* eslint-disable no-bitwise */
9392
- integer >> 16 & 255,
9393
- integer >> 8 & 255,
9394
- integer & 255
9395
- /* eslint-enable no-bitwise */
9396
- ];
9397
- },
9398
- enumerable: false
9399
- },
9400
- hexToAnsi256: {
9401
- value: (hex) => styles2.rgbToAnsi256(...styles2.hexToRgb(hex)),
9402
- enumerable: false
9403
- },
9404
- ansi256ToAnsi: {
9405
- value(code) {
9406
- if (code < 8) {
9407
- return 30 + code;
9408
- }
9409
- if (code < 16) {
9410
- return 90 + (code - 8);
9411
- }
9412
- let red;
9413
- let green;
9414
- let blue;
9415
- if (code >= 232) {
9416
- red = ((code - 232) * 10 + 8) / 255;
9417
- green = red;
9418
- blue = red;
9419
- } else {
9420
- code -= 16;
9421
- const remainder = code % 36;
9422
- red = Math.floor(code / 36) / 5;
9423
- green = Math.floor(remainder / 6) / 5;
9424
- blue = remainder % 6 / 5;
9425
- }
9426
- const value = Math.max(red, green, blue) * 2;
9427
- if (value === 0) {
9428
- return 30;
9429
- }
9430
- let result = 30 + (Math.round(blue) << 2 | Math.round(green) << 1 | Math.round(red));
9431
- if (value === 2) {
9432
- result += 60;
9433
- }
9434
- return result;
9435
- },
9436
- enumerable: false
9437
- },
9438
- rgbToAnsi: {
9439
- value: (red, green, blue) => styles2.ansi256ToAnsi(styles2.rgbToAnsi256(red, green, blue)),
9440
- enumerable: false
9441
- },
9442
- hexToAnsi: {
9443
- value: (hex) => styles2.ansi256ToAnsi(styles2.hexToAnsi256(hex)),
9444
- enumerable: false
9445
- }
9298
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
9299
+ const answer = await new Promise((resolve) => {
9300
+ rl.question(`${message} (y/N) `, resolve);
9446
9301
  });
9447
- return styles2;
9448
- }
9449
- var ansiStyles2 = assembleStyles2();
9450
- var ansi_styles_default2 = ansiStyles2;
9451
-
9452
- // ../../node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/vendor/supports-color/index.js
9453
- import process2 from "process";
9454
- import os from "os";
9455
- import tty from "tty";
9456
- function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process2.argv) {
9457
- const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
9458
- const position = argv.indexOf(prefix + flag);
9459
- const terminatorPosition = argv.indexOf("--");
9460
- return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
9461
- }
9462
- var { env } = process2;
9463
- var flagForceColor;
9464
- if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
9465
- flagForceColor = 0;
9466
- } else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
9467
- flagForceColor = 1;
9468
- }
9469
- function envForceColor() {
9470
- if ("FORCE_COLOR" in env) {
9471
- if (env.FORCE_COLOR === "true") {
9472
- return 1;
9473
- }
9474
- if (env.FORCE_COLOR === "false") {
9475
- return 0;
9476
- }
9477
- return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
9302
+ rl.close();
9303
+ if (answer.toLowerCase() !== "y") {
9304
+ console.log("Cancelled.");
9305
+ process.exit(0);
9478
9306
  }
9479
9307
  }
9480
- function translateLevel(level) {
9481
- if (level === 0) {
9482
- return false;
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);
9483
9313
  }
9484
- return {
9485
- level,
9486
- hasBasic: true,
9487
- has256: level >= 2,
9488
- has16m: level >= 3
9489
- };
9490
- }
9491
- function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
9492
- const noFlagForceColor = envForceColor();
9493
- if (noFlagForceColor !== void 0) {
9494
- flagForceColor = noFlagForceColor;
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
+
9600
+ // ../../node_modules/.pnpm/ora@8.2.0/node_modules/ora/index.js
9601
+ import process9 from "process";
9602
+
9603
+ // ../../node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/vendor/ansi-styles/index.js
9604
+ var ANSI_BACKGROUND_OFFSET2 = 10;
9605
+ var wrapAnsi162 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
9606
+ var wrapAnsi2562 = (offset = 0) => (code) => `\x1B[${38 + offset};5;${code}m`;
9607
+ var wrapAnsi16m2 = (offset = 0) => (red, green, blue) => `\x1B[${38 + offset};2;${red};${green};${blue}m`;
9608
+ var styles2 = {
9609
+ modifier: {
9610
+ reset: [0, 0],
9611
+ // 21 isn't widely supported and 22 does the same thing
9612
+ bold: [1, 22],
9613
+ dim: [2, 22],
9614
+ italic: [3, 23],
9615
+ underline: [4, 24],
9616
+ overline: [53, 55],
9617
+ inverse: [7, 27],
9618
+ hidden: [8, 28],
9619
+ strikethrough: [9, 29]
9620
+ },
9621
+ color: {
9622
+ black: [30, 39],
9623
+ red: [31, 39],
9624
+ green: [32, 39],
9625
+ yellow: [33, 39],
9626
+ blue: [34, 39],
9627
+ magenta: [35, 39],
9628
+ cyan: [36, 39],
9629
+ white: [37, 39],
9630
+ // Bright color
9631
+ blackBright: [90, 39],
9632
+ gray: [90, 39],
9633
+ // Alias of `blackBright`
9634
+ grey: [90, 39],
9635
+ // Alias of `blackBright`
9636
+ redBright: [91, 39],
9637
+ greenBright: [92, 39],
9638
+ yellowBright: [93, 39],
9639
+ blueBright: [94, 39],
9640
+ magentaBright: [95, 39],
9641
+ cyanBright: [96, 39],
9642
+ whiteBright: [97, 39]
9643
+ },
9644
+ bgColor: {
9645
+ bgBlack: [40, 49],
9646
+ bgRed: [41, 49],
9647
+ bgGreen: [42, 49],
9648
+ bgYellow: [43, 49],
9649
+ bgBlue: [44, 49],
9650
+ bgMagenta: [45, 49],
9651
+ bgCyan: [46, 49],
9652
+ bgWhite: [47, 49],
9653
+ // Bright color
9654
+ bgBlackBright: [100, 49],
9655
+ bgGray: [100, 49],
9656
+ // Alias of `bgBlackBright`
9657
+ bgGrey: [100, 49],
9658
+ // Alias of `bgBlackBright`
9659
+ bgRedBright: [101, 49],
9660
+ bgGreenBright: [102, 49],
9661
+ bgYellowBright: [103, 49],
9662
+ bgBlueBright: [104, 49],
9663
+ bgMagentaBright: [105, 49],
9664
+ bgCyanBright: [106, 49],
9665
+ bgWhiteBright: [107, 49]
9666
+ }
9667
+ };
9668
+ var modifierNames2 = Object.keys(styles2.modifier);
9669
+ var foregroundColorNames2 = Object.keys(styles2.color);
9670
+ var backgroundColorNames2 = Object.keys(styles2.bgColor);
9671
+ var colorNames2 = [...foregroundColorNames2, ...backgroundColorNames2];
9672
+ function assembleStyles2() {
9673
+ const codes = /* @__PURE__ */ new Map();
9674
+ for (const [groupName, group] of Object.entries(styles2)) {
9675
+ for (const [styleName, style] of Object.entries(group)) {
9676
+ styles2[styleName] = {
9677
+ open: `\x1B[${style[0]}m`,
9678
+ close: `\x1B[${style[1]}m`
9679
+ };
9680
+ group[styleName] = styles2[styleName];
9681
+ codes.set(style[0], style[1]);
9682
+ }
9683
+ Object.defineProperty(styles2, groupName, {
9684
+ value: group,
9685
+ enumerable: false
9686
+ });
9687
+ }
9688
+ Object.defineProperty(styles2, "codes", {
9689
+ value: codes,
9690
+ enumerable: false
9691
+ });
9692
+ styles2.color.close = "\x1B[39m";
9693
+ styles2.bgColor.close = "\x1B[49m";
9694
+ styles2.color.ansi = wrapAnsi162();
9695
+ styles2.color.ansi256 = wrapAnsi2562();
9696
+ styles2.color.ansi16m = wrapAnsi16m2();
9697
+ styles2.bgColor.ansi = wrapAnsi162(ANSI_BACKGROUND_OFFSET2);
9698
+ styles2.bgColor.ansi256 = wrapAnsi2562(ANSI_BACKGROUND_OFFSET2);
9699
+ styles2.bgColor.ansi16m = wrapAnsi16m2(ANSI_BACKGROUND_OFFSET2);
9700
+ Object.defineProperties(styles2, {
9701
+ rgbToAnsi256: {
9702
+ value(red, green, blue) {
9703
+ if (red === green && green === blue) {
9704
+ if (red < 8) {
9705
+ return 16;
9706
+ }
9707
+ if (red > 248) {
9708
+ return 231;
9709
+ }
9710
+ return Math.round((red - 8) / 247 * 24) + 232;
9711
+ }
9712
+ return 16 + 36 * Math.round(red / 255 * 5) + 6 * Math.round(green / 255 * 5) + Math.round(blue / 255 * 5);
9713
+ },
9714
+ enumerable: false
9715
+ },
9716
+ hexToRgb: {
9717
+ value(hex) {
9718
+ const matches = /[a-f\d]{6}|[a-f\d]{3}/i.exec(hex.toString(16));
9719
+ if (!matches) {
9720
+ return [0, 0, 0];
9721
+ }
9722
+ let [colorString] = matches;
9723
+ if (colorString.length === 3) {
9724
+ colorString = [...colorString].map((character) => character + character).join("");
9725
+ }
9726
+ const integer = Number.parseInt(colorString, 16);
9727
+ return [
9728
+ /* eslint-disable no-bitwise */
9729
+ integer >> 16 & 255,
9730
+ integer >> 8 & 255,
9731
+ integer & 255
9732
+ /* eslint-enable no-bitwise */
9733
+ ];
9734
+ },
9735
+ enumerable: false
9736
+ },
9737
+ hexToAnsi256: {
9738
+ value: (hex) => styles2.rgbToAnsi256(...styles2.hexToRgb(hex)),
9739
+ enumerable: false
9740
+ },
9741
+ ansi256ToAnsi: {
9742
+ value(code) {
9743
+ if (code < 8) {
9744
+ return 30 + code;
9745
+ }
9746
+ if (code < 16) {
9747
+ return 90 + (code - 8);
9748
+ }
9749
+ let red;
9750
+ let green;
9751
+ let blue;
9752
+ if (code >= 232) {
9753
+ red = ((code - 232) * 10 + 8) / 255;
9754
+ green = red;
9755
+ blue = red;
9756
+ } else {
9757
+ code -= 16;
9758
+ const remainder = code % 36;
9759
+ red = Math.floor(code / 36) / 5;
9760
+ green = Math.floor(remainder / 6) / 5;
9761
+ blue = remainder % 6 / 5;
9762
+ }
9763
+ const value = Math.max(red, green, blue) * 2;
9764
+ if (value === 0) {
9765
+ return 30;
9766
+ }
9767
+ let result = 30 + (Math.round(blue) << 2 | Math.round(green) << 1 | Math.round(red));
9768
+ if (value === 2) {
9769
+ result += 60;
9770
+ }
9771
+ return result;
9772
+ },
9773
+ enumerable: false
9774
+ },
9775
+ rgbToAnsi: {
9776
+ value: (red, green, blue) => styles2.ansi256ToAnsi(styles2.rgbToAnsi256(red, green, blue)),
9777
+ enumerable: false
9778
+ },
9779
+ hexToAnsi: {
9780
+ value: (hex) => styles2.ansi256ToAnsi(styles2.hexToAnsi256(hex)),
9781
+ enumerable: false
9782
+ }
9783
+ });
9784
+ return styles2;
9785
+ }
9786
+ var ansiStyles2 = assembleStyles2();
9787
+ var ansi_styles_default2 = ansiStyles2;
9788
+
9789
+ // ../../node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/vendor/supports-color/index.js
9790
+ import process3 from "process";
9791
+ import os from "os";
9792
+ import tty from "tty";
9793
+ function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process3.argv) {
9794
+ const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
9795
+ const position = argv.indexOf(prefix + flag);
9796
+ const terminatorPosition = argv.indexOf("--");
9797
+ return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
9798
+ }
9799
+ var { env } = process3;
9800
+ var flagForceColor;
9801
+ if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
9802
+ flagForceColor = 0;
9803
+ } else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
9804
+ flagForceColor = 1;
9805
+ }
9806
+ function envForceColor() {
9807
+ if ("FORCE_COLOR" in env) {
9808
+ if (env.FORCE_COLOR === "true") {
9809
+ return 1;
9810
+ }
9811
+ if (env.FORCE_COLOR === "false") {
9812
+ return 0;
9813
+ }
9814
+ return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
9815
+ }
9816
+ }
9817
+ function translateLevel(level) {
9818
+ if (level === 0) {
9819
+ return false;
9820
+ }
9821
+ return {
9822
+ level,
9823
+ hasBasic: true,
9824
+ has256: level >= 2,
9825
+ has16m: level >= 3
9826
+ };
9827
+ }
9828
+ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
9829
+ const noFlagForceColor = envForceColor();
9830
+ if (noFlagForceColor !== void 0) {
9831
+ flagForceColor = noFlagForceColor;
9495
9832
  }
9496
9833
  const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
9497
9834
  if (forceColor === 0) {
@@ -9515,7 +9852,7 @@ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
9515
9852
  if (env.TERM === "dumb") {
9516
9853
  return min;
9517
9854
  }
9518
- if (process2.platform === "win32") {
9855
+ if (process3.platform === "win32") {
9519
9856
  const osRelease = os.release().split(".");
9520
9857
  if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
9521
9858
  return Number(osRelease[2]) >= 14931 ? 3 : 2;
@@ -9759,10 +10096,10 @@ var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
9759
10096
  var source_default = chalk;
9760
10097
 
9761
10098
  // ../../node_modules/.pnpm/cli-cursor@5.0.0/node_modules/cli-cursor/index.js
9762
- import process5 from "process";
10099
+ import process6 from "process";
9763
10100
 
9764
10101
  // ../../node_modules/.pnpm/restore-cursor@5.1.0/node_modules/restore-cursor/index.js
9765
- import process4 from "process";
10102
+ import process5 from "process";
9766
10103
 
9767
10104
  // ../../node_modules/.pnpm/mimic-function@5.0.1/node_modules/mimic-function/index.js
9768
10105
  var copyProperty = (to, from, property, ignoreNonConfigurable) => {
@@ -9951,7 +10288,7 @@ var SignalExit = class extends SignalExitBase {
9951
10288
  // "SIGHUP" throws an `ENOSYS` error on Windows,
9952
10289
  // so use a supported signal instead
9953
10290
  /* c8 ignore start */
9954
- #hupSig = process3.platform === "win32" ? "SIGINT" : "SIGHUP";
10291
+ #hupSig = process4.platform === "win32" ? "SIGINT" : "SIGHUP";
9955
10292
  /* c8 ignore stop */
9956
10293
  #emitter = new Emitter();
9957
10294
  #process;
@@ -10062,7 +10399,7 @@ var SignalExit = class extends SignalExitBase {
10062
10399
  }
10063
10400
  }
10064
10401
  };
10065
- var process3 = globalThis.process;
10402
+ var process4 = globalThis.process;
10066
10403
  var {
10067
10404
  /**
10068
10405
  * Called when the process is exiting, whether via signal, explicit
@@ -10090,10 +10427,10 @@ var {
10090
10427
  * @internal
10091
10428
  */
10092
10429
  unload
10093
- } = signalExitWrap(processOk(process3) ? new SignalExit(process3) : new SignalExitFallback());
10430
+ } = signalExitWrap(processOk(process4) ? new SignalExit(process4) : new SignalExitFallback());
10094
10431
 
10095
10432
  // ../../node_modules/.pnpm/restore-cursor@5.1.0/node_modules/restore-cursor/index.js
10096
- 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;
10097
10434
  var restoreCursor = terminal ? onetime_default(() => {
10098
10435
  onExit(() => {
10099
10436
  terminal.write("\x1B[?25h");
@@ -10105,14 +10442,14 @@ var restore_cursor_default = restoreCursor;
10105
10442
  // ../../node_modules/.pnpm/cli-cursor@5.0.0/node_modules/cli-cursor/index.js
10106
10443
  var isHidden = false;
10107
10444
  var cliCursor = {};
10108
- cliCursor.show = (writableStream = process5.stderr) => {
10445
+ cliCursor.show = (writableStream = process6.stderr) => {
10109
10446
  if (!writableStream.isTTY) {
10110
10447
  return;
10111
10448
  }
10112
10449
  isHidden = false;
10113
10450
  writableStream.write("\x1B[?25h");
10114
10451
  };
10115
- cliCursor.hide = (writableStream = process5.stderr) => {
10452
+ cliCursor.hide = (writableStream = process6.stderr) => {
10116
10453
  if (!writableStream.isTTY) {
10117
10454
  return;
10118
10455
  }
@@ -10136,12 +10473,12 @@ var cli_cursor_default = cliCursor;
10136
10473
  var import_cli_spinners = __toESM(require_cli_spinners(), 1);
10137
10474
 
10138
10475
  // ../../node_modules/.pnpm/is-unicode-supported@1.3.0/node_modules/is-unicode-supported/index.js
10139
- import process6 from "process";
10140
- function isUnicodeSupported() {
10141
- if (process6.platform !== "win32") {
10142
- return process6.env.TERM !== "linux";
10476
+ import process7 from "process";
10477
+ function isUnicodeSupported2() {
10478
+ if (process7.platform !== "win32") {
10479
+ return process7.env.TERM !== "linux";
10143
10480
  }
10144
- 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";
10145
10482
  }
10146
10483
 
10147
10484
  // ../../node_modules/.pnpm/log-symbols@6.0.0/node_modules/log-symbols/index.js
@@ -10157,7 +10494,7 @@ var fallback = {
10157
10494
  warning: source_default.yellow("\u203C"),
10158
10495
  error: source_default.red("\xD7")
10159
10496
  };
10160
- var logSymbols = isUnicodeSupported() ? main : fallback;
10497
+ var logSymbols = isUnicodeSupported2() ? main : fallback;
10161
10498
  var log_symbols_default = logSymbols;
10162
10499
 
10163
10500
  // ../../node_modules/.pnpm/emoji-regex@10.6.0/node_modules/emoji-regex/index.mjs
@@ -10220,17 +10557,6 @@ function isInteractive({ stream = process.stdout } = {}) {
10220
10557
  );
10221
10558
  }
10222
10559
 
10223
- // ../../node_modules/.pnpm/is-unicode-supported@2.1.0/node_modules/is-unicode-supported/index.js
10224
- import process7 from "process";
10225
- function isUnicodeSupported2() {
10226
- const { env: env2 } = process7;
10227
- const { TERM, TERM_PROGRAM } = env2;
10228
- if (process7.platform !== "win32") {
10229
- return TERM !== "linux";
10230
- }
10231
- 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";
10232
- }
10233
-
10234
10560
  // ../../node_modules/.pnpm/stdin-discarder@0.2.2/node_modules/stdin-discarder/index.js
10235
10561
  import process8 from "process";
10236
10562
  var ASCII_ETX_CODE = 3;
@@ -10324,548 +10650,404 @@ var Ora = class {
10324
10650
  this._isEnabled = this.#isEnabled;
10325
10651
  Object.defineProperty(this, "_linesToClear", {
10326
10652
  get() {
10327
- return this.#linesToClear;
10328
- },
10329
- set(newValue) {
10330
- this.#linesToClear = newValue;
10331
- }
10332
- });
10333
- Object.defineProperty(this, "_frameIndex", {
10334
- get() {
10335
- return this.#frameIndex;
10336
- }
10337
- });
10338
- Object.defineProperty(this, "_lineCount", {
10339
- get() {
10340
- return this.#lineCount;
10341
- }
10342
- });
10343
- }
10344
- }
10345
- get indent() {
10346
- return this.#indent;
10347
- }
10348
- set indent(indent = 0) {
10349
- if (!(indent >= 0 && Number.isInteger(indent))) {
10350
- throw new Error("The `indent` option must be an integer from 0 and up");
10351
- }
10352
- this.#indent = indent;
10353
- this.#updateLineCount();
10354
- }
10355
- get interval() {
10356
- return this.#initialInterval ?? this.#spinner.interval ?? 100;
10357
- }
10358
- get spinner() {
10359
- return this.#spinner;
10360
- }
10361
- set spinner(spinner) {
10362
- this.#frameIndex = -1;
10363
- this.#initialInterval = void 0;
10364
- if (typeof spinner === "object") {
10365
- if (spinner.frames === void 0) {
10366
- throw new Error("The given spinner must have a `frames` property");
10367
- }
10368
- this.#spinner = spinner;
10369
- } else if (!isUnicodeSupported2()) {
10370
- this.#spinner = import_cli_spinners.default.line;
10371
- } else if (spinner === void 0) {
10372
- this.#spinner = import_cli_spinners.default.dots;
10373
- } else if (spinner !== "default" && import_cli_spinners.default[spinner]) {
10374
- this.#spinner = import_cli_spinners.default[spinner];
10375
- } else {
10376
- throw new Error(`There is no built-in spinner named '${spinner}'. See https://github.com/sindresorhus/cli-spinners/blob/main/spinners.json for a full list.`);
10377
- }
10378
- }
10379
- get text() {
10380
- return this.#text;
10381
- }
10382
- set text(value = "") {
10383
- this.#text = value;
10384
- this.#updateLineCount();
10385
- }
10386
- get prefixText() {
10387
- return this.#prefixText;
10388
- }
10389
- set prefixText(value = "") {
10390
- this.#prefixText = value;
10391
- this.#updateLineCount();
10392
- }
10393
- get suffixText() {
10394
- return this.#suffixText;
10395
- }
10396
- set suffixText(value = "") {
10397
- this.#suffixText = value;
10398
- this.#updateLineCount();
10399
- }
10400
- get isSpinning() {
10401
- return this.#id !== void 0;
10402
- }
10403
- #getFullPrefixText(prefixText = this.#prefixText, postfix = " ") {
10404
- if (typeof prefixText === "string" && prefixText !== "") {
10405
- return prefixText + postfix;
10406
- }
10407
- if (typeof prefixText === "function") {
10408
- return prefixText() + postfix;
10409
- }
10410
- return "";
10411
- }
10412
- #getFullSuffixText(suffixText = this.#suffixText, prefix = " ") {
10413
- if (typeof suffixText === "string" && suffixText !== "") {
10414
- return prefix + suffixText;
10415
- }
10416
- if (typeof suffixText === "function") {
10417
- return prefix + suffixText();
10418
- }
10419
- return "";
10420
- }
10421
- #updateLineCount() {
10422
- const columns = this.#stream.columns ?? 80;
10423
- const fullPrefixText = this.#getFullPrefixText(this.#prefixText, "-");
10424
- const fullSuffixText = this.#getFullSuffixText(this.#suffixText, "-");
10425
- const fullText = " ".repeat(this.#indent) + fullPrefixText + "--" + this.#text + "--" + fullSuffixText;
10426
- this.#lineCount = 0;
10427
- for (const line of stripAnsi(fullText).split("\n")) {
10428
- this.#lineCount += Math.max(1, Math.ceil(stringWidth2(line, { countAnsiEscapeCodes: true }) / columns));
10429
- }
10430
- }
10431
- get isEnabled() {
10432
- return this.#isEnabled && !this.#isSilent;
10433
- }
10434
- set isEnabled(value) {
10435
- if (typeof value !== "boolean") {
10436
- throw new TypeError("The `isEnabled` option must be a boolean");
10437
- }
10438
- this.#isEnabled = value;
10439
- }
10440
- get isSilent() {
10441
- return this.#isSilent;
10442
- }
10443
- set isSilent(value) {
10444
- if (typeof value !== "boolean") {
10445
- throw new TypeError("The `isSilent` option must be a boolean");
10446
- }
10447
- this.#isSilent = value;
10448
- }
10449
- frame() {
10450
- const now = Date.now();
10451
- if (this.#frameIndex === -1 || now - this.#lastSpinnerFrameTime >= this.interval) {
10452
- this.#frameIndex = ++this.#frameIndex % this.#spinner.frames.length;
10453
- this.#lastSpinnerFrameTime = now;
10454
- }
10455
- const { frames } = this.#spinner;
10456
- let frame = frames[this.#frameIndex];
10457
- if (this.color) {
10458
- frame = source_default[this.color](frame);
10459
- }
10460
- const fullPrefixText = typeof this.#prefixText === "string" && this.#prefixText !== "" ? this.#prefixText + " " : "";
10461
- const fullText = typeof this.text === "string" ? " " + this.text : "";
10462
- const fullSuffixText = typeof this.#suffixText === "string" && this.#suffixText !== "" ? " " + this.#suffixText : "";
10463
- return fullPrefixText + frame + fullText + fullSuffixText;
10464
- }
10465
- clear() {
10466
- if (!this.#isEnabled || !this.#stream.isTTY) {
10467
- return this;
10468
- }
10469
- this.#stream.cursorTo(0);
10470
- for (let index = 0; index < this.#linesToClear; index++) {
10471
- if (index > 0) {
10472
- this.#stream.moveCursor(0, -1);
10473
- }
10474
- this.#stream.clearLine(1);
10475
- }
10476
- if (this.#indent || this.lastIndent !== this.#indent) {
10477
- this.#stream.cursorTo(this.#indent);
10478
- }
10479
- this.lastIndent = this.#indent;
10480
- this.#linesToClear = 0;
10481
- return this;
10482
- }
10483
- render() {
10484
- if (this.#isSilent) {
10485
- return this;
10486
- }
10487
- this.clear();
10488
- this.#stream.write(this.frame());
10489
- this.#linesToClear = this.#lineCount;
10490
- return this;
10491
- }
10492
- start(text) {
10493
- if (text) {
10494
- this.text = text;
10495
- }
10496
- if (this.#isSilent) {
10497
- return this;
10498
- }
10499
- if (!this.#isEnabled) {
10500
- if (this.text) {
10501
- this.#stream.write(`- ${this.text}
10502
- `);
10503
- }
10504
- return this;
10505
- }
10506
- if (this.isSpinning) {
10507
- return this;
10508
- }
10509
- if (this.#options.hideCursor) {
10510
- cli_cursor_default.hide(this.#stream);
10511
- }
10512
- if (this.#options.discardStdin && process9.stdin.isTTY) {
10513
- this.#isDiscardingStdin = true;
10514
- stdin_discarder_default.start();
10653
+ return this.#linesToClear;
10654
+ },
10655
+ set(newValue) {
10656
+ this.#linesToClear = newValue;
10657
+ }
10658
+ });
10659
+ Object.defineProperty(this, "_frameIndex", {
10660
+ get() {
10661
+ return this.#frameIndex;
10662
+ }
10663
+ });
10664
+ Object.defineProperty(this, "_lineCount", {
10665
+ get() {
10666
+ return this.#lineCount;
10667
+ }
10668
+ });
10515
10669
  }
10516
- this.render();
10517
- this.#id = setInterval(this.render.bind(this), this.interval);
10518
- return this;
10519
10670
  }
10520
- stop() {
10521
- if (!this.#isEnabled) {
10522
- return this;
10523
- }
10524
- clearInterval(this.#id);
10525
- this.#id = void 0;
10526
- this.#frameIndex = 0;
10527
- this.clear();
10528
- if (this.#options.hideCursor) {
10529
- cli_cursor_default.show(this.#stream);
10671
+ get indent() {
10672
+ return this.#indent;
10673
+ }
10674
+ set indent(indent = 0) {
10675
+ if (!(indent >= 0 && Number.isInteger(indent))) {
10676
+ throw new Error("The `indent` option must be an integer from 0 and up");
10530
10677
  }
10531
- if (this.#options.discardStdin && process9.stdin.isTTY && this.#isDiscardingStdin) {
10532
- stdin_discarder_default.stop();
10533
- this.#isDiscardingStdin = false;
10678
+ this.#indent = indent;
10679
+ this.#updateLineCount();
10680
+ }
10681
+ get interval() {
10682
+ return this.#initialInterval ?? this.#spinner.interval ?? 100;
10683
+ }
10684
+ get spinner() {
10685
+ return this.#spinner;
10686
+ }
10687
+ set spinner(spinner) {
10688
+ this.#frameIndex = -1;
10689
+ this.#initialInterval = void 0;
10690
+ if (typeof spinner === "object") {
10691
+ if (spinner.frames === void 0) {
10692
+ throw new Error("The given spinner must have a `frames` property");
10693
+ }
10694
+ this.#spinner = spinner;
10695
+ } else if (!isUnicodeSupported()) {
10696
+ this.#spinner = import_cli_spinners.default.line;
10697
+ } else if (spinner === void 0) {
10698
+ this.#spinner = import_cli_spinners.default.dots;
10699
+ } else if (spinner !== "default" && import_cli_spinners.default[spinner]) {
10700
+ this.#spinner = import_cli_spinners.default[spinner];
10701
+ } else {
10702
+ throw new Error(`There is no built-in spinner named '${spinner}'. See https://github.com/sindresorhus/cli-spinners/blob/main/spinners.json for a full list.`);
10534
10703
  }
10535
- return this;
10536
10704
  }
10537
- succeed(text) {
10538
- return this.stopAndPersist({ symbol: log_symbols_default.success, text });
10705
+ get text() {
10706
+ return this.#text;
10539
10707
  }
10540
- fail(text) {
10541
- return this.stopAndPersist({ symbol: log_symbols_default.error, text });
10708
+ set text(value = "") {
10709
+ this.#text = value;
10710
+ this.#updateLineCount();
10542
10711
  }
10543
- warn(text) {
10544
- return this.stopAndPersist({ symbol: log_symbols_default.warning, text });
10712
+ get prefixText() {
10713
+ return this.#prefixText;
10545
10714
  }
10546
- info(text) {
10547
- return this.stopAndPersist({ symbol: log_symbols_default.info, text });
10715
+ set prefixText(value = "") {
10716
+ this.#prefixText = value;
10717
+ this.#updateLineCount();
10548
10718
  }
10549
- stopAndPersist(options = {}) {
10550
- if (this.#isSilent) {
10551
- return this;
10552
- }
10553
- const prefixText = options.prefixText ?? this.#prefixText;
10554
- const fullPrefixText = this.#getFullPrefixText(prefixText, " ");
10555
- const symbolText = options.symbol ?? " ";
10556
- const text = options.text ?? this.text;
10557
- const separatorText = symbolText ? " " : "";
10558
- const fullText = typeof text === "string" ? separatorText + text : "";
10559
- const suffixText = options.suffixText ?? this.#suffixText;
10560
- const fullSuffixText = this.#getFullSuffixText(suffixText, " ");
10561
- const textToWrite = fullPrefixText + symbolText + fullText + fullSuffixText + "\n";
10562
- this.stop();
10563
- this.#stream.write(textToWrite);
10564
- return this;
10719
+ get suffixText() {
10720
+ return this.#suffixText;
10565
10721
  }
10566
- };
10567
- function ora(options) {
10568
- return new Ora(options);
10569
- }
10570
-
10571
- // src/commands/pentest.ts
10572
- var import_picocolors5 = __toESM(require_picocolors(), 1);
10573
-
10574
- // src/lib/confirm.ts
10575
- var import_picocolors3 = __toESM(require_picocolors(), 1);
10576
- import { createInterface } from "readline";
10577
- async function confirmDestructive(message, yes) {
10578
- if (yes) return;
10579
- if (!isTTY()) {
10580
- console.error("Use --yes to confirm destructive operations in non-interactive mode.");
10581
- process.exit(2);
10722
+ set suffixText(value = "") {
10723
+ this.#suffixText = value;
10724
+ this.#updateLineCount();
10582
10725
  }
10583
- const rl = createInterface({ input: process.stdin, output: process.stderr });
10584
- const answer = await new Promise((resolve) => {
10585
- rl.question(`${message} (y/N) `, resolve);
10586
- });
10587
- rl.close();
10588
- if (answer.toLowerCase() !== "y") {
10589
- console.log("Cancelled.");
10590
- process.exit(0);
10726
+ get isSpinning() {
10727
+ return this.#id !== void 0;
10591
10728
  }
10592
- }
10593
- async function confirmAuthorization(target, yes) {
10594
- if (yes) return;
10595
- if (!isTTY()) {
10596
- console.error("Use --yes to accept authorization terms in non-interactive mode.");
10597
- process.exit(2);
10729
+ #getFullPrefixText(prefixText = this.#prefixText, postfix = " ") {
10730
+ if (typeof prefixText === "string" && prefixText !== "") {
10731
+ return prefixText + postfix;
10732
+ }
10733
+ if (typeof prefixText === "function") {
10734
+ return prefixText() + postfix;
10735
+ }
10736
+ return "";
10598
10737
  }
10599
- const warning = import_picocolors3.default.yellow("\u26A0");
10600
- console.error(`
10601
- ${warning} ${import_picocolors3.default.bold("Authorization Required")}
10602
-
10603
- Penetration testing without proper authorization is illegal
10604
- and may result in criminal prosecution.
10605
-
10606
- ${import_picocolors3.default.bold("Target:")} ${target}
10607
-
10608
- By proceeding, you confirm that:
10609
- ${import_picocolors3.default.dim("\u2022")} You own or have explicit written authorization to test this target
10610
- ${import_picocolors3.default.dim("\u2022")} You will comply with all applicable laws and regulations
10611
- ${import_picocolors3.default.dim("\u2022")} You will not use the results for illegal or malicious purposes
10612
- ${import_picocolors3.default.dim("\u2022")} You accept full legal responsibility for this pentest
10613
-
10614
- ${import_picocolors3.default.dim("Unauthorized access to computer systems is a criminal offense under")}
10615
- ${import_picocolors3.default.dim("laws such as the CFAA (US), Computer Misuse Act (UK), and similar")}
10616
- ${import_picocolors3.default.dim("legislation worldwide. KinoSec bears no liability for unauthorized")}
10617
- ${import_picocolors3.default.dim("use of its services.")}
10618
- `);
10619
- const rl = createInterface({ input: process.stdin, output: process.stderr });
10620
- const answer = await new Promise((resolve) => {
10621
- rl.question("Agree and continue? (y/N) ", resolve);
10622
- });
10623
- rl.close();
10624
- if (answer.toLowerCase() !== "y") {
10625
- console.log("Cancelled.");
10626
- process.exit(0);
10738
+ #getFullSuffixText(suffixText = this.#suffixText, prefix = " ") {
10739
+ if (typeof suffixText === "string" && suffixText !== "") {
10740
+ return prefix + suffixText;
10741
+ }
10742
+ if (typeof suffixText === "function") {
10743
+ return prefix + suffixText();
10744
+ }
10745
+ return "";
10627
10746
  }
10628
- }
10629
-
10630
- // src/commands/schedule.ts
10631
- var import_picocolors4 = __toESM(require_picocolors(), 1);
10632
- var VALID_PATTERNS = ["once", "daily", "weekly", "biweekly", "monthly"];
10633
- var VALID_MODES = ["quick", "full"];
10634
- var DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
10635
- function extractTime(scheduledAt) {
10636
- if (!scheduledAt) return "\u2014";
10637
- const d = new Date(scheduledAt);
10638
- return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", hour12: false });
10639
- }
10640
- function formatPattern(s) {
10641
- const time = extractTime(s.scheduled_at);
10642
- switch (s.recurrence_pattern) {
10643
- case "once":
10644
- return `Once at ${time}`;
10645
- case "daily":
10646
- return `Daily at ${time}`;
10647
- case "weekly":
10648
- return `Weekly ${s.day_of_week !== null ? DAY_NAMES[s.day_of_week] : ""} at ${time}`;
10649
- case "biweekly":
10650
- return `Every 2 weeks ${s.day_of_week !== null ? DAY_NAMES[s.day_of_week] : ""} at ${time}`;
10651
- case "monthly":
10652
- return `Monthly day ${s.day_of_month ?? "?"} at ${time}`;
10653
- default:
10654
- return s.recurrence_pattern;
10747
+ #updateLineCount() {
10748
+ const columns = this.#stream.columns ?? 80;
10749
+ const fullPrefixText = this.#getFullPrefixText(this.#prefixText, "-");
10750
+ const fullSuffixText = this.#getFullSuffixText(this.#suffixText, "-");
10751
+ const fullText = " ".repeat(this.#indent) + fullPrefixText + "--" + this.#text + "--" + fullSuffixText;
10752
+ this.#lineCount = 0;
10753
+ for (const line of stripAnsi(fullText).split("\n")) {
10754
+ this.#lineCount += Math.max(1, Math.ceil(stringWidth2(line, { countAnsiEscapeCodes: true }) / columns));
10755
+ }
10655
10756
  }
10656
- }
10657
- function registerScheduleCommand(pentestCmd) {
10658
- const schedule = pentestCmd.command("schedule").description("Manage scheduled pentests");
10659
- 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", `
10660
- Examples:
10661
- $ kinosec pentest schedule list
10662
- $ kinosec pentest schedule list --status active --json`).action(async (options) => {
10663
- try {
10664
- const opts = schedule.parent.parent.opts();
10665
- const { token, apiUrl } = resolveToken(opts);
10666
- const client = new KinoSecClient({ apiUrl, token });
10667
- const result = await client.schedules.list({
10668
- limit: parseInt(options.limit, 10),
10669
- cursor: options.cursor,
10670
- status: options.status
10671
- });
10672
- if (opts.json || opts.output === "json") {
10673
- printJson(result);
10674
- return;
10675
- }
10676
- if (result.items.length === 0) {
10677
- console.log("No scheduled pentests found.");
10678
- return;
10679
- }
10680
- printTable(
10681
- ["ID", "Target", "Mode", "Schedule", "Status", "Next Run"],
10682
- result.items.map((s) => [
10683
- s.id,
10684
- truncate(s.domain_or_ip, 25),
10685
- s.scan_mode,
10686
- formatPattern(s),
10687
- s.is_active ? import_picocolors4.default.green("active") : import_picocolors4.default.yellow("paused"),
10688
- s.next_scheduled_run ? new Date(s.next_scheduled_run).toLocaleString() : "\u2014"
10689
- ])
10690
- );
10691
- if (result.next_cursor) {
10692
- console.log(import_picocolors4.default.dim(`
10693
- More results available. Use --cursor ${result.next_cursor}`));
10694
- }
10695
- } catch (error) {
10696
- handleError(error);
10757
+ get isEnabled() {
10758
+ return this.#isEnabled && !this.#isSilent;
10759
+ }
10760
+ set isEnabled(value) {
10761
+ if (typeof value !== "boolean") {
10762
+ throw new TypeError("The `isEnabled` option must be a boolean");
10697
10763
  }
10698
- });
10699
- schedule.command("get").description("Get schedule details").argument("<id>", "Schedule ID").addHelpText("after", `
10700
- Examples:
10701
- $ kinosec pentest schedule get abc123
10702
- $ kinosec pentest schedule get abc123 --json`).action(async (id) => {
10703
- try {
10704
- const opts = schedule.parent.parent.opts();
10705
- const { token, apiUrl } = resolveToken(opts);
10706
- const client = new KinoSecClient({ apiUrl, token });
10707
- const s = await client.schedules.get(id);
10708
- if (opts.json || opts.output === "json") {
10709
- printJson(s);
10710
- return;
10764
+ this.#isEnabled = value;
10765
+ }
10766
+ get isSilent() {
10767
+ return this.#isSilent;
10768
+ }
10769
+ set isSilent(value) {
10770
+ if (typeof value !== "boolean") {
10771
+ throw new TypeError("The `isSilent` option must be a boolean");
10772
+ }
10773
+ this.#isSilent = value;
10774
+ }
10775
+ frame() {
10776
+ const now = Date.now();
10777
+ if (this.#frameIndex === -1 || now - this.#lastSpinnerFrameTime >= this.interval) {
10778
+ this.#frameIndex = ++this.#frameIndex % this.#spinner.frames.length;
10779
+ this.#lastSpinnerFrameTime = now;
10780
+ }
10781
+ const { frames } = this.#spinner;
10782
+ let frame = frames[this.#frameIndex];
10783
+ if (this.color) {
10784
+ frame = source_default[this.color](frame);
10785
+ }
10786
+ const fullPrefixText = typeof this.#prefixText === "string" && this.#prefixText !== "" ? this.#prefixText + " " : "";
10787
+ const fullText = typeof this.text === "string" ? " " + this.text : "";
10788
+ const fullSuffixText = typeof this.#suffixText === "string" && this.#suffixText !== "" ? " " + this.#suffixText : "";
10789
+ return fullPrefixText + frame + fullText + fullSuffixText;
10790
+ }
10791
+ clear() {
10792
+ if (!this.#isEnabled || !this.#stream.isTTY) {
10793
+ return this;
10794
+ }
10795
+ this.#stream.cursorTo(0);
10796
+ for (let index = 0; index < this.#linesToClear; index++) {
10797
+ if (index > 0) {
10798
+ this.#stream.moveCursor(0, -1);
10711
10799
  }
10712
- printTable(
10713
- ["Field", "Value"],
10714
- [
10715
- ["ID", s.id],
10716
- ["Target", s.domain_or_ip],
10717
- ["Name", s.name],
10718
- ["Mode", s.scan_mode],
10719
- ["Schedule", formatPattern(s)],
10720
- ["Timezone", s.timezone],
10721
- ["Status", s.is_active ? "Active" : "Paused"],
10722
- ["Next Run", s.next_scheduled_run ?? "\u2014"],
10723
- ["Last Run", s.last_scheduled_run ?? "Never"],
10724
- ["Created", s.created_at]
10725
- ]
10726
- );
10727
- } catch (error) {
10728
- handleError(error);
10800
+ this.#stream.clearLine(1);
10729
10801
  }
10730
- });
10731
- 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", `
10732
- Examples:
10733
- $ kinosec pentest schedule create --asset abc123 --pattern weekly --day-of-week 1 --time 09:00
10734
- $ kinosec pentest schedule create --asset abc123 --pattern daily --mode quick --time 14:00
10735
- $ kinosec pentest schedule create --asset abc123 --pattern monthly --day-of-month 15 --time 03:00 --timezone UTC`).action(async (options) => {
10736
- if (!VALID_PATTERNS.includes(options.pattern)) {
10737
- console.error(`Invalid pattern "${options.pattern}". Must be: ${VALID_PATTERNS.join(", ")}`);
10738
- process.exit(2);
10802
+ if (this.#indent || this.lastIndent !== this.#indent) {
10803
+ this.#stream.cursorTo(this.#indent);
10739
10804
  }
10740
- if (!VALID_MODES.includes(options.mode)) {
10741
- console.error(`Invalid mode "${options.mode}". Must be: ${VALID_MODES.join(", ")}`);
10742
- process.exit(2);
10805
+ this.lastIndent = this.#indent;
10806
+ this.#linesToClear = 0;
10807
+ return this;
10808
+ }
10809
+ render() {
10810
+ if (this.#isSilent) {
10811
+ return this;
10743
10812
  }
10744
- if ((options.pattern === "weekly" || options.pattern === "biweekly") && options.dayOfWeek === void 0) {
10745
- console.error(`--day-of-week is required for ${options.pattern} schedules.`);
10746
- process.exit(2);
10813
+ this.clear();
10814
+ this.#stream.write(this.frame());
10815
+ this.#linesToClear = this.#lineCount;
10816
+ return this;
10817
+ }
10818
+ start(text) {
10819
+ if (text) {
10820
+ this.text = text;
10747
10821
  }
10748
- if (options.pattern === "monthly" && options.dayOfMonth === void 0) {
10749
- console.error("--day-of-month is required for monthly schedules.");
10750
- process.exit(2);
10822
+ if (this.#isSilent) {
10823
+ return this;
10751
10824
  }
10752
- try {
10753
- const opts = schedule.parent.parent.opts();
10754
- const { token, apiUrl } = resolveToken(opts);
10755
- const client = new KinoSecClient({ apiUrl, token });
10756
- const params = {
10757
- asset_id: options.domain_or_ip,
10758
- name: options.name ?? `${options.pattern} ${options.mode} pentest`,
10759
- scan_mode: options.mode,
10760
- recurrence_pattern: options.pattern,
10761
- time_of_day: options.time,
10762
- timezone: options.timezone
10763
- };
10764
- if (options.dayOfWeek !== void 0) params.day_of_week = parseInt(options.dayOfWeek, 10);
10765
- if (options.dayOfMonth !== void 0) params.day_of_month = parseInt(options.dayOfMonth, 10);
10766
- const s = await client.schedules.create(params);
10767
- if (opts.json || opts.output === "json") {
10768
- printJson(s);
10769
- return;
10825
+ if (!this.#isEnabled) {
10826
+ if (this.text) {
10827
+ this.#stream.write(`- ${this.text}
10828
+ `);
10770
10829
  }
10771
- printSuccess(`Schedule created: ${s.domain_or_ip}`);
10772
- console.log(` ID: ${s.id}`);
10773
- console.log(` Schedule: ${formatPattern(s)}`);
10774
- console.log(` Next run: ${s.next_scheduled_run ?? "pending"}`);
10775
- console.log(`
10776
- Manage: kinosec pentest schedule get ${s.id}`);
10777
- } catch (error) {
10778
- handleError(error);
10830
+ return this;
10779
10831
  }
10780
- });
10781
- 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", `
10782
- Examples:
10783
- $ kinosec pentest schedule update abc123 --pattern daily --time 14:00
10784
- $ kinosec pentest schedule update abc123 --mode quick`).action(async (id, options) => {
10785
- const params = {};
10786
- if (options.pattern) params.recurrence_pattern = options.pattern;
10787
- if (options.name) params.name = options.name;
10788
- if (options.mode) params.scan_mode = options.mode;
10789
- if (options.time) params.time_of_day = options.time;
10790
- if (options.dayOfWeek !== void 0) params.day_of_week = parseInt(options.dayOfWeek, 10);
10791
- if (options.dayOfMonth !== void 0) params.day_of_month = parseInt(options.dayOfMonth, 10);
10792
- if (options.timezone) params.timezone = options.timezone;
10793
- if (Object.keys(params).length === 0) {
10794
- console.error("Provide at least one field to update.");
10795
- process.exit(2);
10832
+ if (this.isSpinning) {
10833
+ return this;
10796
10834
  }
10797
- try {
10798
- const opts = schedule.parent.parent.opts();
10799
- const { token, apiUrl } = resolveToken(opts);
10800
- const client = new KinoSecClient({ apiUrl, token });
10801
- const s = await client.schedules.update(id, params);
10802
- if (opts.json || opts.output === "json") {
10803
- printJson(s);
10804
- return;
10805
- }
10806
- printSuccess(`Schedule updated: ${s.domain_or_ip}`);
10807
- console.log(` Schedule: ${formatPattern(s)}`);
10808
- console.log(` Next run: ${s.next_scheduled_run ?? "pending"}`);
10809
- } catch (error) {
10810
- handleError(error);
10835
+ if (this.#options.hideCursor) {
10836
+ cli_cursor_default.hide(this.#stream);
10811
10837
  }
10812
- });
10813
- schedule.command("delete").description("Delete a schedule").argument("<id>", "Schedule ID").option("-y, --yes", "Skip confirmation prompt").addHelpText("after", `
10814
- Examples:
10815
- $ kinosec pentest schedule delete abc123
10816
- $ kinosec pentest schedule delete abc123 --yes`).action(async (id, options) => {
10817
- try {
10818
- await confirmDestructive(`Delete schedule ${id}?`, Boolean(options.yes));
10819
- const opts = schedule.parent.parent.opts();
10820
- const { token, apiUrl } = resolveToken(opts);
10821
- const client = new KinoSecClient({ apiUrl, token });
10822
- await client.schedules.delete(id);
10823
- if (opts.json || opts.output === "json") {
10824
- printJson({ deleted: true, id });
10825
- return;
10826
- }
10827
- printSuccess(`Schedule ${id} deleted.`);
10828
- } catch (error) {
10829
- handleError(error);
10838
+ if (this.#options.discardStdin && process9.stdin.isTTY) {
10839
+ this.#isDiscardingStdin = true;
10840
+ stdin_discarder_default.start();
10830
10841
  }
10831
- });
10832
- schedule.command("pause").description("Pause a scheduled pentest").argument("<id>", "Schedule ID").addHelpText("after", `
10833
- Examples:
10834
- $ kinosec pentest schedule pause abc123`).action(async (id) => {
10835
- try {
10836
- const opts = schedule.parent.parent.opts();
10837
- const { token, apiUrl } = resolveToken(opts);
10838
- const client = new KinoSecClient({ apiUrl, token });
10839
- const result = await client.schedules.pause(id);
10840
- if (opts.json || opts.output === "json") {
10841
- printJson(result);
10842
- return;
10843
- }
10844
- printSuccess("Schedule paused.");
10845
- } catch (error) {
10846
- handleError(error);
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;
10849
+ }
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);
10847
10856
  }
10857
+ if (this.#options.discardStdin && process9.stdin.isTTY && this.#isDiscardingStdin) {
10858
+ stdin_discarder_default.stop();
10859
+ this.#isDiscardingStdin = false;
10860
+ }
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;
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);
10848
10937
  });
10849
- schedule.command("resume").description("Resume a paused schedule").argument("<id>", "Schedule ID").addHelpText("after", `
10850
- Examples:
10851
- $ kinosec pentest schedule resume abc123`).action(async (id) => {
10852
- try {
10853
- const opts = schedule.parent.parent.opts();
10854
- const { token, apiUrl } = resolveToken(opts);
10855
- const client = new KinoSecClient({ apiUrl, token });
10856
- const result = await client.schedules.resume(id);
10857
- if (opts.json || opts.output === "json") {
10858
- printJson(result);
10859
- 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;
10860
11010
  }
10861
- printSuccess("Schedule resumed.");
10862
- if (result.next_scheduled_run) {
10863
- 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);
10864
11019
  }
10865
- } catch (error) {
10866
- 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;
10867
11027
  }
10868
- });
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
+ }
10869
11051
  }
10870
11052
 
10871
11053
  // src/commands/pentest.ts
@@ -10878,7 +11060,7 @@ function isValidDomain(value) {
10878
11060
  return value.includes(".") && !/\s/.test(value);
10879
11061
  }
10880
11062
  function registerPentestCommand(program3) {
10881
- 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", `
10882
11064
  Examples:
10883
11065
  $ kinosec pentest -t delikado.org
10884
11066
  $ kinosec pentest --target delikado.org --mode quick
@@ -10912,10 +11094,15 @@ Examples:
10912
11094
  asset = await client.assets.lookup(domain);
10913
11095
  } catch (err) {
10914
11096
  if (err instanceof ApiError && err.status === 404) {
10915
- console.error(`No asset found for '${domain}'. Run kinosec asset list to see your assets.`);
10916
- process.exit(1);
11097
+ asset = await client.assets.create({
11098
+ asset_type: "domain",
11099
+ asset_value: domain,
11100
+ verification_method: "dns"
11101
+ });
11102
+ console.error(`Asset created for ${domain}.`);
11103
+ } else {
11104
+ throw err;
10917
11105
  }
10918
- throw err;
10919
11106
  }
10920
11107
  targetDisplay = domain;
10921
11108
  params = { asset_id: asset.id, scan_mode: options.mode };
@@ -10929,9 +11116,22 @@ Examples:
10929
11116
  printSuccess(`Pentest started: ${result.domain_or_ip}`);
10930
11117
  console.log(` Profile ID: ${result.scan_profile_id}`);
10931
11118
  console.log(` Mode: ${result.scan_mode}`);
10932
- console.log(` Status: ${result.scan_status}`);
10933
- console.log(`
11119
+ if (!options.watch || !isTTY()) {
11120
+ console.log(` Status: ${result.scan_status}`);
11121
+ console.log(`
10934
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
+ });
10935
11135
  } catch (error) {
10936
11136
  handleError(error);
10937
11137
  }
@@ -10968,7 +11168,7 @@ Examples:
10968
11168
  ])
10969
11169
  );
10970
11170
  if (result.next_cursor) {
10971
- console.log(import_picocolors5.default.dim(`
11171
+ console.log(import_picocolors6.default.dim(`
10972
11172
  More results available. Use --cursor ${result.next_cursor}`));
10973
11173
  }
10974
11174
  } catch (error) {
@@ -11031,67 +11231,408 @@ Examples:
11031
11231
  const opts = program3.opts();
11032
11232
  const { token, apiUrl } = resolveToken(opts);
11033
11233
  const client = new KinoSecClient({ apiUrl, token });
11034
- const useJson = opts.json || opts.output === "json";
11035
- const timeoutMs = parseInt(options.timeout, 10) * 1e3;
11036
- const intervalMs = parseInt(options.interval, 10) * 1e3;
11037
- const startTime = Date.now();
11038
- let spinner = null;
11039
- let lastActivity = "";
11040
- if (!useJson && isTTY()) {
11041
- spinner = ora("Fetching pentest status...").start();
11042
- }
11043
- const cleanup = () => {
11044
- if (spinner) spinner.stop();
11045
- };
11046
- process.once("SIGINT", () => {
11047
- cleanup();
11048
- console.log("\nWatch cancelled. Pentest is still running.");
11049
- 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
11050
11239
  });
11051
- while (true) {
11052
- const scanDetail = await client.scans.get(id);
11053
- const elapsed = Math.round((Date.now() - startTime) / 1e3);
11054
- const activity = scanDetail.current_activity || "";
11055
- if (useJson) {
11056
- process.stdout.write(JSON.stringify({ ...scanDetail, elapsed_seconds: elapsed }) + "\n");
11057
- } else if (spinner) {
11058
- if (activity && activity !== lastActivity && lastActivity) {
11059
- spinner.stopAndPersist({ symbol: import_picocolors5.default.green("\u2713"), text: lastActivity });
11060
- spinner = ora().start();
11061
- }
11062
- lastActivity = activity || lastActivity;
11063
- const activitySuffix = activity ? ` \u2014 ${activity}` : "";
11064
- spinner.text = `${scanDetail.domain_or_ip}${activitySuffix} \u2014 ${elapsed}s elapsed`;
11065
- }
11066
- const terminalStatuses = ["Completed", "Failed", "Stopped"];
11067
- if (terminalStatuses.some((s) => scanDetail.scan_status.includes(s))) {
11068
- cleanup();
11069
- if (useJson) {
11070
- break;
11240
+ } catch (error) {
11241
+ handleError(error);
11242
+ }
11243
+ });
11244
+ }
11245
+
11246
+ // src/commands/asset.ts
11247
+ var import_picocolors8 = __toESM(require_picocolors(), 1);
11248
+
11249
+ // src/commands/asset-auth.ts
11250
+ var import_picocolors7 = __toESM(require_picocolors(), 1);
11251
+
11252
+ // src/lib/resolve-asset.ts
11253
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-/i;
11254
+ async function resolveAsset(client, identifier) {
11255
+ try {
11256
+ if (UUID_RE.test(identifier)) {
11257
+ return await client.assets.get(identifier);
11258
+ }
11259
+ return await client.assets.lookup(identifier);
11260
+ } catch (error) {
11261
+ if (error instanceof ApiError && error.status === 404) {
11262
+ throw new ApiError({
11263
+ status: 404,
11264
+ code: error.code,
11265
+ detail: `Asset "${identifier}" not found. Create it with:
11266
+ kinosec asset create --type domain --value ${identifier} --verify-method dns
11267
+ or use pentest which auto-creates the asset:
11268
+ kinosec pentest -t ${identifier}`,
11269
+ requestId: error.requestId
11270
+ });
11271
+ }
11272
+ throw error;
11273
+ }
11274
+ }
11275
+
11276
+ // src/lib/prompt.ts
11277
+ import { createInterface as createInterface2 } from "readline";
11278
+ async function promptMasked(label) {
11279
+ if (!isTTY() || !process.stdin.isTTY) {
11280
+ console.error(`Cannot prompt for ${label} in non-interactive mode. Use flags instead.`);
11281
+ process.exit(2);
11282
+ }
11283
+ process.stderr.write(`${label}: `);
11284
+ return new Promise((resolve, reject) => {
11285
+ const chars = [];
11286
+ process.stdin.setRawMode(true);
11287
+ process.stdin.resume();
11288
+ process.stdin.setEncoding("utf-8");
11289
+ const onData = (chunk) => {
11290
+ for (const ch of chunk) {
11291
+ if (ch === "\r" || ch === "\n") {
11292
+ process.stdin.setRawMode(false);
11293
+ process.stdin.pause();
11294
+ process.stdin.removeListener("data", onData);
11295
+ process.stderr.write("\n");
11296
+ resolve(chars.join(""));
11297
+ return;
11298
+ } else if (ch === "") {
11299
+ process.stdin.setRawMode(false);
11300
+ process.stdin.pause();
11301
+ process.stdin.removeListener("data", onData);
11302
+ process.stderr.write("\n");
11303
+ reject(new Error("Cancelled"));
11304
+ return;
11305
+ } else if (ch === "\x7F" || ch === "\b") {
11306
+ if (chars.length > 0) {
11307
+ chars.pop();
11308
+ process.stderr.write("\b \b");
11071
11309
  }
11072
- if (scanDetail.scan_status.includes("Failed")) {
11073
- console.log(import_picocolors5.default.red(`
11074
- Pentest failed: ${scanDetail.failure_reason || "unknown reason"}`));
11075
- process.exit(1);
11310
+ } else if (ch >= " ") {
11311
+ chars.push(ch);
11312
+ process.stderr.write("*");
11313
+ }
11314
+ }
11315
+ };
11316
+ process.stdin.on("data", onData);
11317
+ });
11318
+ }
11319
+ async function promptText(label) {
11320
+ if (!isTTY() || !process.stdin.isTTY) {
11321
+ console.error(`Cannot prompt for ${label} in non-interactive mode. Use flags instead.`);
11322
+ process.exit(2);
11323
+ }
11324
+ const rl = createInterface2({ input: process.stdin, output: process.stderr });
11325
+ const answer = await new Promise((resolve) => {
11326
+ rl.question(`${label}: `, resolve);
11327
+ });
11328
+ rl.close();
11329
+ return answer;
11330
+ }
11331
+ async function promptSelect(message, options) {
11332
+ if (!isTTY() || !process.stdin.isTTY) {
11333
+ console.error("Cannot show interactive menu in non-interactive mode. Use flags instead.");
11334
+ process.exit(2);
11335
+ }
11336
+ console.error(`
11337
+ ${message}`);
11338
+ for (let i = 0; i < options.length; i++) {
11339
+ console.error(` ${i + 1}. ${options[i].label} \u2014 ${options[i].description}`);
11340
+ }
11341
+ const rl = createInterface2({ input: process.stdin, output: process.stderr });
11342
+ const answer = await new Promise((resolve) => {
11343
+ rl.question("> ", resolve);
11344
+ });
11345
+ rl.close();
11346
+ const index = parseInt(answer, 10) - 1;
11347
+ if (isNaN(index) || index < 0 || index >= options.length) {
11348
+ console.error("Invalid selection.");
11349
+ process.exit(2);
11350
+ }
11351
+ return options[index].value;
11352
+ }
11353
+
11354
+ // src/commands/asset-auth.ts
11355
+ var VALID_AUTH_TYPES = ["bearer", "basic", "apikey", "session", "cookie"];
11356
+ var AUTH_TYPE_OPTIONS = [
11357
+ { value: "bearer", label: "Bearer Token", description: "Authorization header with token" },
11358
+ { value: "apikey", label: "API Key", description: "Custom header (e.g. X-API-Key)" },
11359
+ { value: "basic", label: "Basic Auth", description: "Username + password" },
11360
+ { value: "session", label: "Session Login", description: "Replays login form POST" },
11361
+ { value: "cookie", label: "Cookie / Session", description: "Inject browser cookies" }
11362
+ ];
11363
+ async function collectCredentialsInteractive(authType) {
11364
+ const params = { auth_type: authType, credentials: {} };
11365
+ switch (authType) {
11366
+ case "bearer": {
11367
+ params.credentials = { token: await promptMasked("Bearer token") };
11368
+ break;
11369
+ }
11370
+ case "basic": {
11371
+ const username = await promptText("Username");
11372
+ const password = await promptMasked("Password");
11373
+ params.credentials = { username, password };
11374
+ break;
11375
+ }
11376
+ case "apikey": {
11377
+ const key = await promptMasked("API key");
11378
+ const headerName = await promptText("Header name (e.g. X-API-Key)");
11379
+ params.credentials = { key };
11380
+ params.auth_header_name = headerName;
11381
+ break;
11382
+ }
11383
+ case "session": {
11384
+ const username = await promptText("Username or email");
11385
+ const password = await promptMasked("Password");
11386
+ const endpoint = await promptText("Login endpoint URL");
11387
+ params.credentials = { email: username, password };
11388
+ params.auth_endpoint_url = endpoint;
11389
+ break;
11390
+ }
11391
+ case "cookie": {
11392
+ params.credentials = { cookie: await promptMasked("Cookie string") };
11393
+ break;
11394
+ }
11395
+ }
11396
+ return params;
11397
+ }
11398
+ function collectCredentialsFromFlags(options) {
11399
+ const authType = options.type;
11400
+ const params = { auth_type: authType, credentials: {} };
11401
+ switch (authType) {
11402
+ case "bearer": {
11403
+ if (!options.bearerToken) {
11404
+ console.error("--bearer-token is required for bearer auth type.");
11405
+ process.exit(2);
11406
+ }
11407
+ params.credentials = { token: options.bearerToken };
11408
+ break;
11409
+ }
11410
+ case "basic": {
11411
+ if (!options.username || !options.password) {
11412
+ console.error("--username and --password are required for basic auth type.");
11413
+ process.exit(2);
11414
+ }
11415
+ params.credentials = { username: options.username, password: options.password };
11416
+ break;
11417
+ }
11418
+ case "apikey": {
11419
+ if (!options.key || !options.headerName) {
11420
+ console.error("--key and --header-name are required for apikey auth type.");
11421
+ process.exit(2);
11422
+ }
11423
+ params.credentials = { key: options.key };
11424
+ params.auth_header_name = options.headerName;
11425
+ break;
11426
+ }
11427
+ case "session": {
11428
+ if (!options.username || !options.password || !options.authEndpoint) {
11429
+ console.error("--username, --password, and --auth-endpoint are required for session auth type.");
11430
+ process.exit(2);
11431
+ }
11432
+ params.credentials = { email: options.username, password: options.password };
11433
+ params.auth_endpoint_url = options.authEndpoint;
11434
+ break;
11435
+ }
11436
+ case "cookie": {
11437
+ if (!options.cookie) {
11438
+ console.error("--cookie is required for cookie auth type.");
11439
+ process.exit(2);
11440
+ }
11441
+ params.credentials = { cookie: options.cookie };
11442
+ break;
11443
+ }
11444
+ }
11445
+ if (options.context) {
11446
+ params.context_text = options.context;
11447
+ }
11448
+ return params;
11449
+ }
11450
+ function registerAssetAuthCommand(assetCmd) {
11451
+ const auth = assetCmd.command("auth").description("Manage asset authentication credentials");
11452
+ auth.command("set").description("Configure authentication credentials for an asset").argument("<asset>", "Asset domain name or UUID").option("--type <type>", "Auth type: bearer, basic, apikey, session, cookie").option("--bearer-token <value>", "Bearer token (bearer type)").option("--username <value>", "Username or email (basic, session types)").option("--password <value>", "Password (basic, session types)").option("--key <value>", "API key value (apikey type)").option("--header-name <name>", "Custom header name (apikey type, e.g. X-API-Key)").option("--cookie <value>", "Cookie string (cookie type)").option("--auth-endpoint <url>", "Login endpoint URL (session type)").option("--context <text>", "Optional notes about the auth configuration").addHelpText("after", `
11453
+ Examples:
11454
+ $ kinosec asset auth set example.com # interactive
11455
+ $ kinosec asset auth set example.com --type bearer --bearer-token "$TOKEN"
11456
+ $ kinosec asset auth set example.com --type basic --username admin --password "$PASS"
11457
+ $ kinosec asset auth set example.com --type apikey --key "$KEY" --header-name "X-API-Key"
11458
+ $ kinosec asset auth set example.com --type session --username admin@example.com --password "$PASS" --auth-endpoint "https://example.com/api/login"
11459
+ $ kinosec asset auth set example.com --type cookie --cookie "session_id=abc123"
11460
+
11461
+ Security: Pass credentials via environment variables to avoid shell history exposure.`).action(async (assetIdentifier, options) => {
11462
+ try {
11463
+ const opts = auth.parent.parent.opts();
11464
+ const { token, apiUrl } = resolveToken(opts);
11465
+ const client = new KinoSecClient({ apiUrl, token });
11466
+ const asset = await resolveAsset(client, assetIdentifier);
11467
+ let params;
11468
+ if (options.type) {
11469
+ if (!VALID_AUTH_TYPES.includes(options.type)) {
11470
+ console.error(`Invalid auth type "${options.type}". Must be: ${VALID_AUTH_TYPES.join(", ")}`);
11471
+ process.exit(2);
11472
+ }
11473
+ params = collectCredentialsFromFlags(options);
11474
+ } else {
11475
+ const authType = await promptSelect("Authentication type:", AUTH_TYPE_OPTIONS);
11476
+ params = await collectCredentialsInteractive(authType);
11477
+ if (options.context) {
11478
+ params.context_text = options.context;
11479
+ }
11480
+ }
11481
+ const result = await client.assets.credentials.set(asset.id, params);
11482
+ if (opts.json || opts.output === "json") {
11483
+ printJson({ ...result, asset: asset.asset_value });
11484
+ return;
11485
+ }
11486
+ printSuccess(`Credentials saved for ${asset.asset_value} (${result.auth_type})`);
11487
+ } catch (error) {
11488
+ handleError(error);
11489
+ }
11490
+ });
11491
+ auth.command("show").description("Show authentication configuration for an asset").argument("<asset>", "Asset domain name or UUID").addHelpText("after", `
11492
+ Examples:
11493
+ $ kinosec asset auth show example.com
11494
+ $ kinosec asset auth show example.com --json`).action(async (assetIdentifier) => {
11495
+ try {
11496
+ const opts = auth.parent.parent.opts();
11497
+ const { token, apiUrl } = resolveToken(opts);
11498
+ const client = new KinoSecClient({ apiUrl, token });
11499
+ const asset = await resolveAsset(client, assetIdentifier);
11500
+ const creds = await client.assets.credentials.get(asset.id);
11501
+ if (opts.json || opts.output === "json") {
11502
+ printJson({ asset: asset.asset_value, ...creds });
11503
+ return;
11504
+ }
11505
+ if (!creds.has_credentials) {
11506
+ console.log(`Asset: ${asset.asset_value}`);
11507
+ console.log(`Auth Type: ${import_picocolors7.default.dim("(none)")}`);
11508
+ console.log(`Has Credentials: no`);
11509
+ return;
11510
+ }
11511
+ console.log(`Asset: ${asset.asset_value}`);
11512
+ console.log(`Auth Type: ${creds.auth_type}`);
11513
+ console.log(`Has Credentials: yes`);
11514
+ if (creds.auth_header_name) {
11515
+ console.log(`Header Name: ${creds.auth_header_name}`);
11516
+ }
11517
+ if (creds.auth_endpoint_url) {
11518
+ console.log(`Auth Endpoint: ${creds.auth_endpoint_url}`);
11519
+ }
11520
+ if (creds.context_text) {
11521
+ console.log(`Notes: ${creds.context_text}`);
11522
+ }
11523
+ if (creds.target_account) {
11524
+ const ta = creds.target_account;
11525
+ const hasAny = ta.has_username || ta.has_cookie || ta.has_bearer_token || ta.has_apikey;
11526
+ if (hasAny) {
11527
+ const detail = ta.has_username ? "username" : ta.has_bearer_token ? "bearer token" : ta.has_cookie ? "cookie" : "API key";
11528
+ console.log(`Target Account: ${import_picocolors7.default.green("configured")} (${detail})`);
11529
+ } else {
11530
+ console.log(`Target Account: ${import_picocolors7.default.dim("not configured")}`);
11531
+ }
11532
+ }
11533
+ } catch (error) {
11534
+ handleError(error);
11535
+ }
11536
+ });
11537
+ auth.command("remove").description("Remove all credentials for an asset").argument("<asset>", "Asset domain name or UUID").option("-y, --yes", "Skip confirmation prompt").addHelpText("after", `
11538
+ Examples:
11539
+ $ kinosec asset auth remove example.com
11540
+ $ kinosec asset auth remove example.com --yes`).action(async (assetIdentifier, options) => {
11541
+ try {
11542
+ const opts = auth.parent.parent.opts();
11543
+ const { token, apiUrl } = resolveToken(opts);
11544
+ const client = new KinoSecClient({ apiUrl, token });
11545
+ const asset = await resolveAsset(client, assetIdentifier);
11546
+ await confirmDestructive(`Remove all credentials for ${asset.asset_value}?`, Boolean(options.yes));
11547
+ await client.assets.credentials.remove(asset.id);
11548
+ if (opts.json || opts.output === "json") {
11549
+ printJson({ deleted: true, asset: asset.asset_value });
11550
+ return;
11551
+ }
11552
+ printSuccess(`Credentials removed for ${asset.asset_value}`);
11553
+ } catch (error) {
11554
+ handleError(error);
11555
+ }
11556
+ });
11557
+ auth.command("set-target").description("Configure IDOR target account credentials").argument("<asset>", "Asset domain name or UUID").option("--target-username <value>", "Target account username or email").option("--target-password <value>", "Target account password").option("--target-totp-secret <value>", "Target account TOTP secret").option("--target-token <value>", "Target bearer token").option("--target-cookie <value>", "Target cookie string").option("--target-key <value>", "Target API key").addHelpText("after", `
11558
+ Examples:
11559
+ $ kinosec asset auth set-target example.com # interactive
11560
+ $ kinosec asset auth set-target example.com --target-username victim@example.com --target-password "$PASS"
11561
+ $ kinosec asset auth set-target example.com --target-token "$VICTIM_TOKEN"
11562
+ $ kinosec asset auth set-target example.com --target-cookie "session_id=victim_abc"`).action(async (assetIdentifier, options) => {
11563
+ try {
11564
+ const opts = auth.parent.parent.opts();
11565
+ const { token, apiUrl } = resolveToken(opts);
11566
+ const client = new KinoSecClient({ apiUrl, token });
11567
+ const asset = await resolveAsset(client, assetIdentifier);
11568
+ const creds = await client.assets.credentials.get(asset.id);
11569
+ if (!creds.has_credentials || !creds.auth_type) {
11570
+ console.error(`No primary credentials configured for ${asset.asset_value}. Run 'kinosec asset auth set' first.`);
11571
+ process.exit(2);
11572
+ }
11573
+ const isTokenBased = creds.auth_type === "bearer" || creds.auth_type === "apikey" || creds.auth_type === "cookie";
11574
+ const hasAnyFlag = options.targetUsername || options.targetPassword || options.targetTotpSecret || options.targetToken || options.targetCookie || options.targetKey;
11575
+ let targetParams;
11576
+ if (hasAnyFlag) {
11577
+ targetParams = {
11578
+ username: options.targetUsername,
11579
+ password: options.targetPassword,
11580
+ totp_secret: options.targetTotpSecret,
11581
+ bearer_token: options.targetToken,
11582
+ cookie: options.targetCookie,
11583
+ apikey: options.targetKey
11584
+ };
11585
+ } else {
11586
+ console.error(`
11587
+ Primary auth type: ${creds.auth_type}
11588
+ `);
11589
+ if (isTokenBased) {
11590
+ if (creds.auth_type === "bearer") {
11591
+ const targetToken = await promptMasked("Target bearer token");
11592
+ targetParams = { bearer_token: targetToken };
11593
+ } else if (creds.auth_type === "cookie") {
11594
+ const targetCookie = await promptMasked("Target cookie string");
11595
+ targetParams = { cookie: targetCookie };
11596
+ } else {
11597
+ const targetKey = await promptMasked("Target API key");
11598
+ targetParams = { apikey: targetKey };
11076
11599
  }
11077
- if (scanDetail.scan_status.includes("Stopped")) {
11078
- console.log(import_picocolors5.default.yellow("\nPentest was cancelled."));
11079
- break;
11600
+ } else {
11601
+ const username = await promptText("Target account username");
11602
+ const password = await promptMasked("Target account password");
11603
+ targetParams = { username, password };
11604
+ const totp = await promptText("Target TOTP secret (optional, Enter to skip)");
11605
+ if (totp) {
11606
+ targetParams.totp_secret = totp;
11080
11607
  }
11081
- console.log(import_picocolors5.default.green(`
11082
- Pentest completed: ${scanDetail.domain_or_ip}`));
11083
- console.log(` View findings: kinosec finding list ${id}`);
11084
- break;
11085
- }
11086
- if (Date.now() - startTime > timeoutMs) {
11087
- cleanup();
11088
- console.error(`
11089
- Timeout: pentest still running after ${options.timeout}s.`);
11090
- console.error(`Check status: kinosec pentest get ${id}`);
11091
- process.exit(3);
11092
11608
  }
11093
- await new Promise((resolve) => setTimeout(resolve, intervalMs));
11094
11609
  }
11610
+ await client.assets.credentials.setTarget(asset.id, targetParams);
11611
+ if (opts.json || opts.output === "json") {
11612
+ printJson({ saved: true, asset: asset.asset_value });
11613
+ return;
11614
+ }
11615
+ printSuccess(`Target account saved for ${asset.asset_value} (IDOR testing)`);
11616
+ } catch (error) {
11617
+ handleError(error);
11618
+ }
11619
+ });
11620
+ auth.command("remove-target").description("Remove target account credentials (keeps primary credentials)").argument("<asset>", "Asset domain name or UUID").option("-y, --yes", "Skip confirmation prompt").addHelpText("after", `
11621
+ Examples:
11622
+ $ kinosec asset auth remove-target example.com
11623
+ $ kinosec asset auth remove-target example.com --yes`).action(async (assetIdentifier, options) => {
11624
+ try {
11625
+ const opts = auth.parent.parent.opts();
11626
+ const { token, apiUrl } = resolveToken(opts);
11627
+ const client = new KinoSecClient({ apiUrl, token });
11628
+ const asset = await resolveAsset(client, assetIdentifier);
11629
+ await confirmDestructive(`Remove target account for ${asset.asset_value}?`, Boolean(options.yes));
11630
+ await client.assets.credentials.removeTarget(asset.id);
11631
+ if (opts.json || opts.output === "json") {
11632
+ printJson({ deleted: true, asset: asset.asset_value });
11633
+ return;
11634
+ }
11635
+ printSuccess(`Target account removed for ${asset.asset_value}`);
11095
11636
  } catch (error) {
11096
11637
  handleError(error);
11097
11638
  }
@@ -11099,7 +11640,6 @@ Timeout: pentest still running after ${options.timeout}s.`);
11099
11640
  }
11100
11641
 
11101
11642
  // src/commands/asset.ts
11102
- var import_picocolors6 = __toESM(require_picocolors(), 1);
11103
11643
  function registerAssetCommand(program3) {
11104
11644
  const asset = program3.command("asset").description("Manage assets");
11105
11645
  asset.command("list").description("List assets").option("--limit <n>", "Max results per page", "20").option("--cursor <cursor>", "Pagination cursor").option("--search <term>", "Filter by asset value (substring match)").option("--status <status>", "Filter by status (verified, unverified)").option("--type <type>", "Filter by type (domain, subdomain, ip)").addHelpText("after", `
@@ -11138,7 +11678,7 @@ Examples:
11138
11678
  ])
11139
11679
  );
11140
11680
  if (result.next_cursor) {
11141
- console.log(import_picocolors6.default.dim(`
11681
+ console.log(import_picocolors8.default.dim(`
11142
11682
  More results available. Use --cursor ${result.next_cursor}`));
11143
11683
  }
11144
11684
  } catch (error) {
@@ -11246,10 +11786,11 @@ Examples:
11246
11786
  handleError(error);
11247
11787
  }
11248
11788
  });
11789
+ registerAssetAuthCommand(asset);
11249
11790
  }
11250
11791
 
11251
11792
  // src/commands/finding.ts
11252
- var import_picocolors7 = __toESM(require_picocolors(), 1);
11793
+ var import_picocolors9 = __toESM(require_picocolors(), 1);
11253
11794
  function formatPoc(poc) {
11254
11795
  if (poc == null) return null;
11255
11796
  if (typeof poc === "string") return poc.trim() || null;
@@ -11290,7 +11831,7 @@ Examples:
11290
11831
  ])
11291
11832
  );
11292
11833
  if (result.next_cursor) {
11293
- console.log(import_picocolors7.default.dim(`
11834
+ console.log(import_picocolors9.default.dim(`
11294
11835
  More results available. Use --cursor ${result.next_cursor}`));
11295
11836
  }
11296
11837
  } catch (error) {
@@ -11378,7 +11919,7 @@ Examples:
11378
11919
  }
11379
11920
 
11380
11921
  // src/commands/report.ts
11381
- var import_picocolors8 = __toESM(require_picocolors(), 1);
11922
+ var import_picocolors10 = __toESM(require_picocolors(), 1);
11382
11923
  function renderMarkdown(data) {
11383
11924
  const { scan, findings } = data;
11384
11925
  const lines = [];
@@ -11455,7 +11996,7 @@ Examples:
11455
11996
  ])
11456
11997
  );
11457
11998
  if (result.next_cursor) {
11458
- console.log(import_picocolors8.default.dim(`
11999
+ console.log(import_picocolors10.default.dim(`
11459
12000
  More results available. Use --cursor ${result.next_cursor}`));
11460
12001
  }
11461
12002
  } catch (error) {
@@ -11526,7 +12067,7 @@ Examples:
11526
12067
  }
11527
12068
 
11528
12069
  // src/commands/token.ts
11529
- var import_picocolors9 = __toESM(require_picocolors(), 1);
12070
+ var import_picocolors11 = __toESM(require_picocolors(), 1);
11530
12071
  function registerTokenCommand(program3) {
11531
12072
  const token = program3.command("token").description("Manage API tokens");
11532
12073
  token.command("list").description("List API tokens").option("--limit <n>", "Max results per page", "20").option("--cursor <cursor>", "Pagination cursor").addHelpText("after", `
@@ -11561,7 +12102,7 @@ Examples:
11561
12102
  ])
11562
12103
  );
11563
12104
  if (result.next_cursor) {
11564
- console.log(import_picocolors9.default.dim(`
12105
+ console.log(import_picocolors11.default.dim(`
11565
12106
  More results available. Use --cursor ${result.next_cursor}`));
11566
12107
  }
11567
12108
  } catch (error) {
@@ -11588,7 +12129,7 @@ Examples:
11588
12129
  }
11589
12130
  printSuccess(`Token created: ${result.name}`);
11590
12131
  console.log("");
11591
- console.log(import_picocolors9.default.bold(" Token: ") + result.token);
12132
+ console.log(import_picocolors11.default.bold(" Token: ") + result.token);
11592
12133
  console.log("");
11593
12134
  printWarning("Save this token \u2014 it will not be shown again.");
11594
12135
  console.log(` ID: ${result.id}`);
@@ -11621,7 +12162,7 @@ Examples:
11621
12162
 
11622
12163
  // src/cli.ts
11623
12164
  var program2 = new Command();
11624
- program2.name("kinosec").description("KinoSec CLI \u2014 run pentests from your terminal").version("0.3.3", "-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.5.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");
11625
12166
  registerAuthCommand(program2);
11626
12167
  registerConfigCommand(program2);
11627
12168
  registerMeCommand(program2);
@@ -11630,7 +12171,7 @@ registerAssetCommand(program2);
11630
12171
  registerFindingCommand(program2);
11631
12172
  registerReportCommand(program2);
11632
12173
  registerTokenCommand(program2);
11633
- program2.parseAsync(process.argv).then(() => checkForUpdate("0.3.3")).catch(handleError);
12174
+ program2.parseAsync(process.argv).then(() => checkForUpdate("0.5.0")).catch(handleError);
11634
12175
  /*! Bundled license information:
11635
12176
 
11636
12177
  ky/distribution/index.js: