@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 +6 -13
- package/dist/cli.js +400 -47
- package/dist/cli.js.map +1 -1
- package/package.json +21 -6
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
|
|
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.
|
|
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/
|
|
8026
|
-
import {
|
|
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(
|
|
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:
|
|
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(
|
|
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(
|
|
10263
|
+
console.log(import_picocolors4.default.yellow("\nScan was cancelled."));
|
|
9894
10264
|
break;
|
|
9895
10265
|
}
|
|
9896
|
-
console.log(
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
10442
|
+
return import_picocolors6.default.red(import_picocolors6.default.bold(level));
|
|
10089
10443
|
case "high":
|
|
10090
|
-
return
|
|
10444
|
+
return import_picocolors6.default.red(level);
|
|
10091
10445
|
case "medium":
|
|
10092
|
-
return
|
|
10446
|
+
return import_picocolors6.default.yellow(level);
|
|
10093
10447
|
case "low":
|
|
10094
|
-
return
|
|
10448
|
+
return import_picocolors6.default.blue(level);
|
|
10095
10449
|
case "info":
|
|
10096
|
-
return
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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:
|