@kinosec/cli 0.1.4 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,10 +17,6 @@ Or run without installing:
17
17
  npx @kinosec/cli scan list
18
18
  ```
19
19
 
20
- Or download a standalone binary from [GitHub Releases](https://github.com/kinosec-ai/cli/releases) — no Node.js required.
21
-
22
- See [docs/install.md](docs/install.md) for all installation methods.
23
-
24
20
  ## Quick Start
25
21
 
26
22
  ```bash
@@ -36,6 +32,9 @@ kinosec scan create --asset <id> --mode quick
36
32
  # Watch progress in real-time
37
33
  kinosec scan watch <scan-id>
38
34
 
35
+ # Schedule a weekly scan
36
+ kinosec scan schedule create --asset <id> --pattern weekly --day-of-week 1 --time 09:00
37
+
39
38
  # View findings
40
39
  kinosec finding list --scan <scan-id>
41
40
 
@@ -43,8 +42,6 @@ kinosec finding list --scan <scan-id>
43
42
  kinosec report export --scan <report-id> --format markdown > report.md
44
43
  ```
45
44
 
46
- See [docs/quickstart.md](docs/quickstart.md) for a full walkthrough.
47
-
48
45
  ## Commands
49
46
 
50
47
  ```
@@ -54,6 +51,7 @@ kinosec me Show authenticated user info
54
51
 
55
52
  kinosec asset list|get|create|delete|verify Manage assets
56
53
  kinosec scan list|get|create|cancel|watch Manage security scans
54
+ kinosec scan schedule create|list|get|... Schedule recurring scans
57
55
  kinosec finding list|get|update|retest Manage findings
58
56
  kinosec report list|get|export Manage reports
59
57
  kinosec token list|create|revoke Manage API tokens
@@ -61,8 +59,6 @@ kinosec token list|create|revoke Manage API tokens
61
59
 
62
60
  Every command supports `--json` for machine-readable output and `--help` for usage details.
63
61
 
64
- See [docs/commands.md](docs/commands.md) for the full command reference.
65
-
66
62
  ## Authentication
67
63
 
68
64
  The CLI authenticates via Personal Access Tokens (PATs). Tokens are resolved in this order:
@@ -71,8 +67,6 @@ The CLI authenticates via Personal Access Tokens (PATs). Tokens are resolved in
71
67
  2. `KINOSEC_TOKEN` environment variable
72
68
  3. `~/.config/kinosec/credentials.json` (saved by `kinosec auth login`)
73
69
 
74
- See [docs/authentication.md](docs/authentication.md) for details.
75
-
76
70
  ## Example
77
71
 
