@kinosec/cli 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -19
- package/dist/cli.js +461 -108
- package/dist/cli.js.map +1 -1
- package/package.json +21 -6
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;
|
|
@@ -7781,7 +7782,7 @@ var KinoSecClient = class {
|
|
|
7781
7782
|
this.api = distribution_default.create({
|
|
7782
7783
|
prefixUrl: config.apiUrl,
|
|
7783
7784
|
headers: {
|
|
7784
|
-
"user-agent": config.userAgent ?? `
|
|
7785
|
+
"user-agent": config.userAgent ?? `kinosec/0.1.0 (${process.platform})`
|
|
7785
7786
|
},
|
|
7786
7787
|
hooks: {
|
|
7787
7788
|
beforeRequest: [
|
|
@@ -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,17 +8127,92 @@ 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) {
|
|
8103
8208
|
const token = options.token ?? process.env.KINOSEC_TOKEN ?? readCredentials()?.token;
|
|
8104
8209
|
if (!token) {
|
|
8105
|
-
console.error('Not authenticated. Run "
|
|
8210
|
+
console.error('Not authenticated. Run "kinosec auth login" or set KINOSEC_TOKEN.');
|
|
8106
8211
|
process.exit(77);
|
|
8107
8212
|
}
|
|
8108
8213
|
if (options.token) {
|
|
8109
8214
|
console.error(
|
|
8110
|
-
"Warning: passing a token via --token exposes it in shell history. Prefer:
|
|
8215
|
+
"Warning: passing a token via --token exposes it in shell history. Prefer: kinosec auth login (interactive), KINOSEC_TOKEN env var, or stdin pipe."
|
|
8111
8216
|
);
|
|
8112
8217
|
}
|
|
8113
8218
|
const apiUrl = options.apiUrl ?? process.env.KINOSEC_API_URL ?? getConfigValue("api_url") ?? DEFAULT_API_URL;
|
|
@@ -8132,14 +8237,14 @@ function registerAuthCommand(program3) {
|
|
|
8132
8237
|
const auth = program3.command("auth").description("Manage authentication");
|
|
8133
8238
|
auth.command("login").description("Authenticate with a KinoSec API token").addHelpText("after", `
|
|
8134
8239
|
Examples:
|
|
8135
|
-
$
|
|
8136
|
-
$
|
|
8137
|
-
$ echo $TOKEN |
|
|
8240
|
+
$ kinosec auth login # interactive prompt
|
|
8241
|
+
$ kinosec auth login --token kns_live_pat_...
|
|
8242
|
+
$ echo $TOKEN | kinosec auth login # pipe from stdin`).action(async () => {
|
|
8138
8243
|
try {
|
|
8139
8244
|
let token = program3.opts().token;
|
|
8140
8245
|
if (token) {
|
|
8141
8246
|
console.error(
|
|
8142
|
-
"Warning: passing a token via --token exposes it in shell history. Prefer:
|
|
8247
|
+
"Warning: passing a token via --token exposes it in shell history. Prefer: kinosec auth login (interactive), KINOSEC_TOKEN env var, or stdin pipe."
|
|
8143
8248
|
);
|
|
8144
8249
|
}
|
|
8145
8250
|
if (!token) {
|
|
@@ -8178,11 +8283,11 @@ Examples:
|
|
|
8178
8283
|
});
|
|
8179
8284
|
auth.command("status").description("Show current authentication status").addHelpText("after", `
|
|
8180
8285
|
Examples:
|
|
8181
|
-
$
|
|
8286
|
+
$ kinosec auth status`).action(async () => {
|
|
8182
8287
|
try {
|
|
8183
8288
|
const creds = readCredentials();
|
|
8184
8289
|
if (!creds) {
|
|
8185
|
-
console.error('Not authenticated. Run "
|
|
8290
|
+
console.error('Not authenticated. Run "kinosec auth login" or set KINOSEC_TOKEN.');
|
|
8186
8291
|
process.exit(77);
|
|
8187
8292
|
}
|
|
8188
8293
|
const apiUrl = program3.opts().apiUrl ?? getConfigValue("api_url") ?? DEFAULT_API_URL;
|
|
@@ -8211,7 +8316,7 @@ Examples:
|
|
|
8211
8316
|
});
|
|
8212
8317
|
auth.command("logout").description("Remove stored credentials").addHelpText("after", `
|
|
8213
8318
|
Examples:
|
|
8214
|
-
$
|
|
8319
|
+
$ kinosec auth logout`).action(() => {
|
|
8215
8320
|
deleteCredentials();
|
|
8216
8321
|
printSuccess("Logged out. Credentials removed.");
|
|
8217
8322
|
});
|
|
@@ -8223,8 +8328,8 @@ function registerConfigCommand(program3) {
|
|
|
8223
8328
|
const config = program3.command("config").description("Manage CLI configuration");
|
|
8224
8329
|
config.command("set").description("Set a config value").argument("<key>", `Config key (${VALID_KEYS.join(", ")})`).argument("<value>", "Config value").addHelpText("after", `
|
|
8225
8330
|
Examples:
|
|
8226
|
-
$
|
|
8227
|
-
$
|
|
8331
|
+
$ kinosec config set api_url https://custom.api.url/v1
|
|
8332
|
+
$ kinosec config set output json`).action((key, value) => {
|
|
8228
8333
|
if (!VALID_KEYS.includes(key)) {
|
|
8229
8334
|
console.error(`Invalid config key "${key}". Valid keys: ${VALID_KEYS.join(", ")}`);
|
|
8230
8335
|
process.exit(2);
|
|
@@ -8234,7 +8339,7 @@ Examples:
|
|
|
8234
8339
|
});
|
|
8235
8340
|
config.command("get").description("Get a config value").argument("<key>", "Config key").addHelpText("after", `
|
|
8236
8341
|
Examples:
|
|
8237
|
-
$
|
|
8342
|
+
$ kinosec config get api_url`).action((key) => {
|
|
8238
8343
|
const value = getConfigValue(key);
|
|
8239
8344
|
if (value === void 0) {
|
|
8240
8345
|
console.error(`Config key "${key}" is not set.`);
|
|
@@ -8244,7 +8349,7 @@ Examples:
|
|
|
8244
8349
|
});
|
|
8245
8350
|
config.command("list").description("List all config values").addHelpText("after", `
|
|
8246
8351
|
Examples:
|
|
8247
|
-
$
|
|
8352
|
+
$ kinosec config list`).action(() => {
|
|
8248
8353
|
const cfg = readConfig();
|
|
8249
8354
|
const opts = program3.opts();
|
|
8250
8355
|
if (opts.json || opts.output === "json") {
|
|
@@ -8265,8 +8370,8 @@ Examples:
|
|
|
8265
8370
|
function registerMeCommand(program3) {
|
|
8266
8371
|
program3.command("me").description("Show authenticated user info").addHelpText("after", `
|
|
8267
8372
|
Examples:
|
|
8268
|
-
$
|
|
8269
|
-
$
|
|
8373
|
+
$ kinosec me
|
|
8374
|
+
$ kinosec me --json`).action(async () => {
|
|
8270
8375
|
try {
|
|
8271
8376
|
const opts = program3.opts();
|
|
8272
8377
|
const { token, apiUrl } = resolveToken(opts);
|
|
@@ -9713,13 +9818,278 @@ 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
|
-
$
|
|
9722
|
-
$
|
|
10091
|
+
$ kinosec scan list
|
|
10092
|
+
$ kinosec scan list --limit 5 --json`).action(async (options) => {
|
|
9723
10093
|
try {
|
|
9724
10094
|
const opts = program3.opts();
|
|
9725
10095
|
const { token, apiUrl } = resolveToken(opts);
|
|
@@ -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) {
|
|
@@ -9756,8 +10126,8 @@ More results available. Use --cursor ${result.next_cursor}`));
|
|
|
9756
10126
|
});
|
|
9757
10127
|
scan.command("get").description("Get scan details").argument("<id>", "Scan profile ID").addHelpText("after", `
|
|
9758
10128
|
Examples:
|
|
9759
|
-
$
|
|
9760
|
-
$
|
|
10129
|
+
$ kinosec scan get abc123
|
|
10130
|
+
$ kinosec scan get abc123 --json`).action(async (id) => {
|
|
9761
10131
|
try {
|
|
9762
10132
|
const opts = program3.opts();
|
|
9763
10133
|
const { token, apiUrl } = resolveToken(opts);
|
|
@@ -9786,8 +10156,8 @@ Examples:
|
|
|
9786
10156
|
});
|
|
9787
10157
|
scan.command("create").description("Start a new scan").option("--asset <id>", "Asset ID to scan").option("--profile <id>", "Scan profile ID (alternative to --asset)").option("--mode <mode>", "Scan mode: quick or full", "quick").addHelpText("after", `
|
|
9788
10158
|
Examples:
|
|
9789
|
-
$
|
|
9790
|
-
$
|
|
10159
|
+
$ kinosec scan create --asset abc123 --mode quick
|
|
10160
|
+
$ kinosec scan create --profile def456`).action(async (options) => {
|
|
9791
10161
|
if (!options.asset && !options.profile) {
|
|
9792
10162
|
console.error("Provide --asset <id> or --profile <id>.");
|
|
9793
10163
|
process.exit(2);
|
|
@@ -9797,7 +10167,7 @@ Examples:
|
|
|
9797
10167
|
if (!uuidRe.test(idValue)) {
|
|
9798
10168
|
console.error(`Invalid ID format: "${idValue}"`);
|
|
9799
10169
|
console.error("IDs are UUIDs (e.g. 550e8400-e29b-41d4-a716-446655440000).");
|
|
9800
|
-
console.error("Run
|
|
10170
|
+
console.error("Run kinosec asset list to find your asset ID.");
|
|
9801
10171
|
process.exit(2);
|
|
9802
10172
|
}
|
|
9803
10173
|
try {
|
|
@@ -9815,14 +10185,14 @@ Examples:
|
|
|
9815
10185
|
console.log(` Mode: ${result.scan_mode}`);
|
|
9816
10186
|
console.log(` Status: ${result.scan_status}`);
|
|
9817
10187
|
console.log(`
|
|
9818
|
-
Watch progress:
|
|
10188
|
+
Watch progress: kinosec scan watch ${result.scan_profile_id}`);
|
|
9819
10189
|
} catch (error) {
|
|
9820
10190
|
handleError(error);
|
|
9821
10191
|
}
|
|
9822
10192
|
});
|
|
9823
10193
|
scan.command("cancel").description("Cancel a running scan").argument("<id>", "Scan profile ID").addHelpText("after", `
|
|
9824
10194
|
Examples:
|
|
9825
|
-
$
|
|
10195
|
+
$ kinosec scan cancel abc123`).action(async (id) => {
|
|
9826
10196
|
try {
|
|
9827
10197
|
const opts = program3.opts();
|
|
9828
10198
|
const { token, apiUrl } = resolveToken(opts);
|
|
@@ -9839,9 +10209,9 @@ Examples:
|
|
|
9839
10209
|
});
|
|
9840
10210
|
scan.command("watch").description("Watch scan progress in real-time").argument("<id>", "Scan profile ID").option("--timeout <seconds>", "Max time to watch", "1800").option("--interval <seconds>", "Poll interval", "5").addHelpText("after", `
|
|
9841
10211
|
Examples:
|
|
9842
|
-
$
|
|
9843
|
-
$
|
|
9844
|
-
$
|
|
10212
|
+
$ kinosec scan watch abc123
|
|
10213
|
+
$ kinosec scan watch abc123 --timeout 600
|
|
10214
|
+
$ kinosec scan watch abc123 --json`).action(async (id, options) => {
|
|
9845
10215
|
try {
|
|
9846
10216
|
const opts = program3.opts();
|
|
9847
10217
|
const { token, apiUrl } = resolveToken(opts);
|
|
@@ -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,24 +10255,24 @@ 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
|
-
console.log(` View findings:
|
|
10268
|
+
console.log(` View findings: kinosec finding list --scan ${id}`);
|
|
9899
10269
|
break;
|
|
9900
10270
|
}
|
|
9901
10271
|
if (Date.now() - startTime > timeoutMs) {
|
|
9902
10272
|
cleanup();
|
|
9903
10273
|
console.error(`
|
|
9904
10274
|
Timeout: scan still running after ${options.timeout}s.`);
|
|
9905
|
-
console.error(`Check status:
|
|
10275
|
+
console.error(`Check status: kinosec scan get ${id}`);
|
|
9906
10276
|
process.exit(3);
|
|
9907
10277
|
}
|
|
9908
10278
|
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
@@ -9914,41 +10284,25 @@ 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
|
-
$
|
|
9944
|
-
$
|
|
10292
|
+
$ kinosec asset list
|
|
10293
|
+
$ kinosec asset list --search example.com
|
|
10294
|
+
$ kinosec asset list --type subdomain --status verified
|
|
10295
|
+
$ kinosec asset list --limit 50 --json`).action(async (options) => {
|
|
9945
10296
|
try {
|
|
9946
10297
|
const opts = program3.opts();
|
|
9947
10298
|
const { token, apiUrl } = resolveToken(opts);
|
|
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) {
|
|
@@ -9978,8 +10332,8 @@ More results available. Use --cursor ${result.next_cursor}`));
|
|
|
9978
10332
|
});
|
|
9979
10333
|
asset.command("get").description("Get asset details").argument("<id>", "Asset ID").addHelpText("after", `
|
|
9980
10334
|
Examples:
|
|
9981
|
-
$
|
|
9982
|
-
$
|
|
10335
|
+
$ kinosec asset get abc123
|
|
10336
|
+
$ kinosec asset get abc123 --json`).action(async (id) => {
|
|
9983
10337
|
try {
|
|
9984
10338
|
const opts = program3.opts();
|
|
9985
10339
|
const { token, apiUrl } = resolveToken(opts);
|
|
@@ -10007,8 +10361,8 @@ Examples:
|
|
|
10007
10361
|
});
|
|
10008
10362
|
asset.command("create").description("Create a new asset").requiredOption("--type <type>", "Asset type (domain, subdomain, ip)").requiredOption("--value <value>", "Asset value (e.g. example.com)").requiredOption("--verify-method <method>", "Verification method: dns or file").option("--parent <domain>", "Parent domain (for subdomains)").addHelpText("after", `
|
|
10009
10363
|
Examples:
|
|
10010
|
-
$
|
|
10011
|
-
$
|
|
10364
|
+
$ kinosec asset create --type domain --value example.com --verify-method dns
|
|
10365
|
+
$ kinosec asset create --type subdomain --value api.example.com --verify-method file --parent example.com`).action(async (options) => {
|
|
10012
10366
|
try {
|
|
10013
10367
|
const opts = program3.opts();
|
|
10014
10368
|
const { token, apiUrl } = resolveToken(opts);
|
|
@@ -10028,15 +10382,15 @@ Examples:
|
|
|
10028
10382
|
console.log(` Type: ${result.asset_type}`);
|
|
10029
10383
|
console.log(` Status: ${result.verification_status}`);
|
|
10030
10384
|
console.log(`
|
|
10031
|
-
Verify:
|
|
10385
|
+
Verify: kinosec asset verify ${result.id}`);
|
|
10032
10386
|
} catch (error) {
|
|
10033
10387
|
handleError(error);
|
|
10034
10388
|
}
|
|
10035
10389
|
});
|
|
10036
10390
|
asset.command("delete").description("Delete an asset").argument("<id>", "Asset ID").option("-y, --yes", "Skip confirmation prompt").addHelpText("after", `
|
|
10037
10391
|
Examples:
|
|
10038
|
-
$
|
|
10039
|
-
$
|
|
10392
|
+
$ kinosec asset delete abc123
|
|
10393
|
+
$ kinosec asset delete abc123 --yes`).action(async (id, options) => {
|
|
10040
10394
|
try {
|
|
10041
10395
|
await confirmDestructive(`Delete asset ${id}?`, Boolean(options.yes));
|
|
10042
10396
|
const opts = program3.opts();
|
|
@@ -10054,8 +10408,8 @@ Examples:
|
|
|
10054
10408
|
});
|
|
10055
10409
|
asset.command("verify").description("Trigger asset verification").argument("<id>", "Asset ID").addHelpText("after", `
|
|
10056
10410
|
Examples:
|
|
10057
|
-
$
|
|
10058
|
-
$
|
|
10411
|
+
$ kinosec asset verify abc123
|
|
10412
|
+
$ kinosec asset verify abc123 --json`).action(async (id) => {
|
|
10059
10413
|
try {
|
|
10060
10414
|
const opts = program3.opts();
|
|
10061
10415
|
const { token, apiUrl } = resolveToken(opts);
|
|
@@ -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
|
}
|
|
@@ -10103,8 +10457,8 @@ function registerFindingCommand(program3) {
|
|
|
10103
10457
|
const finding = program3.command("finding").description("Manage findings");
|
|
10104
10458
|
finding.command("list").description("List findings").option("--scan <id>", "Filter by scan profile ID").option("--limit <n>", "Max results per page", "20").option("--cursor <cursor>", "Pagination cursor").addHelpText("after", `
|
|
10105
10459
|
Examples:
|
|
10106
|
-
$
|
|
10107
|
-
$
|
|
10460
|
+
$ kinosec finding list --scan abc123
|
|
10461
|
+
$ kinosec finding list --limit 50 --json`).action(async (options) => {
|
|
10108
10462
|
try {
|
|
10109
10463
|
const opts = program3.opts();
|
|
10110
10464
|
const { token, apiUrl } = resolveToken(opts);
|
|
@@ -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) {
|
|
@@ -10142,8 +10496,8 @@ More results available. Use --cursor ${result.next_cursor}`));
|
|
|
10142
10496
|
});
|
|
10143
10497
|
finding.command("get").description("Get finding details").argument("<id>", "Finding ID").addHelpText("after", `
|
|
10144
10498
|
Examples:
|
|
10145
|
-
$
|
|
10146
|
-
$
|
|
10499
|
+
$ kinosec finding get abc123
|
|
10500
|
+
$ kinosec finding get abc123 --json`).action(async (id) => {
|
|
10147
10501
|
try {
|
|
10148
10502
|
const opts = program3.opts();
|
|
10149
10503
|
const { token, apiUrl } = resolveToken(opts);
|
|
@@ -10173,8 +10527,8 @@ Examples:
|
|
|
10173
10527
|
});
|
|
10174
10528
|
finding.command("update").description("Update finding status").argument("<id>", "Finding ID").requiredOption("--status <status>", "New status: open, fixed, false_positive, investigating, wont_fix").addHelpText("after", `
|
|
10175
10529
|
Examples:
|
|
10176
|
-
$
|
|
10177
|
-
$
|
|
10530
|
+
$ kinosec finding update abc123 --status false_positive
|
|
10531
|
+
$ kinosec finding update abc123 --status fixed`).action(async (id, options) => {
|
|
10178
10532
|
if (!VALID_STATUSES.includes(options.status)) {
|
|
10179
10533
|
console.error(`Invalid status "${options.status}". Valid: ${VALID_STATUSES.join(", ")}`);
|
|
10180
10534
|
process.exit(2);
|
|
@@ -10197,8 +10551,8 @@ Examples:
|
|
|
10197
10551
|
});
|
|
10198
10552
|
finding.command("retest").description("Trigger a retest for a finding").argument("<id>", "Finding ID").addHelpText("after", `
|
|
10199
10553
|
Examples:
|
|
10200
|
-
$
|
|
10201
|
-
$
|
|
10554
|
+
$ kinosec finding retest abc123
|
|
10555
|
+
$ kinosec finding retest abc123 --json`).action(async (id) => {
|
|
10202
10556
|
try {
|
|
10203
10557
|
const opts = program3.opts();
|
|
10204
10558
|
const { token, apiUrl } = resolveToken(opts);
|
|
@@ -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 = [];
|
|
@@ -10266,8 +10620,8 @@ function registerReportCommand(program3) {
|
|
|
10266
10620
|
const report = program3.command("report").description("Manage reports");
|
|
10267
10621
|
report.command("list").description("List reports").option("--limit <n>", "Max results per page", "20").option("--cursor <cursor>", "Pagination cursor").addHelpText("after", `
|
|
10268
10622
|
Examples:
|
|
10269
|
-
$
|
|
10270
|
-
$
|
|
10623
|
+
$ kinosec report list
|
|
10624
|
+
$ kinosec report list --limit 10 --json`).action(async (options) => {
|
|
10271
10625
|
try {
|
|
10272
10626
|
const opts = program3.opts();
|
|
10273
10627
|
const { token, apiUrl } = resolveToken(opts);
|
|
@@ -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) {
|
|
@@ -10306,8 +10660,8 @@ More results available. Use --cursor ${result.next_cursor}`));
|
|
|
10306
10660
|
});
|
|
10307
10661
|
report.command("get").description("Get report details").argument("<id>", "Report ID").addHelpText("after", `
|
|
10308
10662
|
Examples:
|
|
10309
|
-
$
|
|
10310
|
-
$
|
|
10663
|
+
$ kinosec report get abc123
|
|
10664
|
+
$ kinosec report get abc123 --json`).action(async (id) => {
|
|
10311
10665
|
try {
|
|
10312
10666
|
const opts = program3.opts();
|
|
10313
10667
|
const { token, apiUrl } = resolveToken(opts);
|
|
@@ -10341,8 +10695,8 @@ Examples:
|
|
|
10341
10695
|
});
|
|
10342
10696
|
report.command("export").description("Export a scan report").requiredOption("--scan <id>", "Scan report ID").option("--format <format>", "Export format: json or markdown", "json").addHelpText("after", `
|
|
10343
10697
|
Examples:
|
|
10344
|
-
$
|
|
10345
|
-
$
|
|
10698
|
+
$ kinosec report export --scan abc123 --format json > report.json
|
|
10699
|
+
$ kinosec report export --scan abc123 --format markdown > report.md`).action(async (options) => {
|
|
10346
10700
|
const validFormats = ["json", "markdown"];
|
|
10347
10701
|
if (!validFormats.includes(options.format)) {
|
|
10348
10702
|
console.error(`Invalid format "${options.format}". Valid: ${validFormats.join(", ")}`);
|
|
@@ -10368,13 +10722,13 @@ 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", `
|
|
10375
10729
|
Examples:
|
|
10376
|
-
$
|
|
10377
|
-
$
|
|
10730
|
+
$ kinosec token list
|
|
10731
|
+
$ kinosec token list --json`).action(async (options) => {
|
|
10378
10732
|
try {
|
|
10379
10733
|
const opts = program3.opts();
|
|
10380
10734
|
const { token: authToken, apiUrl } = resolveToken(opts);
|
|
@@ -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) {
|
|
@@ -10412,8 +10766,8 @@ More results available. Use --cursor ${result.next_cursor}`));
|
|
|
10412
10766
|
});
|
|
10413
10767
|
token.command("create").description("Create a new API token").requiredOption("--name <name>", "Token name").requiredOption("--scopes <scopes>", "Comma-separated scopes (e.g. assets:read,scans:create)").option("--expires <days>", "Days until expiry", "90").addHelpText("after", `
|
|
10414
10768
|
Examples:
|
|
10415
|
-
$
|
|
10416
|
-
$
|
|
10769
|
+
$ kinosec token create --name "CI" --scopes assets:read,scans:create
|
|
10770
|
+
$ kinosec token create --name "Full" --scopes assets:read,scans:read --expires 30`).action(async (options) => {
|
|
10417
10771
|
try {
|
|
10418
10772
|
const opts = program3.opts();
|
|
10419
10773
|
const { token: authToken, apiUrl } = resolveToken(opts);
|
|
@@ -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}`);
|
|
@@ -10442,8 +10796,8 @@ Examples:
|
|
|
10442
10796
|
});
|
|
10443
10797
|
token.command("revoke").description("Revoke an API token").argument("<id>", "Token ID").option("-y, --yes", "Skip confirmation prompt").addHelpText("after", `
|
|
10444
10798
|
Examples:
|
|
10445
|
-
$
|
|
10446
|
-
$
|
|
10799
|
+
$ kinosec token revoke abc123
|
|
10800
|
+
$ kinosec token revoke abc123 --yes`).action(async (id, options) => {
|
|
10447
10801
|
try {
|
|
10448
10802
|
await confirmDestructive(`Revoke token ${id}?`, Boolean(options.yes));
|
|
10449
10803
|
const opts = program3.opts();
|
|
@@ -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("
|
|
10820
|
+
program2.name("kinosec").description("KinoSec CLI \u2014 run pentests from your terminal").version("0.2.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");
|
|
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.0")).catch(handleError);
|
|
10477
10830
|
/*! Bundled license information:
|
|
10478
10831
|
|
|
10479
10832
|
ky/distribution/index.js:
|