@kinosec/cli 0.11.1 → 0.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -8435,28 +8435,111 @@ ${import_picocolors3.default.dim("use of its services.")}
8435
8435
  // src/commands/schedule.ts
8436
8436
  var import_picocolors4 = __toESM(require_picocolors(), 1);
8437
8437
 
8438
- // src/lib/validate-mode.ts
8439
- var VALID_SCAN_MODES = ["full", "quick", "htb", "defcon", "exploit-only"];
8440
- function validateScanMode(mode) {
8441
- if (!VALID_SCAN_MODES.includes(mode)) {
8442
- console.error(`Invalid mode "${mode}". Must be one of: ${VALID_SCAN_MODES.join(", ")}.`);
8443
- process.exit(2);
8438
+ // src/lib/validate-schedule.ts
8439
+ var SCHEDULE_PATTERNS = ["once", "daily", "weekly", "biweekly", "monthly"];
8440
+ var SCHEDULE_MODES = ["quick", "full"];
8441
+ var TIME_OF_DAY = /^([01]?\d|2[0-3]):[0-5]\d$/;
8442
+ var DIGITS = /^\d+$/;
8443
+ var ONCE_AT = /^(\d{4})-(\d{2})-(\d{2})T([01]\d|2[0-3]):[0-5]\d(:[0-5]\d(\.\d{3})?)?(Z|[+-]([01]\d|2[0-3]):[0-5]\d)$/;
8444
+ function scheduleInputError(flags, now) {
8445
+ const { pattern, mode, at, time, timezone, dayOfWeek, dayOfMonth } = flags;
8446
+ if (pattern !== void 0 && !SCHEDULE_PATTERNS.includes(pattern)) {
8447
+ return `Invalid --pattern "${pattern}". Must be one of: ${SCHEDULE_PATTERNS.join(", ")}.`;
8448
+ }
8449
+ if (mode !== void 0 && !SCHEDULE_MODES.includes(mode)) {
8450
+ return `Invalid --mode "${mode}". Schedules support: ${SCHEDULE_MODES.join(", ")}.`;
8451
+ }
8452
+ if (at !== void 0) {
8453
+ const atError = onceAtError(at, now);
8454
+ if (atError) return atError;
8455
+ }
8456
+ if (time !== void 0 && !TIME_OF_DAY.test(time)) {
8457
+ return `Invalid --time "${time}". Use 24-hour HH:MM, e.g. 09:00.`;
8458
+ }
8459
+ if (timezone !== void 0 && !isKnownTimezone(timezone)) {
8460
+ return `Invalid --timezone "${timezone}". Use an IANA name, e.g. America/New_York.`;
8461
+ }
8462
+ if (dayOfWeek !== void 0 && !isWholeNumberIn(dayOfWeek, 0, 6)) {
8463
+ return `Invalid --day-of-week "${dayOfWeek}". Use a whole number 0-6 (0=Sunday).`;
8464
+ }
8465
+ if (dayOfMonth !== void 0 && !isWholeNumberIn(dayOfMonth, 1, 31)) {
8466
+ return `Invalid --day-of-month "${dayOfMonth}". Use a whole number 1-31.`;
8467
+ }
8468
+ if (pattern === void 0 && at !== void 0) return "--at requires --pattern once.";
8469
+ if (pattern === void 0) return null;
8470
+ return patternFitError(pattern, flags);
8471
+ }
8472
+ function patternFitError(pattern, flags) {
8473
+ if (pattern === "once" && flags.at === void 0) {
8474
+ return "--at is required for once schedules: YYYY-MM-DDTHH:MM followed by Z or \xB1HH:MM.";
8475
+ }
8476
+ if (pattern === "once" && flags.time !== void 0) {
8477
+ return "--time does not apply to once schedules; put the time in --at.";
8478
+ }
8479
+ if (pattern !== "once" && flags.at !== void 0) {
8480
+ return "--at only applies to once schedules.";
8481
+ }
8482
+ const isWeekly = pattern === "weekly" || pattern === "biweekly";
8483
+ if (isWeekly && flags.dayOfWeek === void 0) {
8484
+ return `--day-of-week is required for ${pattern} schedules.`;
8485
+ }
8486
+ if (!isWeekly && flags.dayOfWeek !== void 0) {
8487
+ return "--day-of-week only applies to weekly and biweekly schedules.";
8444
8488
  }
8489
+ if (pattern === "monthly" && flags.dayOfMonth === void 0) {
8490
+ return "--day-of-month is required for monthly schedules.";
8491
+ }
8492
+ if (pattern !== "monthly" && flags.dayOfMonth !== void 0) {
8493
+ return "--day-of-month only applies to monthly schedules.";
8494
+ }
8495
+ return null;
8496
+ }
8497
+ function onceAtError(at, now) {
8498
+ const match = ONCE_AT.exec(at);
8499
+ const ms = Date.parse(at);
8500
+ if (!match || Number.isNaN(ms) || !isCalendarDate(Number(match[1]), Number(match[2]), Number(match[3]))) {
8501
+ return `Invalid --at "${at}". Use YYYY-MM-DDTHH:MM followed by Z or \xB1HH:MM, on a real date.`;
8502
+ }
8503
+ if (ms <= now.getTime()) return `--at "${at}" is in the past. Pick a future time.`;
8504
+ return null;
8505
+ }
8506
+ function isCalendarDate(year, month, day) {
8507
+ const date = new Date(Date.UTC(year, month - 1, day));
8508
+ return date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
8509
+ }
8510
+ function isKnownTimezone(timezone) {
8511
+ try {
8512
+ new Intl.DateTimeFormat("en-US", { timeZone: timezone });
8513
+ return true;
8514
+ } catch (error2) {
8515
+ if (error2 instanceof RangeError) return false;
8516
+ throw error2;
8517
+ }
8518
+ }
8519
+ function isWholeNumberIn(value, min, max) {
8520
+ if (!DIGITS.test(value)) return false;
8521
+ const n = Number(value);
8522
+ return n >= min && n <= max;
8445
8523
  }
8446
8524
 
8447
8525
  // src/commands/schedule.ts
8448
- var VALID_PATTERNS = ["once", "daily", "weekly", "biweekly", "monthly"];
8449
8526
  var DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
8450
8527
  function extractTime(scheduledAt) {
8451
8528
  if (!scheduledAt) return "\u2014";
8452
8529
  const d = new Date(scheduledAt);
8453
8530
  return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", hour12: false });
8454
8531
  }
8532
+ function extractDate(scheduledAt) {
8533
+ if (!scheduledAt) return "\u2014";
8534
+ const d = new Date(scheduledAt);
8535
+ const pad = (n) => String(n).padStart(2, "0");
8536
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
8537
+ }
8455
8538
  function formatPattern(s) {
8456
8539
  const time = extractTime(s.scheduled_at);
8457
8540
  switch (s.recurrence_pattern) {
8458
8541
  case "once":
8459
- return `Once at ${time}`;
8542
+ return `Once on ${extractDate(s.scheduled_at)} at ${time}`;
8460
8543
  case "daily":
8461
8544
  return `Daily at ${time}`;
8462
8545
  case "weekly":
@@ -8469,6 +8552,22 @@ function formatPattern(s) {
8469
8552
  return s.recurrence_pattern;
8470
8553
  }
8471
8554
  }
8555
+ function modeTypedOnPentest(pentestCmd) {
8556
+ return pentestCmd.getOptionValueSource("mode") === "cli" ? String(pentestCmd.getOptionValue("mode")) : void 0;
8557
+ }
8558
+ function yesTypedOnPentest(pentestCmd) {
8559
+ return pentestCmd.getOptionValueSource("yes") === "cli";
8560
+ }
8561
+ function exitOnInputError(flags) {
8562
+ const error2 = scheduleInputError(flags, /* @__PURE__ */ new Date());
8563
+ if (!error2) return;
8564
+ console.error(error2);
8565
+ process.exit(2);
8566
+ }
8567
+ function scheduleMode(mode) {
8568
+ if (mode === "quick" || mode === "full") return mode;
8569
+ throw new Error(`Unchecked schedule mode "${mode}".`);
8570
+ }
8472
8571
  function registerScheduleCommand(pentestCmd) {
8473
8572
  const schedule = pentestCmd.command("schedule").description("Manage scheduled pentests");
8474
8573
  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", `
@@ -8543,36 +8642,35 @@ Examples:
8543
8642
  handleError(error2);
8544
8643
  }
8545
8644
  });
8546
- 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 (default: 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", `
8645
+ 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 (default: full)", "full").option("--at <datetime>", "Run time for --pattern once: YYYY-MM-DDTHH:MM followed by Z or \xB1HH:MM").option("--time <HH:MM>", "Time of day, 24h, for recurring patterns (default: 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", `
8547
8646
  Examples:
8548
8647
  $ kinosec pentest schedule create --asset abc123 --pattern weekly --day-of-week 1 --time 09:00
8549
8648
  $ kinosec pentest schedule create --asset abc123 --pattern daily --time 14:00
8550
- $ kinosec pentest schedule create --asset abc123 --pattern monthly --day-of-month 15 --time 03:00 --timezone UTC`).action(async (options) => {
8551
- if (!VALID_PATTERNS.includes(options.pattern)) {
8552
- console.error(`Invalid pattern "${options.pattern}". Must be: ${VALID_PATTERNS.join(", ")}`);
8553
- process.exit(2);
8554
- }
8555
- validateScanMode(options.mode);
8556
- if ((options.pattern === "weekly" || options.pattern === "biweekly") && options.dayOfWeek === void 0) {
8557
- console.error(`--day-of-week is required for ${options.pattern} schedules.`);
8558
- process.exit(2);
8559
- }
8560
- if (options.pattern === "monthly" && options.dayOfMonth === void 0) {
8561
- console.error("--day-of-month is required for monthly schedules.");
8562
- process.exit(2);
8563
- }
8649
+ $ kinosec pentest schedule create --asset abc123 --pattern monthly --day-of-month 15 --time 03:00 --timezone UTC
8650
+ $ kinosec pentest schedule create --asset abc123 --pattern once --at 2099-12-01T09:00+01:00`).action(async (options) => {
8651
+ const mode = modeTypedOnPentest(pentestCmd) ?? options.mode;
8652
+ exitOnInputError({
8653
+ pattern: options.pattern,
8654
+ mode,
8655
+ at: options.at,
8656
+ time: options.time,
8657
+ timezone: options.timezone,
8658
+ dayOfWeek: options.dayOfWeek,
8659
+ dayOfMonth: options.dayOfMonth
8660
+ });
8564
8661
  try {
8565
8662
  const opts = schedule.parent.parent.opts();
8566
8663
  const { token, apiUrl } = resolveToken(opts);
8567
8664
  const client = new KinoSecClient({ apiUrl, token });
8568
8665
  const params = {
8569
- asset_id: options.domain_or_ip,
8570
- name: options.name ?? `${options.pattern} ${options.mode} pentest`,
8571
- scan_mode: options.mode,
8666
+ asset_id: options.asset,
8667
+ name: options.name ?? `${options.pattern} ${mode} pentest`,
8668
+ scan_mode: scheduleMode(mode),
8572
8669
  recurrence_pattern: options.pattern,
8573
- time_of_day: options.time,
8574
8670
  timezone: options.timezone
8575
8671
  };
8672
+ if (options.pattern === "once") params.once_at = new Date(options.at).toISOString();
8673
+ else params.time_of_day = options.time ?? "09:00";
8576
8674
  if (options.dayOfWeek !== void 0) params.day_of_week = parseInt(options.dayOfWeek, 10);
8577
8675
  if (options.dayOfMonth !== void 0) params.day_of_month = parseInt(options.dayOfMonth, 10);
8578
8676
  const s = await client.schedules.create(params);
@@ -8590,17 +8688,26 @@ Manage: kinosec pentest schedule get ${s.id}`);
8590
8688
  handleError(error2);
8591
8689
  }
8592
8690
  });
8593
- 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").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", `
8691
+ 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("--at <datetime>", "Run time with --pattern once: ISO date and time with an offset").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", `
8594
8692
  Examples:
8595
8693
  $ kinosec pentest schedule update abc123 --pattern daily --time 14:00
8596
- $ kinosec pentest schedule update abc123 --time 14:00`).action(async (id, options) => {
8694
+ $ kinosec pentest schedule update abc123 --time 14:00
8695
+ $ kinosec pentest schedule update abc123 --pattern once --at 2099-12-01T09:00Z`).action(async (id, options) => {
8696
+ const mode = modeTypedOnPentest(pentestCmd) ?? options.mode;
8697
+ exitOnInputError({
8698
+ pattern: options.pattern,
8699
+ mode,
8700
+ at: options.at,
8701
+ time: options.time,
8702
+ timezone: options.timezone,
8703
+ dayOfWeek: options.dayOfWeek,
8704
+ dayOfMonth: options.dayOfMonth
8705
+ });
8597
8706
  const params = {};
8598
8707
  if (options.pattern) params.recurrence_pattern = options.pattern;
8599
8708
  if (options.name) params.name = options.name;
8600
- if (options.mode) {
8601
- validateScanMode(options.mode);
8602
- params.scan_mode = options.mode;
8603
- }
8709
+ if (mode) params.scan_mode = scheduleMode(mode);
8710
+ if (options.at !== void 0) params.once_at = new Date(options.at).toISOString();
8604
8711
  if (options.time) params.time_of_day = options.time;
8605
8712
  if (options.dayOfWeek !== void 0) params.day_of_week = parseInt(options.dayOfWeek, 10);
8606
8713
  if (options.dayOfMonth !== void 0) params.day_of_month = parseInt(options.dayOfMonth, 10);
@@ -8630,7 +8737,7 @@ Examples:
8630
8737
  $ kinosec pentest schedule delete abc123
8631
8738
  $ kinosec pentest schedule delete abc123 --yes`).action(async (id, options) => {
8632
8739
  try {
8633
- await confirmDestructive(`Delete schedule ${id}?`, Boolean(options.yes));
8740
+ await confirmDestructive(`Delete schedule ${id}?`, Boolean(options.yes) || yesTypedOnPentest(pentestCmd));
8634
8741
  const opts = schedule.parent.parent.opts();
8635
8742
  const { token, apiUrl } = resolveToken(opts);
8636
8743
  const client = new KinoSecClient({ apiUrl, token });
@@ -11904,18 +12011,6 @@ function normalizeActivityHistory(raw) {
11904
12011
  return JSON.stringify(entry);
11905
12012
  });
11906
12013
  }
11907
- function filterActivityForTier(entries, showFullDetails) {
11908
- if (showFullDetails) return entries;
11909
- return entries.filter((line) => !/^\s{4,}/.test(line)).map((line) => {
11910
- if (/\] Running:/.test(line)) {
11911
- return line.replace(
11912
- /Running:.*/,
11913
- "Running: [upgrade to Security Pro for full details]"
11914
- );
11915
- }
11916
- return line;
11917
- });
11918
- }
11919
12014
 
11920
12015
  // src/lib/buzzwords.ts
11921
12016
  var SCAN_EXECUTION_PHRASES = [
@@ -12019,15 +12114,6 @@ function printActivityLines(entries, tty3) {
12019
12114
  console.log(`${prompt} ${text}`);
12020
12115
  }
12021
12116
  }
12022
- async function resolveShowFullDetails(client) {
12023
- try {
12024
- const me = await client.me.get();
12025
- const slug = me.plan.slug;
12026
- return slug === "security_pro" || slug === "enterprise";
12027
- } catch {
12028
- return false;
12029
- }
12030
- }
12031
12117
  async function watchPentest(client, profileId, options) {
12032
12118
  const { timeoutMs, intervalMs, useJson, showFindingsSummary } = options;
12033
12119
  const tty3 = isTTY();
@@ -12056,7 +12142,6 @@ async function watchPentest(client, profileId, options) {
12056
12142
  console.log("\nWatch cancelled. Pentest is still running.");
12057
12143
  process.exit(0);
12058
12144
  });
12059
- const showFullDetails = await resolveShowFullDetails(client);
12060
12145
  let consecutiveErrors = 0;
12061
12146
  const MAX_CONSECUTIVE_ERRORS = 2;
12062
12147
  let deferredExitCode = null;
@@ -12104,17 +12189,16 @@ ${icon2} Lost connection to pentest.`);
12104
12189
  JSON.stringify({ ...scanDetail, elapsed_seconds: elapsed }) + "\n"
12105
12190
  );
12106
12191
  } else {
12107
- const normalized = normalizeActivityHistory(scanDetail.activity_history);
12108
- const filtered = filterActivityForTier(normalized, showFullDetails);
12109
- if (printedCount > filtered.length) printedCount = 0;
12110
- const newEntries = filtered.slice(printedCount);
12192
+ const entries = normalizeActivityHistory(scanDetail.activity_history);
12193
+ if (printedCount > entries.length) printedCount = 0;
12194
+ const newEntries = entries.slice(printedCount);
12111
12195
  if (newEntries.length > 0) {
12112
12196
  if (spinner) spinner.stop();
12113
12197
  printActivityLines(newEntries, tty3);
12114
12198
  printedCount += newEntries.length;
12115
12199
  if (spinner) spinner = ora({ color: "green", isEnabled: true, discardStdin: false }).start();
12116
12200
  }
12117
- currentPhase = extractPhase(filtered);
12201
+ currentPhase = extractPhase(entries);
12118
12202
  updateSpinnerText();
12119
12203
  }
12120
12204
  const status = scanDetail.scan_status;
@@ -12317,6 +12401,15 @@ Timeout: Scout did not connect within ${secs}s.`);
12317
12401
  }
12318
12402
  }
12319
12403
 
12404
+ // src/lib/validate-mode.ts
12405
+ var VALID_SCAN_MODES = ["full", "quick", "htb", "defcon", "exploit-only"];
12406
+ function validateScanMode(mode) {
12407
+ if (!VALID_SCAN_MODES.includes(mode)) {
12408
+ console.error(`Invalid mode "${mode}". Must be one of: ${VALID_SCAN_MODES.join(", ")}.`);
12409
+ process.exit(2);
12410
+ }
12411
+ }
12412
+
12320
12413
  // src/lib/scope.ts
12321
12414
  var import_picocolors7 = __toESM(require_picocolors(), 1);
12322
12415
 
@@ -12778,12 +12871,10 @@ Examples:
12778
12871
  $ kinosec pentest -t target.htb --mode htb --source ./challenge.zip --zip-password hackthebox
12779
12872
 
12780
12873
  Graybox (--source):
12781
- Packs the target's source (paste-mode only) and folds it into the agent as authoritative
12782
- context. Works with --mode full and quick for any user's own asset; htb/defcon additionally
12783
- require admin access. Large source is delivered out-of-band, so there's no size ceiling to
12784
- fight \u2014 --budget just controls how much source is packed (bigger bundles add context and cost).
12785
- The source is sent over HTTPS, used only on the ephemeral scan droplet, and is NOT stored
12786
- server-side (the training log records just a "[GRAYBOX] N bytes injected" marker, never the source).`).action(async (options) => {
12874
+ Packs the target's source and sends it with the pentest as extra context.
12875
+ Works with --mode full and quick on your own assets. --budget controls how much
12876
+ source is packed (bigger bundles add context and cost).
12877
+ The source is sent over HTTPS, used only while the pentest runs, and is NOT stored.`).action(async (options) => {
12787
12878
  if (!options.target && !options.profile) {
12788
12879
  console.error("Provide --target <domain> or --profile <id>.");
12789
12880
  process.exit(2);
@@ -13456,10 +13547,9 @@ Examples:
13456
13547
  // src/commands/asset.ts
13457
13548
  function registerAssetCommand(program3) {
13458
13549
  const asset = program3.command("asset").description("Manage assets");
13459
- 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", `
13550
+ asset.command("list").description("List assets").option("--limit <n>", "Max results per page", "20").option("--cursor <cursor>", "Pagination cursor").option("--status <status>", "Filter by status (verified, unverified)").option("--type <type>", "Filter by type (domain, subdomain, ip)").addHelpText("after", `
13460
13551
  Examples:
13461
13552
  $ kinosec asset list
13462
- $ kinosec asset list --search example.com
13463
13553
  $ kinosec asset list --type subdomain --status verified
13464
13554
  $ kinosec asset list --limit 50 --json`).action(async (options) => {
13465
13555
  try {
@@ -13469,7 +13559,6 @@ Examples:
13469
13559
  const result = await client.assets.list({
13470
13560
  limit: parseInt(options.limit, 10),
13471
13561
  cursor: options.cursor,
13472
- search: options.search,
13473
13562
  status: options.status,
13474
13563
  type: options.type
13475
13564
  });
@@ -13704,7 +13793,7 @@ function registerFindingCommand(program3) {
13704
13793
  const finding = program3.command("finding").description("Manage findings");
13705
13794
  finding.command("list").description("List findings").argument("[profile-id]", "Pentest profile ID").option("--limit <n>", "Max results per page", "20").option("--cursor <cursor>", "Pagination cursor").addHelpText("after", `
13706
13795
  Examples:
13707
- $ kinosec finding list abc123
13796
+ $ kinosec finding list a1b2c3d4-0000-0000-0000-000000000001
13708
13797
  $ kinosec finding list --limit 50 --json`).action(async (profileId, options) => {
13709
13798
  try {
13710
13799
  const opts = program3.opts();
@@ -13713,7 +13802,7 @@ Examples:
13713
13802
  const result = await client.findings.list({
13714
13803
  limit: parseInt(options.limit, 10),
13715
13804
  cursor: options.cursor,
13716
- scan: profileId
13805
+ scan_profile_id: profileId
13717
13806
  });
13718
13807
  if (opts.json || opts.output === "json") {
13719
13808
  printJson(result);
@@ -13970,6 +14059,7 @@ Examples:
13970
14059
 
13971
14060
  // src/commands/token.ts
13972
14061
  var import_picocolors13 = __toESM(require_picocolors(), 1);
14062
+ var WEB_APP_TOKENS_URL = "https://kinosec.ai/settings?tab=api-tokens";
13973
14063
  function registerTokenCommand(program3) {
13974
14064
  const token = program3.command("token").description("Manage API tokens");
13975
14065
  token.command("list").description("List API tokens").option("--limit <n>", "Max results per page", "20").option("--cursor <cursor>", "Pagination cursor").addHelpText("after", `
@@ -14015,8 +14105,8 @@ More results available. Use --cursor ${result.next_cursor}`));
14015
14105
  Examples:
14016
14106
  $ kinosec token create --name "CI" --scopes assets:read,scans:create
14017
14107
  $ kinosec token create --name "Full" --scopes assets:read,scans:read --expires 30`).action(async (options) => {
14108
+ const opts = program3.opts();
14018
14109
  try {
14019
- const opts = program3.opts();
14020
14110
  const { token: authToken, apiUrl } = resolveToken(opts);
14021
14111
  const client = new KinoSecClient({ apiUrl, token: authToken });
14022
14112
  const scopes = options.scopes.split(",").map((s) => s.trim());
@@ -14038,6 +14128,13 @@ Examples:
14038
14128
  console.log(` Scopes: ${result.scopes.join(", ")}`);
14039
14129
  console.log(` Expires: ${new Date(result.expires_at).toLocaleDateString()}`);
14040
14130
  } catch (error2) {
14131
+ const isJson = opts.json || opts.output === "json";
14132
+ if (error2 instanceof ApiError && error2.code === "pat_cannot_create_token" && !isJson) {
14133
+ printWarning("API tokens can't create other API tokens.");
14134
+ console.error("Create a new token in the KinoSec web app:");
14135
+ console.error(` ${WEB_APP_TOKENS_URL}`);
14136
+ console.error("");
14137
+ }
14041
14138
  handleError(error2);
14042
14139
  }
14043
14140
  });
@@ -14064,7 +14161,7 @@ Examples:
14064
14161
 
14065
14162
  // src/cli.ts
14066
14163
  var program2 = new Command();
14067
- program2.name("kinosec").description("KinoSec CLI \u2014 run pentests from your terminal").version("0.11.1", "-v, --version").option("--token <pat>", "API token (overrides KINOSEC_TOKEN)").option("--output <format>", "Output format: text or json", "text").option("--json", "Shorthand for --output json").option("--no-color", "Disable colors");
14164
+ program2.name("kinosec").description("KinoSec CLI \u2014 run pentests from your terminal").version("0.11.2", "-v, --version").option("--token <pat>", "API token (overrides KINOSEC_TOKEN)").option("--output <format>", "Output format: text or json", "text").option("--json", "Shorthand for --output json").option("--no-color", "Disable colors");
14068
14165
  registerAuthCommand(program2);
14069
14166
  registerMeCommand(program2);
14070
14167
  registerCreditsCommand(program2);
@@ -14073,10 +14170,9 @@ registerAssetCommand(program2);
14073
14170
  registerFindingCommand(program2);
14074
14171
  registerReportCommand(program2);
14075
14172
  registerTokenCommand(program2);
14076
- program2.parseAsync(process.argv).then(() => checkForUpdate("0.11.1")).catch(handleError);
14173
+ program2.parseAsync(process.argv).then(() => checkForUpdate("0.11.2")).catch(handleError);
14077
14174
  /*! Bundled license information:
14078
14175
 
14079
14176
  ky/distribution/index.js:
14080
14177
  (*! MIT License © Sindre Sorhus *)
14081
14178
  */
14082
- //# sourceMappingURL=cli.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kinosec/cli",
3
- "version": "0.11.1",
3
+ "version": "0.11.2",
4
4
  "description": "Official KinoSec command-line interface",
5
5
  "type": "module",
6
6
  "bin": {