78
72
  ```bash
@@ -93,12 +87,11 @@ f1a2b3c4-... SQL Injection Critical open active_
93
87
 
94
88
  ## Requirements
95
89
 
96
- - Node.js >= 20.0.0 (for npm install)
97
- - No requirements for standalone binaries
90
+ - Node.js >= 20.0.0
98
91
 
99
92
  ## Contributing
100
93
 
101
- This CLI is maintained by [KinoSec](https://kinosec.ai). External contributions are not accepted at this time. Bug reports via Issues are welcome.
94
+ This CLI is maintained by [KinoSec](https://kinosec.ai). External contributions are not accepted at this time.
102
95
 
103
96
  ## License
104
97
 
package/dist/cli.js CHANGED
@@ -7771,6 +7771,7 @@ var KinoSecClient = class {
7771
7771
  api;
7772
7772
  assets;
7773
7773
  scans;
7774
+ schedules;
7774
7775
  findings;
7775
7776
  reports;
7776
7777
  tokens;
@@ -7819,6 +7820,7 @@ var KinoSecClient = class {
7819
7820
  });
7820
7821
  this.assets = new AssetsResource(this.api);
7821
7822
  this.scans = new ScansResource(this.api);
7823
+ this.schedules = new SchedulesResource(this.api);
7822
7824
  this.findings = new FindingsResource(this.api);
7823
7825
  this.reports = new ReportsResource(this.api);
7824
7826
  this.tokens = new TokensResource(this.api);
@@ -7868,6 +7870,33 @@ var ScansResource = class {
7868
7870
  return this.api.post(`v1/scans/${id}/cancel`, { json: {} }).json();
7869
7871
  }
7870
7872
  };
7873
+ var SchedulesResource = class {
7874
+ constructor(api) {
7875
+ this.api = api;
7876
+ }
7877
+ api;
7878
+ async list(params = {}) {
7879
+ return this.api.get("v1/scans/schedules", { searchParams: buildSearchParams(params) }).json();
7880
+ }
7881
+ async get(id) {
7882
+ return this.api.get(`v1/scans/schedules/${id}`).json();
7883
+ }
7884
+ async create(params) {
7885
+ return this.api.post("v1/scans/schedules", { json: params }).json();
7886
+ }
7887
+ async update(id, params) {
7888
+ return this.api.patch(`v1/scans/schedules/${id}`, { json: params }).json();
7889
+ }
7890
+ async delete(id) {
7891
+ await this.api.delete(`v1/scans/schedules/${id}`);
7892
+ }
7893
+ async pause(id) {
7894
+ return this.api.post(`v1/scans/schedules/${id}/pause`, { json: {} }).json();
7895
+ }
7896
+ async resume(id) {
7897
+ return this.api.post(`v1/scans/schedules/${id}/resume`, { json: {} }).json();
7898
+ }
7899
+ };
7871
7900
  var FindingsResource = class {
7872
7901
  constructor(api) {
7873
7902
  this.api = api;
@@ -8022,8 +8051,9 @@ function shouldUseJson() {
8022
8051
  return false;
8023
8052
  }
8024
8053
 
8025
- // src/commands/auth.ts
8026
- import { createInterface } from "readline";
8054
+ // src/lib/update-check.ts
8055
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
8056
+ import { join as join2, dirname as dirname2 } from "path";
8027
8057
 
8028
8058
  // src/lib/config.ts
8029
8059
  import { existsSync, mkdirSync, readFileSync, writeFileSync, openSync, writeSync, closeSync, unlinkSync, constants } from "fs";
@@ -8097,6 +8127,81 @@ function deleteCredentials() {
8097
8127
  }
8098
8128
  }
8099
8129
 
8130
+ // src/lib/update-check.ts
8131
+ var CACHE_FILE = join2(configDir, "update-check.json");
8132
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
8133
+ var FETCH_TIMEOUT_MS = 3e3;
8134
+ var NPM_PACKAGE = "@kinosec/cli";
8135
+ var REGISTRY_URL = `https://registry.npmjs.org/${NPM_PACKAGE}/latest`;
8136
+ function isUpdateSuppressed() {
8137
+ return !!(process.env.KINOSEC_NO_UPDATE_CHECK === "1" || process.env.CI || process.env.GITHUB_ACTIONS || process.env.GITLAB_CI || process.env.JENKINS_URL);
8138
+ }
8139
+ function isNewerVersion(current, latest) {
8140
+ const c = current.split(".").map(Number);
8141
+ const l = latest.split(".").map(Number);
8142
+ for (let i = 0; i < 3; i++) {
8143
+ if ((l[i] ?? 0) > (c[i] ?? 0)) return true;
8144
+ if ((l[i] ?? 0) < (c[i] ?? 0)) return false;
8145
+ }
8146
+ return false;
8147
+ }
8148
+ function readCache() {
8149
+ if (!existsSync2(CACHE_FILE)) return null;
8150
+ try {
8151
+ const data = JSON.parse(readFileSync2(CACHE_FILE, "utf-8"));
8152
+ const age = Date.now() - new Date(data.checked_at).getTime();
8153
+ if (age > CACHE_TTL_MS) return null;
8154
+ return data;
8155
+ } catch {
8156
+ return null;
8157
+ }
8158
+ }
8159
+ function writeCache(latest) {
8160
+ try {
8161
+ const dir = dirname2(CACHE_FILE);
8162
+ if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true });
8163
+ writeFileSync2(
8164
+ CACHE_FILE,
8165
+ JSON.stringify({ latest, checked_at: (/* @__PURE__ */ new Date()).toISOString() }, null, 2) + "\n",
8166
+ "utf-8"
8167
+ );
8168
+ } catch {
8169
+ }
8170
+ }
8171
+ async function fetchLatestVersion() {
8172
+ try {
8173
+ const response = await fetch(REGISTRY_URL, {
8174
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
8175
+ });
8176
+ if (!response.ok) return null;
8177
+ const data = await response.json();
8178
+ return data.version ?? null;
8179
+ } catch {
8180
+ return null;
8181
+ }
8182
+ }
8183
+ async function checkForUpdate(currentVersion) {
8184
+ if (isUpdateSuppressed()) return;
8185
+ let latest = null;
8186
+ const cached = readCache();
8187
+ if (cached) {
8188
+ latest = cached.latest;
8189
+ } else {
8190
+ latest = await fetchLatestVersion();
8191
+ if (latest) writeCache(latest);
8192
+ }
8193
+ if (latest && isNewerVersion(currentVersion, latest)) {
8194
+ console.error(
8195
+ `
8196
+ A new version of KinoSec CLI is available: ${latest} (current: ${currentVersion})
8197
+ Update: npm update -g @kinosec/cli`
8198
+ );
8199
+ }
8200
+ }
8201
+
8202
+ // src/commands/auth.ts
8203
+ import { createInterface } from "readline";
8204
+
8100
8205
  // src/lib/auth.ts
8101
8206
  var DEFAULT_API_URL = "https://api.kinosec.ai/v1";
8102
8207
  function resolveToken(options) {
@@ -9713,9 +9818,274 @@ function ora(options) {
9713
9818
  }
9714
9819
 
9715
9820
  // src/commands/scan.ts
9821
+ var import_picocolors4 = __toESM(require_picocolors(), 1);
9822
+
9823
+ // src/commands/schedule.ts
9716
9824
  var import_picocolors3 = __toESM(require_picocolors(), 1);
9825
+
9826
+ // src/lib/confirm.ts
9827
+ import { createInterface as createInterface2 } from "readline";
9828
+ async function confirmDestructive(message, yes) {
9829
+ if (yes) return;
9830
+ if (!isTTY()) {
9831
+ console.error("Use --yes to confirm destructive operations in non-interactive mode.");
9832
+ process.exit(2);
9833
+ }
9834
+ const rl = createInterface2({ input: process.stdin, output: process.stderr });
9835
+ const answer = await new Promise((resolve) => {
9836
+ rl.question(`${message} (y/N) `, resolve);
9837
+ });
9838
+ rl.close();
9839
+ if (answer.toLowerCase() !== "y") {
9840
+ console.log("Cancelled.");
9841
+ process.exit(0);
9842
+ }
9843
+ }
9844
+
9845
+ // src/commands/schedule.ts
9846
+ var VALID_PATTERNS = ["once", "daily", "weekly", "biweekly", "monthly"];
9847
+ var VALID_MODES = ["quick", "full"];
9848
+ var DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
9849
+ function extractTime(scheduledAt) {
9850
+ if (!scheduledAt) return "\u2014";
9851
+ const d = new Date(scheduledAt);
9852
+ return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", hour12: false });
9853
+ }
9854
+ function formatPattern(s) {
9855
+ const time = extractTime(s.scheduled_at);
9856
+ switch (s.recurrence_pattern) {
9857
+ case "once":
9858
+ return `Once at ${time}`;
9859
+ case "daily":
9860
+ return `Daily at ${time}`;
9861
+ case "weekly":
9862
+ return `Weekly ${s.day_of_week !== null ? DAY_NAMES[s.day_of_week] : ""} at ${time}`;
9863
+ case "biweekly":
9864
+ return `Every 2 weeks ${s.day_of_week !== null ? DAY_NAMES[s.day_of_week] : ""} at ${time}`;
9865
+ case "monthly":
9866
+ return `Monthly day ${s.day_of_month ?? "?"} at ${time}`;
9867
+ default:
9868
+ return s.recurrence_pattern;
9869
+ }
9870
+ }
9871
+ function registerScheduleCommand(scanCmd) {
9872
+ const schedule = scanCmd.command("schedule").description("Manage scheduled scans (continuous protection)");
9873
+ schedule.command("list").description("List scheduled scans").option("--limit <n>", "Max results per page", "20").option("--cursor <cursor>", "Pagination cursor").option("--status <status>", "Filter: active, paused, or all", "all").addHelpText("after", `
9874
+ Examples:
9875
+ $ kinosec scan schedule list
9876
+ $ kinosec scan schedule list --status active --json`).action(async (options) => {
9877
+ try {
9878
+ const opts = schedule.parent.parent.opts();
9879
+ const { token, apiUrl } = resolveToken(opts);
9880
+ const client = new KinoSecClient({ apiUrl, token });
9881
+ const result = await client.schedules.list({
9882
+ limit: parseInt(options.limit, 10),
9883
+ cursor: options.cursor,
9884
+ status: options.status
9885
+ });
9886
+ if (opts.json || opts.output === "json") {
9887
+ printJson(result);
9888
+ return;
9889
+ }
9890
+ if (result.items.length === 0) {
9891
+ console.log("No scheduled scans found.");
9892
+ return;
9893
+ }
9894
+ printTable(
9895
+ ["ID", "Target", "Mode", "Schedule", "Status", "Next Run"],
9896
+ result.items.map((s) => [
9897
+ s.id,
9898
+ truncate(s.domain_or_ip, 25),
9899
+ s.scan_mode,
9900
+ formatPattern(s),
9901
+ s.is_active ? import_picocolors3.default.green("active") : import_picocolors3.default.yellow("paused"),
9902
+ s.next_scheduled_run ? new Date(s.next_scheduled_run).toLocaleString() : "\u2014"
9903
+ ])
9904
+ );
9905
+ if (result.next_cursor) {
9906
+ console.log(import_picocolors3.default.dim(`
9907
+ More results available. Use --cursor ${result.next_cursor}`));
9908
+ }
9909
+ } catch (error) {
9910
+ handleError(error);
9911
+ }
9912
+ });
9913
+ schedule.command("get").description("Get schedule details").argument("<id>", "Schedule ID").addHelpText("after", `
9914
+ Examples:
9915
+ $ kinosec scan schedule get abc123
9916
+ $ kinosec scan schedule get abc123 --json`).action(async (id) => {
9917
+ try {
9918
+ const opts = schedule.parent.parent.opts();
9919
+ const { token, apiUrl } = resolveToken(opts);
9920
+ const client = new KinoSecClient({ apiUrl, token });
9921
+ const s = await client.schedules.get(id);
9922
+ if (opts.json || opts.output === "json") {
9923
+ printJson(s);
9924
+ return;
9925
+ }
9926
+ printTable(
9927
+ ["Field", "Value"],
9928
+ [
9929
+ ["ID", s.id],
9930
+ ["Target", s.domain_or_ip],
9931
+ ["Name", s.name],
9932
+ ["Mode", s.scan_mode],
9933
+ ["Schedule", formatPattern(s)],
9934
+ ["Timezone", s.timezone],
9935
+ ["Status", s.is_active ? "Active" : "Paused"],
9936
+ ["Next Run", s.next_scheduled_run ?? "\u2014"],
9937
+ ["Last Run", s.last_scheduled_run ?? "Never"],
9938
+ ["Created", s.created_at]
9939
+ ]
9940
+ );
9941
+ } catch (error) {
9942
+ handleError(error);
9943
+ }
9944
+ });
9945
+ schedule.command("create").description("Create a scheduled scan").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", `
9946
+ Examples:
9947
+ $ kinosec scan schedule create --asset abc123 --pattern weekly --day-of-week 1 --time 09:00
9948
+ $ kinosec scan schedule create --asset abc123 --pattern daily --mode quick --time 14:00
9949
+ $ kinosec scan schedule create --asset abc123 --pattern monthly --day-of-month 15 --time 03:00 --timezone UTC`).action(async (options) => {
9950
+ if (!VALID_PATTERNS.includes(options.pattern)) {
9951
+ console.error(`Invalid pattern "${options.pattern}". Must be: ${VALID_PATTERNS.join(", ")}`);
9952
+ process.exit(2);
9953
+ }
9954
+ if (!VALID_MODES.includes(options.mode)) {
9955
+ console.error(`Invalid mode "${options.mode}". Must be: ${VALID_MODES.join(", ")}`);
9956
+ process.exit(2);
9957
+ }
9958
+ if ((options.pattern === "weekly" || options.pattern === "biweekly") && options.dayOfWeek === void 0) {
9959
+ console.error(`--day-of-week is required for ${options.pattern} schedules.`);
9960
+ process.exit(2);
9961
+ }
9962
+ if (options.pattern === "monthly" && options.dayOfMonth === void 0) {
9963
+ console.error("--day-of-month is required for monthly schedules.");
9964
+ process.exit(2);
9965
+ }
9966
+ try {
9967
+ const opts = schedule.parent.parent.opts();
9968
+ const { token, apiUrl } = resolveToken(opts);
9969
+ const client = new KinoSecClient({ apiUrl, token });
9970
+ const params = {
9971
+ asset_id: options.domain_or_ip,
9972
+ name: options.name ?? `${options.pattern} ${options.mode} scan`,
9973
+ scan_mode: options.mode,
9974
+ recurrence_pattern: options.pattern,
9975
+ time_of_day: options.time,
9976
+ timezone: options.timezone
9977
+ };
9978
+ if (options.dayOfWeek !== void 0) params.day_of_week = parseInt(options.dayOfWeek, 10);
9979
+ if (options.dayOfMonth !== void 0) params.day_of_month = parseInt(options.dayOfMonth, 10);
9980
+ const s = await client.schedules.create(params);
9981
+ if (opts.json || opts.output === "json") {
9982
+ printJson(s);
9983
+ return;
9984
+ }
9985
+ printSuccess(`Schedule created: ${s.domain_or_ip}`);
9986
+ console.log(` ID: ${s.id}`);
9987
+ console.log(` Schedule: ${formatPattern(s)}`);
9988
+ console.log(` Next run: ${s.next_scheduled_run ?? "pending"}`);
9989
+ console.log(`
9990
+ Manage: kinosec scan schedule get ${s.id}`);
9991
+ } catch (error) {
9992
+ handleError(error);
9993
+ }
9994
+ });
9995
+ 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", `
9996
+ Examples:
9997
+ $ kinosec scan schedule update abc123 --pattern daily --time 14:00
9998
+ $ kinosec scan schedule update abc123 --mode quick`).action(async (id, options) => {
9999
+ const params = {};
10000
+ if (options.pattern) params.recurrence_pattern = options.pattern;
10001
+ if (options.name) params.name = options.name;
10002
+ if (options.mode) params.scan_mode = options.mode;
10003
+ if (options.time) params.time_of_day = options.time;
10004
+ if (options.dayOfWeek !== void 0) params.day_of_week = parseInt(options.dayOfWeek, 10);
10005
+ if (options.dayOfMonth !== void 0) params.day_of_month = parseInt(options.dayOfMonth, 10);
10006
+ if (options.timezone) params.timezone = options.timezone;
10007
+ if (Object.keys(params).length === 0) {
10008
+ console.error("Provide at least one field to update.");
10009
+ process.exit(2);
10010
+ }
10011
+ try {
10012
+ const opts = schedule.parent.parent.opts();
10013
+ const { token, apiUrl } = resolveToken(opts);
10014
+ const client = new KinoSecClient({ apiUrl, token });
10015
+ const s = await client.schedules.update(id, params);
10016
+ if (opts.json || opts.output === "json") {
10017
+ printJson(s);
10018
+ return;
10019
+ }
10020
+ printSuccess(`Schedule updated: ${s.domain_or_ip}`);
10021
+ console.log(` Schedule: ${formatPattern(s)}`);
10022
+ console.log(` Next run: ${s.next_scheduled_run ?? "pending"}`);
10023
+ } catch (error) {
10024
+ handleError(error);
10025
+ }
10026
+ });
10027
+ schedule.command("delete").description("Delete a schedule").argument("<id>", "Schedule ID").option("-y, --yes", "Skip confirmation prompt").addHelpText("after", `
10028
+ Examples:
10029
+ $ kinosec scan schedule delete abc123
10030
+ $ kinosec scan schedule delete abc123 --yes`).action(async (id, options) => {
10031
+ try {
10032
+ await confirmDestructive(`Delete schedule ${id}?`, Boolean(options.yes));
10033
+ const opts = schedule.parent.parent.opts();
10034
+ const { token, apiUrl } = resolveToken(opts);
10035
+ const client = new KinoSecClient({ apiUrl, token });
10036
+ await client.schedules.delete(id);
10037
+ if (opts.json || opts.output === "json") {
10038
+ printJson({ deleted: true, id });
10039
+ return;
10040
+ }
10041
+ printSuccess(`Schedule ${id} deleted.`);
10042
+ } catch (error) {
10043
+ handleError(error);
10044
+ }
10045
+ });
10046
+ schedule.command("pause").description("Pause a scheduled scan").argument("<id>", "Schedule ID").addHelpText("after", `
10047
+ Examples:
10048
+ $ kinosec scan schedule pause abc123`).action(async (id) => {
10049
+ try {
10050
+ const opts = schedule.parent.parent.opts();
10051
+ const { token, apiUrl } = resolveToken(opts);
10052
+ const client = new KinoSecClient({ apiUrl, token });
10053
+ const result = await client.schedules.pause(id);
10054
+ if (opts.json || opts.output === "json") {
10055
+ printJson(result);
10056
+ return;
10057
+ }
10058
+ printSuccess("Schedule paused.");
10059
+ } catch (error) {
10060
+ handleError(error);
10061
+ }
10062
+ });
10063
+ schedule.command("resume").description("Resume a paused schedule").argument("<id>", "Schedule ID").addHelpText("after", `
10064
+ Examples:
10065
+ $ kinosec scan schedule resume abc123`).action(async (id) => {
10066
+ try {
10067
+ const opts = schedule.parent.parent.opts();
10068
+ const { token, apiUrl } = resolveToken(opts);
10069
+ const client = new KinoSecClient({ apiUrl, token });
10070
+ const result = await client.schedules.resume(id);
10071
+ if (opts.json || opts.output === "json") {
10072
+ printJson(result);
10073
+ return;
10074
+ }
10075
+ printSuccess("Schedule resumed.");
10076
+ if (result.next_scheduled_run) {
10077
+ console.log(` Next run: ${new Date(result.next_scheduled_run).toLocaleString()}`);
10078
+ }
10079
+ } catch (error) {
10080
+ handleError(error);
10081
+ }
10082
+ });
10083
+ }
10084
+
10085
+ // src/commands/scan.ts
9717
10086
  function registerScanCommand(program3) {
9718
10087
  const scan = program3.command("scan").description("Manage security scans");
10088
+ registerScheduleCommand(scan);
9719
10089
  scan.command("list").description("List scans").option("--limit <n>", "Max results per page", "20").option("--cursor <cursor>", "Pagination cursor").addHelpText("after", `
9720
10090
  Examples:
9721
10091
  $ kinosec scan list
@@ -9747,7 +10117,7 @@ Examples:
9747
10117
  ])
9748
10118
  );
9749
10119
  if (result.next_cursor) {
9750
- console.log(import_picocolors3.default.dim(`
10120
+ console.log(import_picocolors4.default.dim(`
9751
10121
  More results available. Use --cursor ${result.next_cursor}`));
9752
10122
  }
9753
10123
  } catch (error) {
@@ -9871,7 +10241,7 @@ Examples:
9871
10241
  process.stdout.write(JSON.stringify({ ...scanDetail, elapsed_seconds: elapsed }) + "\n");
9872
10242
  } else if (spinner) {
9873
10243
  if (activity && activity !== lastActivity && lastActivity) {
9874
- spinner.stopAndPersist({ symbol: import_picocolors3.default.green("\u2713"), text: lastActivity });
10244
+ spinner.stopAndPersist({ symbol: import_picocolors4.default.green("\u2713"), text: lastActivity });
9875
10245
  spinner = ora().start();
9876
10246
  }
9877
10247
  lastActivity = activity || lastActivity;
@@ -9885,15 +10255,15 @@ Examples:
9885
10255
  break;
9886
10256
  }
9887
10257
  if (scanDetail.scan_status.includes("Failed")) {
9888
- console.log(import_picocolors3.default.red(`
10258
+ console.log(import_picocolors4.default.red(`
9889
10259
  Scan failed: ${scanDetail.failure_reason || "unknown reason"}`));
9890
10260
  process.exit(1);
9891
10261
  }
9892
10262
  if (scanDetail.scan_status.includes("Stopped")) {
9893
- console.log(import_picocolors3.default.yellow("\nScan was cancelled."));
10263
+ console.log(import_picocolors4.default.yellow("\nScan was cancelled."));
9894
10264
  break;
9895
10265
  }
9896
- console.log(import_picocolors3.default.green(`
10266
+ console.log(import_picocolors4.default.green(`
9897
10267
  Scan completed: ${scanDetail.domain_or_ip}`));
9898
10268
  console.log(` View findings: kinosec finding list --scan ${id}`);
9899
10269
  break;
@@ -9914,33 +10284,14 @@ Timeout: scan still running after ${options.timeout}s.`);
9914
10284
  }
9915
10285
 
9916
10286
  // src/commands/asset.ts
9917
- var import_picocolors4 = __toESM(require_picocolors(), 1);
9918
-
9919
- // src/lib/confirm.ts
9920
- import { createInterface as createInterface2 } from "readline";
9921
- async function confirmDestructive(message, yes) {
9922
- if (yes) return;
9923
- if (!isTTY()) {
9924
- console.error("Use --yes to confirm destructive operations in non-interactive mode.");
9925
- process.exit(2);
9926
- }
9927
- const rl = createInterface2({ input: process.stdin, output: process.stderr });
9928
- const answer = await new Promise((resolve) => {
9929
- rl.question(`${message} (y/N) `, resolve);
9930
- });
9931
- rl.close();
9932
- if (answer.toLowerCase() !== "y") {
9933
- console.log("Cancelled.");
9934
- process.exit(0);
9935
- }
9936
- }
9937
-
9938
- // src/commands/asset.ts
10287
+ var import_picocolors5 = __toESM(require_picocolors(), 1);
9939
10288
  function registerAssetCommand(program3) {
9940
10289
  const asset = program3.command("asset").description("Manage assets");
9941
- asset.command("list").description("List assets").option("--limit <n>", "Max results per page", "20").option("--cursor <cursor>", "Pagination cursor").addHelpText("after", `
10290
+ 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", `
9942
10291
  Examples:
9943
10292
  $ kinosec asset list
10293
+ $ kinosec asset list --search example.com
10294
+ $ kinosec asset list --type subdomain --status verified
9944
10295
  $ kinosec asset list --limit 50 --json`).action(async (options) => {
9945
10296
  try {
9946
10297
  const opts = program3.opts();
@@ -9948,7 +10299,10 @@ Examples:
9948
10299
  const client = new KinoSecClient({ apiUrl, token });
9949
10300
  const result = await client.assets.list({
9950
10301
  limit: parseInt(options.limit, 10),
9951
- cursor: options.cursor
10302
+ cursor: options.cursor,
10303
+ search: options.search,
10304
+ status: options.status,
10305
+ type: options.type
9952
10306
  });
9953
10307
  if (opts.json || opts.output === "json") {
9954
10308
  printJson(result);
@@ -9969,7 +10323,7 @@ Examples:
9969
10323
  ])
9970
10324
  );
9971
10325
  if (result.next_cursor) {
9972
- console.log(import_picocolors4.default.dim(`
10326
+ console.log(import_picocolors5.default.dim(`
9973
10327
  More results available. Use --cursor ${result.next_cursor}`));
9974
10328
  }
9975
10329
  } catch (error) {
@@ -10080,20 +10434,20 @@ Examples:
10080
10434
  }
10081
10435
 
10082
10436
  // src/commands/finding.ts
10083
- var import_picocolors5 = __toESM(require_picocolors(), 1);
10437
+ var import_picocolors6 = __toESM(require_picocolors(), 1);
10084
10438
  function severityColor(level) {
10085
10439
  if (!isTTY()) return level;
10086
10440
  switch (level.toLowerCase()) {
10087
10441
  case "critical":
10088
- return import_picocolors5.default.red(import_picocolors5.default.bold(level));
10442
+ return import_picocolors6.default.red(import_picocolors6.default.bold(level));
10089
10443
  case "high":
10090
- return import_picocolors5.default.red(level);
10444
+ return import_picocolors6.default.red(level);
10091
10445
  case "medium":
10092
- return import_picocolors5.default.yellow(level);
10446
+ return import_picocolors6.default.yellow(level);
10093
10447
  case "low":
10094
- return import_picocolors5.default.blue(level);
10448
+ return import_picocolors6.default.blue(level);
10095
10449
  case "info":
10096
- return import_picocolors5.default.dim(level);
10450
+ return import_picocolors6.default.dim(level);
10097
10451
  default:
10098
10452
  return level;
10099
10453
  }
@@ -10133,7 +10487,7 @@ Examples:
10133
10487
  ])
10134
10488
  );
10135
10489
  if (result.next_cursor) {
10136
- console.log(import_picocolors5.default.dim(`
10490
+ console.log(import_picocolors6.default.dim(`
10137
10491
  More results available. Use --cursor ${result.next_cursor}`));
10138
10492
  }
10139
10493
  } catch (error) {
@@ -10218,7 +10572,7 @@ Examples:
10218
10572
  }
10219
10573
 
10220
10574
  // src/commands/report.ts
10221
- var import_picocolors6 = __toESM(require_picocolors(), 1);
10575
+ var import_picocolors7 = __toESM(require_picocolors(), 1);
10222
10576
  function renderMarkdown(data) {
10223
10577
  const { scan, findings } = data;
10224
10578
  const lines = [];
@@ -10297,7 +10651,7 @@ Examples:
10297
10651
  ])
10298
10652
  );
10299
10653
  if (result.next_cursor) {
10300
- console.log(import_picocolors6.default.dim(`
10654
+ console.log(import_picocolors7.default.dim(`
10301
10655
  More results available. Use --cursor ${result.next_cursor}`));
10302
10656
  }
10303
10657
  } catch (error) {
@@ -10368,7 +10722,7 @@ Examples:
10368
10722
  }
10369
10723
 
10370
10724
  // src/commands/token.ts
10371
- var import_picocolors7 = __toESM(require_picocolors(), 1);
10725
+ var import_picocolors8 = __toESM(require_picocolors(), 1);
10372
10726
  function registerTokenCommand(program3) {
10373
10727
  const token = program3.command("token").description("Manage API tokens");
10374
10728
  token.command("list").description("List API tokens").option("--limit <n>", "Max results per page", "20").option("--cursor <cursor>", "Pagination cursor").addHelpText("after", `
@@ -10403,7 +10757,7 @@ Examples:
10403
10757
  ])
10404
10758
  );
10405
10759
  if (result.next_cursor) {
10406
- console.log(import_picocolors7.default.dim(`
10760
+ console.log(import_picocolors8.default.dim(`
10407
10761
  More results available. Use --cursor ${result.next_cursor}`));
10408
10762
  }
10409
10763
  } catch (error) {
@@ -10430,7 +10784,7 @@ Examples:
10430
10784
  }
10431
10785
  printSuccess(`Token created: ${result.name}`);
10432
10786
  console.log("");
10433
- console.log(import_picocolors7.default.bold(" Token: ") + result.token);
10787
+ console.log(import_picocolors8.default.bold(" Token: ") + result.token);
10434
10788
  console.log("");
10435
10789
  printWarning("Save this token \u2014 it will not be shown again.");
10436
10790
  console.log(` ID: ${result.id}`);
@@ -10462,9 +10816,8 @@ Examples:
10462
10816
  }
10463
10817
 
10464
10818
  // src/cli.ts
10465
- var VERSION = "0.1.0";
10466
10819
  var program2 = new Command();
10467
- program2.name("kinosec").description("KinoSec CLI \u2014 run pentests from your terminal").version(VERSION, "-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");
10820
+ program2.name("kinosec").description("KinoSec CLI \u2014 run pentests from your terminal").version("0.2.1", "-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");
10468
10821
  registerAuthCommand(program2);
10469
10822
  registerConfigCommand(program2);
10470
10823
  registerMeCommand(program2);
@@ -10473,7 +10826,7 @@ registerAssetCommand(program2);
10473
10826
  registerFindingCommand(program2);
10474
10827
  registerReportCommand(program2);
10475
10828
  registerTokenCommand(program2);
10476
- program2.parseAsync(process.argv).catch(handleError);
10829
+ program2.parseAsync(process.argv).then(() => checkForUpdate("0.2.1")).catch(handleError);
10477
10830
  /*! Bundled license information:
10478
10831
 
10479
10832
  ky/distribution/index.js: