@oculum/cli 1.0.10 → 1.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +515 -146
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -40915,8 +40915,8 @@ var {
|
|
|
40915
40915
|
} = import_index.default;
|
|
40916
40916
|
|
|
40917
40917
|
// src/commands/scan.ts
|
|
40918
|
-
var
|
|
40919
|
-
var
|
|
40918
|
+
var import_path4 = require("path");
|
|
40919
|
+
var import_fs5 = require("fs");
|
|
40920
40920
|
init_esm7();
|
|
40921
40921
|
|
|
40922
40922
|
// node_modules/chalk/source/vendor/ansi-styles/index.js
|
|
@@ -43882,8 +43882,83 @@ function validateConfig(config) {
|
|
|
43882
43882
|
validated.watch.clear = config.watch.clear;
|
|
43883
43883
|
}
|
|
43884
43884
|
}
|
|
43885
|
+
if (config.profiles && typeof config.profiles === "object") {
|
|
43886
|
+
validated.profiles = config.profiles;
|
|
43887
|
+
}
|
|
43888
|
+
if (typeof config.defaultProfile === "string") {
|
|
43889
|
+
validated.defaultProfile = config.defaultProfile;
|
|
43890
|
+
}
|
|
43885
43891
|
return validated;
|
|
43886
43892
|
}
|
|
43893
|
+
function getProfileConfig(projectConfig, profileName) {
|
|
43894
|
+
if (!projectConfig) return null;
|
|
43895
|
+
const name = profileName || projectConfig.defaultProfile;
|
|
43896
|
+
if (!name) return null;
|
|
43897
|
+
const profile = projectConfig.profiles?.[name];
|
|
43898
|
+
if (!profile) {
|
|
43899
|
+
if (profileName) {
|
|
43900
|
+
console.warn(source_default.yellow(`Warning: Profile "${profileName}" not found in config`));
|
|
43901
|
+
}
|
|
43902
|
+
return null;
|
|
43903
|
+
}
|
|
43904
|
+
return profile;
|
|
43905
|
+
}
|
|
43906
|
+
|
|
43907
|
+
// src/utils/history.ts
|
|
43908
|
+
var import_fs4 = require("fs");
|
|
43909
|
+
var import_path3 = require("path");
|
|
43910
|
+
var import_os2 = require("os");
|
|
43911
|
+
var import_crypto = require("crypto");
|
|
43912
|
+
var CONFIG_DIR2 = (0, import_path3.join)((0, import_os2.homedir)(), ".oculum");
|
|
43913
|
+
var HISTORY_FILE = (0, import_path3.join)(CONFIG_DIR2, "history.json");
|
|
43914
|
+
var MAX_ENTRIES = 25;
|
|
43915
|
+
function ensureConfigDir2() {
|
|
43916
|
+
if (!(0, import_fs4.existsSync)(CONFIG_DIR2)) {
|
|
43917
|
+
(0, import_fs4.mkdirSync)(CONFIG_DIR2, { recursive: true });
|
|
43918
|
+
}
|
|
43919
|
+
}
|
|
43920
|
+
function readHistoryFile() {
|
|
43921
|
+
try {
|
|
43922
|
+
if (!(0, import_fs4.existsSync)(HISTORY_FILE)) return [];
|
|
43923
|
+
const content = (0, import_fs4.readFileSync)(HISTORY_FILE, "utf-8");
|
|
43924
|
+
const parsed = JSON.parse(content);
|
|
43925
|
+
if (!Array.isArray(parsed)) return [];
|
|
43926
|
+
return parsed;
|
|
43927
|
+
} catch {
|
|
43928
|
+
return [];
|
|
43929
|
+
}
|
|
43930
|
+
}
|
|
43931
|
+
function writeHistoryFile(entries) {
|
|
43932
|
+
ensureConfigDir2();
|
|
43933
|
+
(0, import_fs4.writeFileSync)(HISTORY_FILE, JSON.stringify(entries, null, 2));
|
|
43934
|
+
}
|
|
43935
|
+
function listScanHistory() {
|
|
43936
|
+
return readHistoryFile();
|
|
43937
|
+
}
|
|
43938
|
+
function getScanHistoryEntry(id) {
|
|
43939
|
+
return readHistoryFile().find((e2) => e2.id === id);
|
|
43940
|
+
}
|
|
43941
|
+
function addScanHistoryEntry(input) {
|
|
43942
|
+
const entry = {
|
|
43943
|
+
id: input.id ?? (0, import_crypto.randomUUID)(),
|
|
43944
|
+
createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
43945
|
+
targetPath: input.targetPath,
|
|
43946
|
+
options: input.options,
|
|
43947
|
+
result: input.result
|
|
43948
|
+
};
|
|
43949
|
+
const current = readHistoryFile();
|
|
43950
|
+
const updated = [entry, ...current].slice(0, MAX_ENTRIES);
|
|
43951
|
+
writeHistoryFile(updated);
|
|
43952
|
+
return entry;
|
|
43953
|
+
}
|
|
43954
|
+
function deleteScanHistoryEntry(id) {
|
|
43955
|
+
const current = readHistoryFile();
|
|
43956
|
+
const updated = current.filter((e2) => e2.id !== id);
|
|
43957
|
+
writeHistoryFile(updated);
|
|
43958
|
+
}
|
|
43959
|
+
function clearScanHistory() {
|
|
43960
|
+
writeHistoryFile([]);
|
|
43961
|
+
}
|
|
43887
43962
|
|
|
43888
43963
|
// src/commands/scan.ts
|
|
43889
43964
|
function getChangedFiles(absolutePath, diff) {
|
|
@@ -43900,22 +43975,22 @@ function getChangedFiles(absolutePath, diff) {
|
|
|
43900
43975
|
args = ["diff", "--name-only", "HEAD"];
|
|
43901
43976
|
}
|
|
43902
43977
|
const out = (0, import_child_process.execFileSync)("git", args, { cwd: gitRoot, stdio: ["ignore", "pipe", "ignore"] }).toString();
|
|
43903
|
-
const files = out.split("\n").map((s) => s.trim()).filter(Boolean).map((p2) => (0,
|
|
43978
|
+
const files = out.split("\n").map((s) => s.trim()).filter(Boolean).map((p2) => (0, import_path4.resolve)(gitRoot, p2)).filter((p2) => p2.startsWith(absolutePath));
|
|
43904
43979
|
return files;
|
|
43905
43980
|
} catch {
|
|
43906
43981
|
return [];
|
|
43907
43982
|
}
|
|
43908
43983
|
}
|
|
43909
43984
|
function isScannableFile(filePath) {
|
|
43910
|
-
const ext2 = (0,
|
|
43911
|
-
const fileName = (0,
|
|
43985
|
+
const ext2 = (0, import_path4.extname)(filePath).toLowerCase();
|
|
43986
|
+
const fileName = (0, import_path4.basename)(filePath);
|
|
43912
43987
|
if (import_scanner.SPECIAL_FILES.includes(fileName)) {
|
|
43913
43988
|
return true;
|
|
43914
43989
|
}
|
|
43915
43990
|
return import_scanner.SCANNABLE_EXTENSIONS.includes(ext2);
|
|
43916
43991
|
}
|
|
43917
43992
|
function getLanguage(filePath) {
|
|
43918
|
-
const ext2 = (0,
|
|
43993
|
+
const ext2 = (0, import_path4.extname)(filePath).toLowerCase();
|
|
43919
43994
|
const langMap = {
|
|
43920
43995
|
".js": "javascript",
|
|
43921
43996
|
".jsx": "javascript",
|
|
@@ -43940,15 +44015,15 @@ function getLanguage(filePath) {
|
|
|
43940
44015
|
return langMap[ext2] || "text";
|
|
43941
44016
|
}
|
|
43942
44017
|
async function collectFiles(targetPath) {
|
|
43943
|
-
const absolutePath = (0,
|
|
43944
|
-
const stats = (0,
|
|
44018
|
+
const absolutePath = (0, import_path4.resolve)(targetPath);
|
|
44019
|
+
const stats = (0, import_fs5.statSync)(absolutePath);
|
|
43945
44020
|
const files = [];
|
|
43946
44021
|
if (stats.isFile()) {
|
|
43947
44022
|
if (isScannableFile(absolutePath)) {
|
|
43948
|
-
const content = (0,
|
|
44023
|
+
const content = (0, import_fs5.readFileSync)(absolutePath, "utf-8");
|
|
43949
44024
|
if (content.length <= import_scanner.MAX_FILE_SIZE) {
|
|
43950
44025
|
files.push({
|
|
43951
|
-
path: (0,
|
|
44026
|
+
path: (0, import_path4.relative)(process.cwd(), absolutePath),
|
|
43952
44027
|
content,
|
|
43953
44028
|
language: getLanguage(absolutePath),
|
|
43954
44029
|
size: content.length
|
|
@@ -44017,8 +44092,8 @@ async function collectFiles(targetPath) {
|
|
|
44017
44092
|
"**/.env.*.local",
|
|
44018
44093
|
"**/**.env"
|
|
44019
44094
|
];
|
|
44020
|
-
const gitignorePath = (0,
|
|
44021
|
-
const hasGitignore = (0,
|
|
44095
|
+
const gitignorePath = (0, import_path4.join)(absolutePath, ".gitignore");
|
|
44096
|
+
const hasGitignore = (0, import_fs5.existsSync)(gitignorePath);
|
|
44022
44097
|
const globOptions = {
|
|
44023
44098
|
cwd: absolutePath,
|
|
44024
44099
|
ignore: ignorePatterns,
|
|
@@ -44032,11 +44107,11 @@ async function collectFiles(targetPath) {
|
|
|
44032
44107
|
for (const file of foundFiles) {
|
|
44033
44108
|
const filePath = String(file);
|
|
44034
44109
|
try {
|
|
44035
|
-
const fileStats = (0,
|
|
44110
|
+
const fileStats = (0, import_fs5.statSync)(filePath);
|
|
44036
44111
|
if (fileStats.size > import_scanner.MAX_FILE_SIZE) continue;
|
|
44037
|
-
const content = (0,
|
|
44112
|
+
const content = (0, import_fs5.readFileSync)(filePath, "utf-8");
|
|
44038
44113
|
files.push({
|
|
44039
|
-
path: (0,
|
|
44114
|
+
path: (0, import_path4.relative)(process.cwd(), filePath),
|
|
44040
44115
|
content,
|
|
44041
44116
|
language: getLanguage(filePath),
|
|
44042
44117
|
size: content.length
|
|
@@ -44048,8 +44123,8 @@ async function collectFiles(targetPath) {
|
|
|
44048
44123
|
return files;
|
|
44049
44124
|
}
|
|
44050
44125
|
async function collectFilesForScan(targetPath, options) {
|
|
44051
|
-
const absolutePath = (0,
|
|
44052
|
-
const stats = (0,
|
|
44126
|
+
const absolutePath = (0, import_path4.resolve)(targetPath);
|
|
44127
|
+
const stats = (0, import_fs5.statSync)(absolutePath);
|
|
44053
44128
|
if (stats.isFile()) {
|
|
44054
44129
|
return collectFiles(targetPath);
|
|
44055
44130
|
}
|
|
@@ -44058,13 +44133,13 @@ async function collectFilesForScan(targetPath, options) {
|
|
|
44058
44133
|
const files = [];
|
|
44059
44134
|
for (const filePath of changed) {
|
|
44060
44135
|
try {
|
|
44061
|
-
if (!(0,
|
|
44136
|
+
if (!(0, import_fs5.existsSync)(filePath)) continue;
|
|
44062
44137
|
if (!isScannableFile(filePath)) continue;
|
|
44063
|
-
const fileStats = (0,
|
|
44138
|
+
const fileStats = (0, import_fs5.statSync)(filePath);
|
|
44064
44139
|
if (fileStats.size > import_scanner.MAX_FILE_SIZE) continue;
|
|
44065
|
-
const content = (0,
|
|
44140
|
+
const content = (0, import_fs5.readFileSync)(filePath, "utf-8");
|
|
44066
44141
|
files.push({
|
|
44067
|
-
path: (0,
|
|
44142
|
+
path: (0, import_path4.relative)(process.cwd(), filePath),
|
|
44068
44143
|
content,
|
|
44069
44144
|
language: getLanguage(filePath),
|
|
44070
44145
|
size: content.length
|
|
@@ -44078,7 +44153,7 @@ async function collectFilesForScan(targetPath, options) {
|
|
|
44078
44153
|
}
|
|
44079
44154
|
function createEmptyResult(targetPath) {
|
|
44080
44155
|
return {
|
|
44081
|
-
repoName: (0,
|
|
44156
|
+
repoName: (0, import_path4.basename)((0, import_path4.resolve)(targetPath)),
|
|
44082
44157
|
repoUrl: "",
|
|
44083
44158
|
branch: "local",
|
|
44084
44159
|
filesScanned: 0,
|
|
@@ -44300,7 +44375,7 @@ async function runScanOnce(targetPath, options) {
|
|
|
44300
44375
|
options.depth,
|
|
44301
44376
|
config.apiKey,
|
|
44302
44377
|
{
|
|
44303
|
-
name: (0,
|
|
44378
|
+
name: (0, import_path4.basename)((0, import_path4.resolve)(targetPath)),
|
|
44304
44379
|
url: "",
|
|
44305
44380
|
branch: "local"
|
|
44306
44381
|
}
|
|
@@ -44312,7 +44387,7 @@ async function runScanOnce(targetPath, options) {
|
|
|
44312
44387
|
result = await (0, import_scanner.runScan)(
|
|
44313
44388
|
files,
|
|
44314
44389
|
{
|
|
44315
|
-
name: (0,
|
|
44390
|
+
name: (0, import_path4.basename)((0, import_path4.resolve)(targetPath)),
|
|
44316
44391
|
url: "",
|
|
44317
44392
|
branch: "local"
|
|
44318
44393
|
},
|
|
@@ -44375,32 +44450,33 @@ Results written to ${options.output}`));
|
|
|
44375
44450
|
}
|
|
44376
44451
|
async function runScan(targetPath, cliOptions) {
|
|
44377
44452
|
const projectConfig = loadProjectConfig(targetPath, cliOptions.quiet);
|
|
44378
|
-
|
|
44453
|
+
const profileConfig = getProfileConfig(projectConfig, cliOptions.profile);
|
|
44454
|
+
let scanDepth = cliOptions.mode || cliOptions.depth;
|
|
44379
44455
|
if (scanDepth === "quick") {
|
|
44380
44456
|
scanDepth = "cheap";
|
|
44381
44457
|
}
|
|
44382
|
-
const
|
|
44383
|
-
depth: scanDepth,
|
|
44384
|
-
format: cliOptions.format ?? "terminal",
|
|
44385
|
-
failOn: cliOptions.failOn ?? "high",
|
|
44458
|
+
const options = {
|
|
44459
|
+
depth: scanDepth ?? profileConfig?.depth ?? projectConfig?.depth ?? "cheap",
|
|
44460
|
+
format: cliOptions.format ?? profileConfig?.format ?? projectConfig?.format ?? "terminal",
|
|
44461
|
+
failOn: cliOptions.failOn ?? profileConfig?.failOn ?? projectConfig?.failOn ?? "high",
|
|
44386
44462
|
color: cliOptions.color,
|
|
44387
|
-
quiet: cliOptions.quiet,
|
|
44463
|
+
quiet: cliOptions.quiet ?? profileConfig?.quiet ?? projectConfig?.quiet,
|
|
44388
44464
|
verbose: cliOptions.verbose,
|
|
44389
44465
|
incremental: cliOptions.incremental,
|
|
44390
44466
|
diff: cliOptions.diff,
|
|
44391
|
-
output: cliOptions.output
|
|
44392
|
-
|
|
44393
|
-
const options = {
|
|
44394
|
-
...baseOptions,
|
|
44395
|
-
depth: baseOptions.depth ?? projectConfig?.depth ?? "cheap",
|
|
44396
|
-
format: baseOptions.format ?? projectConfig?.format ?? "terminal",
|
|
44397
|
-
failOn: baseOptions.failOn ?? projectConfig?.failOn ?? "high",
|
|
44398
|
-
quiet: baseOptions.quiet ?? projectConfig?.quiet,
|
|
44399
|
-
verbose: baseOptions.verbose,
|
|
44400
|
-
output: baseOptions.output ?? projectConfig?.output
|
|
44467
|
+
output: cliOptions.output ?? profileConfig?.output ?? projectConfig?.output,
|
|
44468
|
+
profile: cliOptions.profile
|
|
44401
44469
|
};
|
|
44402
44470
|
try {
|
|
44403
|
-
const { output, exitCode } = await runScanOnce(targetPath, options);
|
|
44471
|
+
const { result, output, exitCode } = await runScanOnce(targetPath, options);
|
|
44472
|
+
try {
|
|
44473
|
+
addScanHistoryEntry({
|
|
44474
|
+
targetPath,
|
|
44475
|
+
options: { depth: options.depth, format: options.format, failOn: options.failOn },
|
|
44476
|
+
result
|
|
44477
|
+
});
|
|
44478
|
+
} catch {
|
|
44479
|
+
}
|
|
44404
44480
|
console.log(output);
|
|
44405
44481
|
if (exitCode !== 0) {
|
|
44406
44482
|
process.exit(exitCode);
|
|
@@ -44411,7 +44487,7 @@ async function runScan(targetPath, cliOptions) {
|
|
|
44411
44487
|
process.exit(1);
|
|
44412
44488
|
}
|
|
44413
44489
|
}
|
|
44414
|
-
var scanCommand = new Command("scan").description("Scan a directory or file for security vulnerabilities").argument("[path]", "path to scan", ".").option("-d, --depth <depth>", "scan depth: cheap (free), validated, deep"
|
|
44490
|
+
var scanCommand = new Command("scan").description("Scan a directory or file for security vulnerabilities").argument("[path]", "path to scan", ".").option("-d, --depth <depth>", "scan depth: cheap (free), validated, deep").option("-m, --mode <mode>", "alias for --depth (quick, validated, deep)").option("-f, --format <format>", "output format: terminal, json, sarif, markdown").option("--fail-on <severity>", "exit with error code if findings at severity").option("--no-color", "disable colored output").option("-q, --quiet", "minimal output for CI (suppress spinners and decorations)").option("-v, --verbose", "show detailed scanner logs (debug mode)").option("-o, --output <file>", "write output to file").option("-p, --profile <name>", "use named profile from oculum.config.json").option("--incremental", "only scan changed files (requires git)").option("--diff <ref>", "diff against branch/commit for incremental scan").addHelpText("after", `
|
|
44415
44491
|
Scan Modes:
|
|
44416
44492
|
cheap Free Fast pattern matching, runs locally
|
|
44417
44493
|
Best for: Quick checks, CI/CD pipelines
|
|
@@ -44436,9 +44512,14 @@ Configuration:
|
|
|
44436
44512
|
{
|
|
44437
44513
|
"depth": "validated",
|
|
44438
44514
|
"failOn": "high",
|
|
44439
|
-
"
|
|
44515
|
+
"profiles": {
|
|
44516
|
+
"ci": { "depth": "validated", "quiet": true, "format": "sarif" },
|
|
44517
|
+
"strict": { "depth": "validated", "failOn": "medium" }
|
|
44518
|
+
}
|
|
44440
44519
|
}
|
|
44441
44520
|
|
|
44521
|
+
Use profiles: oculum scan . --profile ci
|
|
44522
|
+
|
|
44442
44523
|
More Help:
|
|
44443
44524
|
$ oculum help scan-modes Detailed mode comparison
|
|
44444
44525
|
$ oculum help ci-setup CI/CD integration examples
|
|
@@ -44615,11 +44696,12 @@ var statusCommand = new Command("status").description("Show current authenticati
|
|
|
44615
44696
|
var upgradeCommand = new Command("upgrade").description("Upgrade your subscription").action(upgrade);
|
|
44616
44697
|
|
|
44617
44698
|
// src/commands/watch.ts
|
|
44618
|
-
var
|
|
44619
|
-
var
|
|
44699
|
+
var import_path5 = require("path");
|
|
44700
|
+
var import_fs8 = require("fs");
|
|
44701
|
+
var readline = __toESM(require("readline"));
|
|
44620
44702
|
|
|
44621
44703
|
// ../../node_modules/chokidar/esm/index.js
|
|
44622
|
-
var
|
|
44704
|
+
var import_fs7 = require("fs");
|
|
44623
44705
|
var import_promises4 = require("fs/promises");
|
|
44624
44706
|
var import_events = require("events");
|
|
44625
44707
|
var sysPath2 = __toESM(require("path"), 1);
|
|
@@ -44844,10 +44926,10 @@ function readdirp(root, options = {}) {
|
|
|
44844
44926
|
}
|
|
44845
44927
|
|
|
44846
44928
|
// ../../node_modules/chokidar/esm/handler.js
|
|
44847
|
-
var
|
|
44929
|
+
var import_fs6 = require("fs");
|
|
44848
44930
|
var import_promises3 = require("fs/promises");
|
|
44849
44931
|
var sysPath = __toESM(require("path"), 1);
|
|
44850
|
-
var
|
|
44932
|
+
var import_os3 = require("os");
|
|
44851
44933
|
var STR_DATA = "data";
|
|
44852
44934
|
var STR_END = "end";
|
|
44853
44935
|
var STR_CLOSE = "close";
|
|
@@ -44858,7 +44940,7 @@ var isWindows = pl === "win32";
|
|
|
44858
44940
|
var isMacos = pl === "darwin";
|
|
44859
44941
|
var isLinux = pl === "linux";
|
|
44860
44942
|
var isFreeBSD = pl === "freebsd";
|
|
44861
|
-
var isIBMi = (0,
|
|
44943
|
+
var isIBMi = (0, import_os3.type)() === "OS400";
|
|
44862
44944
|
var EVENTS = {
|
|
44863
44945
|
ALL: "all",
|
|
44864
44946
|
READY: "ready",
|
|
@@ -45182,7 +45264,7 @@ function createFsWatchInstance(path2, options, listener, errHandler, emitRaw) {
|
|
|
45182
45264
|
}
|
|
45183
45265
|
};
|
|
45184
45266
|
try {
|
|
45185
|
-
return (0,
|
|
45267
|
+
return (0, import_fs6.watch)(path2, {
|
|
45186
45268
|
persistent: options.persistent
|
|
45187
45269
|
}, handleEvent);
|
|
45188
45270
|
} catch (error) {
|
|
@@ -45265,7 +45347,7 @@ var setFsWatchFileListener = (path2, fullPath, options, handlers) => {
|
|
|
45265
45347
|
let cont = FsWatchFileInstances.get(fullPath);
|
|
45266
45348
|
const copts = cont && cont.options;
|
|
45267
45349
|
if (copts && (copts.persistent < options.persistent || copts.interval > options.interval)) {
|
|
45268
|
-
(0,
|
|
45350
|
+
(0, import_fs6.unwatchFile)(fullPath);
|
|
45269
45351
|
cont = void 0;
|
|
45270
45352
|
}
|
|
45271
45353
|
if (cont) {
|
|
@@ -45276,7 +45358,7 @@ var setFsWatchFileListener = (path2, fullPath, options, handlers) => {
|
|
|
45276
45358
|
listeners: listener,
|
|
45277
45359
|
rawEmitters: rawEmitter,
|
|
45278
45360
|
options,
|
|
45279
|
-
watcher: (0,
|
|
45361
|
+
watcher: (0, import_fs6.watchFile)(fullPath, options, (curr, prev) => {
|
|
45280
45362
|
foreach(cont.rawEmitters, (rawEmitter2) => {
|
|
45281
45363
|
rawEmitter2(EV.CHANGE, fullPath, { curr, prev });
|
|
45282
45364
|
});
|
|
@@ -45293,7 +45375,7 @@ var setFsWatchFileListener = (path2, fullPath, options, handlers) => {
|
|
|
45293
45375
|
delFromSet(cont, KEY_RAW, rawEmitter);
|
|
45294
45376
|
if (isEmptySet(cont.listeners)) {
|
|
45295
45377
|
FsWatchFileInstances.delete(fullPath);
|
|
45296
|
-
(0,
|
|
45378
|
+
(0, import_fs6.unwatchFile)(fullPath);
|
|
45297
45379
|
cont.options = cont.watcher = void 0;
|
|
45298
45380
|
Object.freeze(cont);
|
|
45299
45381
|
}
|
|
@@ -46136,7 +46218,7 @@ var FSWatcher = class extends import_events.EventEmitter {
|
|
|
46136
46218
|
const now = /* @__PURE__ */ new Date();
|
|
46137
46219
|
const writes = this._pendingWrites;
|
|
46138
46220
|
function awaitWriteFinishFn(prevStat) {
|
|
46139
|
-
(0,
|
|
46221
|
+
(0, import_fs7.stat)(fullPath, (err, curStat) => {
|
|
46140
46222
|
if (err || !writes.has(path2)) {
|
|
46141
46223
|
if (err && err.code !== "ENOENT")
|
|
46142
46224
|
awfEmit(err);
|
|
@@ -46313,13 +46395,13 @@ var esm_default = { watch, FSWatcher };
|
|
|
46313
46395
|
var import_scanner2 = __toESM(require_dist());
|
|
46314
46396
|
var import_formatters2 = __toESM(require_formatters());
|
|
46315
46397
|
function isScannableFile2(filePath) {
|
|
46316
|
-
const ext2 = (0,
|
|
46317
|
-
const fileName = (0,
|
|
46398
|
+
const ext2 = (0, import_path5.extname)(filePath).toLowerCase();
|
|
46399
|
+
const fileName = (0, import_path5.basename)(filePath);
|
|
46318
46400
|
if (import_scanner2.SPECIAL_FILES.includes(fileName)) return true;
|
|
46319
46401
|
return import_scanner2.SCANNABLE_EXTENSIONS.includes(ext2);
|
|
46320
46402
|
}
|
|
46321
46403
|
function getLanguage2(filePath) {
|
|
46322
|
-
const ext2 = (0,
|
|
46404
|
+
const ext2 = (0, import_path5.extname)(filePath).toLowerCase();
|
|
46323
46405
|
const langMap = {
|
|
46324
46406
|
".js": "javascript",
|
|
46325
46407
|
".jsx": "javascript",
|
|
@@ -46338,11 +46420,11 @@ function getLanguage2(filePath) {
|
|
|
46338
46420
|
}
|
|
46339
46421
|
function readScanFile(filePath) {
|
|
46340
46422
|
try {
|
|
46341
|
-
const stats = (0,
|
|
46423
|
+
const stats = (0, import_fs8.statSync)(filePath);
|
|
46342
46424
|
if (stats.size > import_scanner2.MAX_FILE_SIZE) return null;
|
|
46343
|
-
const content = (0,
|
|
46425
|
+
const content = (0, import_fs8.readFileSync)(filePath, "utf-8");
|
|
46344
46426
|
return {
|
|
46345
|
-
path: (0,
|
|
46427
|
+
path: (0, import_path5.relative)(process.cwd(), filePath),
|
|
46346
46428
|
content,
|
|
46347
46429
|
language: getLanguage2(filePath),
|
|
46348
46430
|
size: content.length
|
|
@@ -46359,7 +46441,7 @@ function debounce(fn, delay) {
|
|
|
46359
46441
|
};
|
|
46360
46442
|
}
|
|
46361
46443
|
async function watch2(targetPath, options) {
|
|
46362
|
-
const absolutePath = (0,
|
|
46444
|
+
const absolutePath = (0, import_path5.resolve)(targetPath);
|
|
46363
46445
|
const config = getConfig();
|
|
46364
46446
|
if ((options.depth === "validated" || options.depth === "deep") && !isAuthenticated()) {
|
|
46365
46447
|
if (!options.quiet) {
|
|
@@ -46369,31 +46451,40 @@ async function watch2(targetPath, options) {
|
|
|
46369
46451
|
}
|
|
46370
46452
|
options.depth = "cheap";
|
|
46371
46453
|
}
|
|
46372
|
-
|
|
46454
|
+
const changedFiles = /* @__PURE__ */ new Set();
|
|
46455
|
+
let isPaused = false;
|
|
46456
|
+
let scanCount = 0;
|
|
46457
|
+
let totalIssues = 0;
|
|
46458
|
+
const showBanner = () => {
|
|
46459
|
+
if (options.quiet) return;
|
|
46373
46460
|
console.log("");
|
|
46374
46461
|
console.log(source_default.bold(" \u{1F441}\uFE0F Oculum Watch Mode"));
|
|
46375
|
-
console.log(source_default.dim(" " + "\u2500".repeat(
|
|
46462
|
+
console.log(source_default.dim(" " + "\u2500".repeat(50)));
|
|
46376
46463
|
console.log("");
|
|
46377
46464
|
console.log(source_default.dim(" Watching: ") + source_default.white(absolutePath));
|
|
46378
46465
|
console.log(source_default.dim(" Depth: ") + source_default.white(options.depth === "cheap" ? "Quick (pattern matching)" : options.depth));
|
|
46379
|
-
console.log(source_default.dim("
|
|
46466
|
+
console.log(source_default.dim(" Status: ") + (isPaused ? source_default.yellow("Paused") : source_default.green("Active")));
|
|
46467
|
+
if (scanCount > 0) {
|
|
46468
|
+
console.log(source_default.dim(" Scans: ") + source_default.white(`${scanCount} (${totalIssues} issues found)`));
|
|
46469
|
+
}
|
|
46380
46470
|
console.log("");
|
|
46381
|
-
console.log(source_default.dim("
|
|
46471
|
+
console.log(source_default.dim(" Keyboard: ") + source_default.white("[r]") + source_default.dim(" rescan ") + source_default.white("[c]") + source_default.dim(" clear ") + source_default.white("[p]") + source_default.dim(" pause ") + source_default.white("[q]") + source_default.dim(" quit"));
|
|
46472
|
+
console.log(source_default.dim(" " + "\u2500".repeat(50)));
|
|
46382
46473
|
console.log("");
|
|
46383
|
-
}
|
|
46384
|
-
|
|
46474
|
+
};
|
|
46475
|
+
showBanner();
|
|
46385
46476
|
let isScanning = false;
|
|
46386
46477
|
const runScanOnChanges = debounce(async () => {
|
|
46387
|
-
if (isScanning || changedFiles.size === 0) return;
|
|
46478
|
+
if (isScanning || changedFiles.size === 0 || isPaused) return;
|
|
46388
46479
|
isScanning = true;
|
|
46389
46480
|
const filesToScan = Array.from(changedFiles);
|
|
46390
46481
|
changedFiles.clear();
|
|
46391
46482
|
if (options.clearOnScan && !options.quiet) {
|
|
46392
46483
|
console.clear();
|
|
46484
|
+
showBanner();
|
|
46393
46485
|
}
|
|
46394
46486
|
if (!options.quiet) {
|
|
46395
46487
|
const fileText = filesToScan.length === 1 ? "file" : "files";
|
|
46396
|
-
console.log("");
|
|
46397
46488
|
console.log(source_default.cyan(` \u27F3 [${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] Scanning ${filesToScan.length} changed ${fileText}...`));
|
|
46398
46489
|
}
|
|
46399
46490
|
const scanFiles = [];
|
|
@@ -46403,7 +46494,7 @@ async function watch2(targetPath, options) {
|
|
|
46403
46494
|
}
|
|
46404
46495
|
if (scanFiles.length === 0) {
|
|
46405
46496
|
if (!options.quiet) {
|
|
46406
|
-
console.log(source_default.dim("No scannable files found."));
|
|
46497
|
+
console.log(source_default.dim(" No scannable files found."));
|
|
46407
46498
|
}
|
|
46408
46499
|
isScanning = false;
|
|
46409
46500
|
return;
|
|
@@ -46412,24 +46503,36 @@ async function watch2(targetPath, options) {
|
|
|
46412
46503
|
const result = await (0, import_scanner2.runScan)(
|
|
46413
46504
|
scanFiles,
|
|
46414
46505
|
{
|
|
46415
|
-
name: (0,
|
|
46506
|
+
name: (0, import_path5.basename)(absolutePath),
|
|
46416
46507
|
url: "",
|
|
46417
46508
|
branch: "watch"
|
|
46418
46509
|
},
|
|
46419
46510
|
{
|
|
46420
46511
|
enableAI: options.depth !== "cheap" && isAuthenticated(),
|
|
46421
46512
|
scanDepth: options.depth,
|
|
46422
|
-
scanMode: "incremental"
|
|
46513
|
+
scanMode: "incremental",
|
|
46514
|
+
quiet: true
|
|
46515
|
+
// Suppress internal scanner logs
|
|
46423
46516
|
}
|
|
46424
46517
|
);
|
|
46518
|
+
scanCount++;
|
|
46519
|
+
totalIssues += result.vulnerabilities.length;
|
|
46520
|
+
try {
|
|
46521
|
+
addScanHistoryEntry({
|
|
46522
|
+
targetPath: absolutePath,
|
|
46523
|
+
options: { depth: options.depth, format: "terminal", failOn: "high" },
|
|
46524
|
+
result
|
|
46525
|
+
});
|
|
46526
|
+
} catch {
|
|
46527
|
+
}
|
|
46425
46528
|
if (result.vulnerabilities.length === 0) {
|
|
46426
46529
|
if (!options.quiet) {
|
|
46427
|
-
console.log(source_default.green("
|
|
46530
|
+
console.log(source_default.green(" \u2713 No issues found"));
|
|
46428
46531
|
}
|
|
46429
46532
|
} else {
|
|
46430
46533
|
const issueCount = result.vulnerabilities.length;
|
|
46431
46534
|
const issueText = issueCount === 1 ? "issue" : "issues";
|
|
46432
|
-
console.log(source_default.yellow(`
|
|
46535
|
+
console.log(source_default.yellow(` \u26A0 Found ${issueCount} ${issueText}:`));
|
|
46433
46536
|
console.log((0, import_formatters2.formatTerminalOutput)(result, {
|
|
46434
46537
|
maxFindingsPerGroup: 5
|
|
46435
46538
|
}));
|
|
@@ -46440,36 +46543,94 @@ async function watch2(targetPath, options) {
|
|
|
46440
46543
|
}
|
|
46441
46544
|
isScanning = false;
|
|
46442
46545
|
}, options.debounce);
|
|
46443
|
-
const
|
|
46444
|
-
|
|
46546
|
+
const triggerFullRescan = async () => {
|
|
46547
|
+
if (isScanning) return;
|
|
46548
|
+
if (!options.quiet) {
|
|
46549
|
+
console.log(source_default.cyan(`
|
|
46550
|
+
\u27F3 [${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] Triggering full rescan...`));
|
|
46551
|
+
}
|
|
46552
|
+
const { glob: glob3 } = await Promise.resolve().then(() => (init_esm7(), esm_exports));
|
|
46553
|
+
const patterns2 = [
|
|
46554
|
+
"**/*.js",
|
|
46555
|
+
"**/*.jsx",
|
|
46556
|
+
"**/*.ts",
|
|
46557
|
+
"**/*.tsx",
|
|
46558
|
+
"**/*.py",
|
|
46559
|
+
"**/*.go",
|
|
46560
|
+
"**/*.java",
|
|
46561
|
+
"**/*.rb",
|
|
46562
|
+
"**/*.php",
|
|
46563
|
+
"**/*.cs",
|
|
46564
|
+
"**/package.json"
|
|
46565
|
+
];
|
|
46566
|
+
const ignorePatterns2 = [
|
|
46445
46567
|
"**/node_modules/**",
|
|
46446
46568
|
"**/dist/**",
|
|
46447
46569
|
"**/build/**",
|
|
46448
46570
|
"**/.git/**",
|
|
46449
46571
|
"**/vendor/**",
|
|
46450
|
-
"**/__pycache__/**"
|
|
46451
|
-
|
|
46452
|
-
|
|
46453
|
-
|
|
46454
|
-
|
|
46455
|
-
|
|
46456
|
-
|
|
46572
|
+
"**/__pycache__/**"
|
|
46573
|
+
];
|
|
46574
|
+
const allFiles2 = await glob3(patterns2, {
|
|
46575
|
+
cwd: absolutePath,
|
|
46576
|
+
ignore: ignorePatterns2,
|
|
46577
|
+
nodir: true,
|
|
46578
|
+
absolute: true
|
|
46579
|
+
});
|
|
46580
|
+
changedFiles.clear();
|
|
46581
|
+
for (const filePath of allFiles2) {
|
|
46582
|
+
changedFiles.add(filePath);
|
|
46583
|
+
}
|
|
46584
|
+
isScanning = false;
|
|
46585
|
+
runScanOnChanges();
|
|
46586
|
+
};
|
|
46587
|
+
const watcherIgnore = [
|
|
46588
|
+
"**/node_modules/**",
|
|
46589
|
+
"**/dist/**",
|
|
46590
|
+
"**/build/**",
|
|
46591
|
+
"**/.git/**",
|
|
46592
|
+
"**/vendor/**",
|
|
46593
|
+
"**/__pycache__/**",
|
|
46594
|
+
"**/venv/**",
|
|
46595
|
+
"**/.venv/**",
|
|
46596
|
+
"**/coverage/**",
|
|
46597
|
+
"**/.next/**",
|
|
46598
|
+
"**/.nuxt/**",
|
|
46599
|
+
"**/.turbo/**",
|
|
46600
|
+
"**/out/**",
|
|
46601
|
+
"**/.cache/**",
|
|
46602
|
+
"**/.vercel/**",
|
|
46603
|
+
"**/.netlify/**",
|
|
46604
|
+
"**/target/**",
|
|
46605
|
+
"**/.gradle/**",
|
|
46606
|
+
"**/.mvn/**",
|
|
46607
|
+
"**/bower_components/**",
|
|
46608
|
+
"**/.yarn/**",
|
|
46609
|
+
"**/package-lock.json",
|
|
46610
|
+
"**/yarn.lock",
|
|
46611
|
+
"**/pnpm-lock.yaml",
|
|
46612
|
+
"**/*.min.js",
|
|
46613
|
+
"**/*.min.css",
|
|
46614
|
+
"**/*.bundle.js"
|
|
46615
|
+
];
|
|
46616
|
+
const watcher = esm_default.watch(absolutePath, {
|
|
46617
|
+
ignored: watcherIgnore,
|
|
46457
46618
|
persistent: true,
|
|
46458
46619
|
ignoreInitial: true
|
|
46459
46620
|
});
|
|
46460
46621
|
watcher.on("change", (filePath) => {
|
|
46461
46622
|
if (!isScannableFile2(filePath)) return;
|
|
46462
|
-
changedFiles.add((0,
|
|
46623
|
+
changedFiles.add((0, import_path5.resolve)(filePath));
|
|
46463
46624
|
if (!options.quiet) {
|
|
46464
|
-
console.log(source_default.dim(`Changed: ${(0,
|
|
46625
|
+
console.log(source_default.dim(`Changed: ${(0, import_path5.relative)(process.cwd(), filePath)}`));
|
|
46465
46626
|
}
|
|
46466
46627
|
runScanOnChanges();
|
|
46467
46628
|
});
|
|
46468
46629
|
watcher.on("add", (filePath) => {
|
|
46469
46630
|
if (!isScannableFile2(filePath)) return;
|
|
46470
|
-
changedFiles.add((0,
|
|
46631
|
+
changedFiles.add((0, import_path5.resolve)(filePath));
|
|
46471
46632
|
if (!options.quiet) {
|
|
46472
|
-
console.log(source_default.dim(`Added: ${(0,
|
|
46633
|
+
console.log(source_default.dim(`Added: ${(0, import_path5.relative)(process.cwd(), filePath)}`));
|
|
46473
46634
|
}
|
|
46474
46635
|
runScanOnChanges();
|
|
46475
46636
|
});
|
|
@@ -46477,13 +46638,47 @@ async function watch2(targetPath, options) {
|
|
|
46477
46638
|
const enhanced = enhanceError(error);
|
|
46478
46639
|
console.error(formatError(enhanced));
|
|
46479
46640
|
});
|
|
46480
|
-
|
|
46641
|
+
const cleanup = () => {
|
|
46481
46642
|
if (!options.quiet) {
|
|
46482
|
-
console.log(source_default.dim("\n\
|
|
46643
|
+
console.log(source_default.dim("\n\n Stopping watch mode..."));
|
|
46644
|
+
if (scanCount > 0) {
|
|
46645
|
+
console.log(source_default.dim(` Completed ${scanCount} scans, found ${totalIssues} total issues.`));
|
|
46646
|
+
}
|
|
46647
|
+
console.log("");
|
|
46648
|
+
}
|
|
46649
|
+
if (process.stdin.isTTY) {
|
|
46650
|
+
process.stdin.setRawMode(false);
|
|
46483
46651
|
}
|
|
46484
46652
|
watcher.close();
|
|
46485
46653
|
process.exit(0);
|
|
46486
|
-
}
|
|
46654
|
+
};
|
|
46655
|
+
if (process.stdin.isTTY && !options.quiet) {
|
|
46656
|
+
readline.emitKeypressEvents(process.stdin);
|
|
46657
|
+
process.stdin.setRawMode(true);
|
|
46658
|
+
process.stdin.on("keypress", (ch, key) => {
|
|
46659
|
+
if (!key) return;
|
|
46660
|
+
if (key.name === "r") {
|
|
46661
|
+
triggerFullRescan();
|
|
46662
|
+
} else if (key.name === "c") {
|
|
46663
|
+
console.clear();
|
|
46664
|
+
showBanner();
|
|
46665
|
+
} else if (key.name === "p") {
|
|
46666
|
+
isPaused = !isPaused;
|
|
46667
|
+
if (isPaused) {
|
|
46668
|
+
console.log(source_default.yellow("\n \u23F8 Watch paused. Press [p] to resume.\n"));
|
|
46669
|
+
} else {
|
|
46670
|
+
console.log(source_default.green("\n \u25B6 Watch resumed.\n"));
|
|
46671
|
+
if (changedFiles.size > 0) {
|
|
46672
|
+
runScanOnChanges();
|
|
46673
|
+
}
|
|
46674
|
+
}
|
|
46675
|
+
} else if (key.name === "q" || key.ctrl && key.name === "c") {
|
|
46676
|
+
cleanup();
|
|
46677
|
+
}
|
|
46678
|
+
});
|
|
46679
|
+
} else {
|
|
46680
|
+
process.on("SIGINT", cleanup);
|
|
46681
|
+
}
|
|
46487
46682
|
if (!options.quiet) {
|
|
46488
46683
|
console.log(source_default.dim("Performing initial scan..."));
|
|
46489
46684
|
}
|
|
@@ -46507,7 +46702,32 @@ async function watch2(targetPath, options) {
|
|
|
46507
46702
|
"**/build/**",
|
|
46508
46703
|
"**/.git/**",
|
|
46509
46704
|
"**/vendor/**",
|
|
46510
|
-
"**/__pycache__/**"
|
|
46705
|
+
"**/__pycache__/**",
|
|
46706
|
+
"**/venv/**",
|
|
46707
|
+
"**/.venv/**",
|
|
46708
|
+
"**/coverage/**",
|
|
46709
|
+
"**/.next/**",
|
|
46710
|
+
"**/.nuxt/**",
|
|
46711
|
+
"**/.turbo/**",
|
|
46712
|
+
"**/out/**",
|
|
46713
|
+
"**/.cache/**",
|
|
46714
|
+
"**/.vercel/**",
|
|
46715
|
+
"**/.netlify/**",
|
|
46716
|
+
"**/target/**",
|
|
46717
|
+
"**/bin/**",
|
|
46718
|
+
"**/obj/**",
|
|
46719
|
+
"**/.gradle/**",
|
|
46720
|
+
"**/.mvn/**",
|
|
46721
|
+
"**/bower_components/**",
|
|
46722
|
+
"**/jspm_packages/**",
|
|
46723
|
+
"**/.yarn/**",
|
|
46724
|
+
"**/.pnp.*",
|
|
46725
|
+
"**/package-lock.json",
|
|
46726
|
+
"**/yarn.lock",
|
|
46727
|
+
"**/pnpm-lock.yaml",
|
|
46728
|
+
"**/*.min.js",
|
|
46729
|
+
"**/*.min.css",
|
|
46730
|
+
"**/*.bundle.js"
|
|
46511
46731
|
];
|
|
46512
46732
|
const allFiles = await glob2(patterns, {
|
|
46513
46733
|
cwd: absolutePath,
|
|
@@ -46527,9 +46747,15 @@ Examples:
|
|
|
46527
46747
|
$ oculum watch . --clear # Clear console between scans
|
|
46528
46748
|
$ oculum watch . --debounce 1000 # Wait 1s after changes
|
|
46529
46749
|
|
|
46750
|
+
Keyboard Controls:
|
|
46751
|
+
[r] Rescan all files
|
|
46752
|
+
[c] Clear console
|
|
46753
|
+
[p] Pause/resume watching
|
|
46754
|
+
[q] Quit watch mode
|
|
46755
|
+
|
|
46530
46756
|
Tips:
|
|
46531
46757
|
\u2022 Watch mode uses 'cheap' depth by default for fast feedback
|
|
46532
|
-
\u2022
|
|
46758
|
+
\u2022 Scans are recorded to history (view with: oculum history)
|
|
46533
46759
|
\u2022 Combine with oculum.config.json for project-specific settings
|
|
46534
46760
|
`).action((path2, cliOptions) => {
|
|
46535
46761
|
const projectConfig = loadProjectConfig(path2, cliOptions.quiet);
|
|
@@ -47152,59 +47378,6 @@ var Y2 = ({ indicator: t = "dots" } = {}) => {
|
|
|
47152
47378
|
// src/commands/ui.ts
|
|
47153
47379
|
var import_fs11 = require("fs");
|
|
47154
47380
|
|
|
47155
|
-
// src/utils/history.ts
|
|
47156
|
-
var import_fs8 = require("fs");
|
|
47157
|
-
var import_path5 = require("path");
|
|
47158
|
-
var import_os3 = require("os");
|
|
47159
|
-
var import_crypto = require("crypto");
|
|
47160
|
-
var CONFIG_DIR2 = (0, import_path5.join)((0, import_os3.homedir)(), ".oculum");
|
|
47161
|
-
var HISTORY_FILE = (0, import_path5.join)(CONFIG_DIR2, "history.json");
|
|
47162
|
-
var MAX_ENTRIES = 25;
|
|
47163
|
-
function ensureConfigDir2() {
|
|
47164
|
-
if (!(0, import_fs8.existsSync)(CONFIG_DIR2)) {
|
|
47165
|
-
(0, import_fs8.mkdirSync)(CONFIG_DIR2, { recursive: true });
|
|
47166
|
-
}
|
|
47167
|
-
}
|
|
47168
|
-
function readHistoryFile() {
|
|
47169
|
-
try {
|
|
47170
|
-
if (!(0, import_fs8.existsSync)(HISTORY_FILE)) return [];
|
|
47171
|
-
const content = (0, import_fs8.readFileSync)(HISTORY_FILE, "utf-8");
|
|
47172
|
-
const parsed = JSON.parse(content);
|
|
47173
|
-
if (!Array.isArray(parsed)) return [];
|
|
47174
|
-
return parsed;
|
|
47175
|
-
} catch {
|
|
47176
|
-
return [];
|
|
47177
|
-
}
|
|
47178
|
-
}
|
|
47179
|
-
function writeHistoryFile(entries) {
|
|
47180
|
-
ensureConfigDir2();
|
|
47181
|
-
(0, import_fs8.writeFileSync)(HISTORY_FILE, JSON.stringify(entries, null, 2));
|
|
47182
|
-
}
|
|
47183
|
-
function listScanHistory() {
|
|
47184
|
-
return readHistoryFile();
|
|
47185
|
-
}
|
|
47186
|
-
function addScanHistoryEntry(input) {
|
|
47187
|
-
const entry = {
|
|
47188
|
-
id: input.id ?? (0, import_crypto.randomUUID)(),
|
|
47189
|
-
createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
47190
|
-
targetPath: input.targetPath,
|
|
47191
|
-
options: input.options,
|
|
47192
|
-
result: input.result
|
|
47193
|
-
};
|
|
47194
|
-
const current = readHistoryFile();
|
|
47195
|
-
const updated = [entry, ...current].slice(0, MAX_ENTRIES);
|
|
47196
|
-
writeHistoryFile(updated);
|
|
47197
|
-
return entry;
|
|
47198
|
-
}
|
|
47199
|
-
function deleteScanHistoryEntry(id) {
|
|
47200
|
-
const current = readHistoryFile();
|
|
47201
|
-
const updated = current.filter((e2) => e2.id !== id);
|
|
47202
|
-
writeHistoryFile(updated);
|
|
47203
|
-
}
|
|
47204
|
-
function clearScanHistory() {
|
|
47205
|
-
writeHistoryFile([]);
|
|
47206
|
-
}
|
|
47207
|
-
|
|
47208
47381
|
// src/utils/first-run.ts
|
|
47209
47382
|
var import_fs9 = require("fs");
|
|
47210
47383
|
var import_path6 = require("path");
|
|
@@ -47280,7 +47453,7 @@ function showLogo() {
|
|
|
47280
47453
|
async function showWelcomeScreen() {
|
|
47281
47454
|
console.clear();
|
|
47282
47455
|
showLogo();
|
|
47283
|
-
console.log(source_default.bold.white(" AI-Native Security Scanner for Modern Codebases\n"));
|
|
47456
|
+
console.log(source_default.bold.white(" AI-Native api key Security Scanner for Modern Codebases\n"));
|
|
47284
47457
|
console.log(source_default.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
|
|
47285
47458
|
console.log(source_default.white(" Oculum detects security vulnerabilities in AI-generated"));
|
|
47286
47459
|
console.log(source_default.white(" code and LLM-powered applications, including:\n"));
|
|
@@ -48949,6 +49122,200 @@ function showTroubleshooting() {
|
|
|
48949
49122
|
console.log(source_default.white(" - Get support: ") + source_default.cyan("support@oculum.dev\n"));
|
|
48950
49123
|
}
|
|
48951
49124
|
|
|
49125
|
+
// src/commands/history.ts
|
|
49126
|
+
function formatDate3(isoString) {
|
|
49127
|
+
const date = new Date(isoString);
|
|
49128
|
+
return date.toLocaleString("en-US", {
|
|
49129
|
+
month: "short",
|
|
49130
|
+
day: "numeric",
|
|
49131
|
+
hour: "2-digit",
|
|
49132
|
+
minute: "2-digit"
|
|
49133
|
+
});
|
|
49134
|
+
}
|
|
49135
|
+
function truncate(str, maxLen) {
|
|
49136
|
+
if (str.length <= maxLen) return str;
|
|
49137
|
+
return str.slice(0, maxLen - 2) + "..";
|
|
49138
|
+
}
|
|
49139
|
+
function getStatusBadge(entry) {
|
|
49140
|
+
const { severityCounts } = entry.result;
|
|
49141
|
+
if (severityCounts.critical > 0 || severityCounts.high > 0) {
|
|
49142
|
+
return source_default.red("FAIL");
|
|
49143
|
+
}
|
|
49144
|
+
return source_default.green("PASS");
|
|
49145
|
+
}
|
|
49146
|
+
function getTotalIssues(entry) {
|
|
49147
|
+
const { severityCounts } = entry.result;
|
|
49148
|
+
return severityCounts.critical + severityCounts.high + severityCounts.medium + severityCounts.low + severityCounts.info;
|
|
49149
|
+
}
|
|
49150
|
+
function showHistoryList() {
|
|
49151
|
+
const entries = listScanHistory();
|
|
49152
|
+
if (entries.length === 0) {
|
|
49153
|
+
console.log(source_default.dim("\nNo scan history found."));
|
|
49154
|
+
console.log(source_default.dim("Run a scan with: oculum scan .\n"));
|
|
49155
|
+
return;
|
|
49156
|
+
}
|
|
49157
|
+
console.log(source_default.bold("\nRecent Scans"));
|
|
49158
|
+
console.log(source_default.dim("\u2500".repeat(70)));
|
|
49159
|
+
console.log();
|
|
49160
|
+
console.log(
|
|
49161
|
+
source_default.dim(" ID ") + source_default.dim("Date ") + source_default.dim("Path ") + source_default.dim("Issues ") + source_default.dim("Status")
|
|
49162
|
+
);
|
|
49163
|
+
console.log(source_default.dim(" " + "\u2500".repeat(66)));
|
|
49164
|
+
for (const entry of entries) {
|
|
49165
|
+
const id = entry.id.slice(0, 8);
|
|
49166
|
+
const date = formatDate3(entry.createdAt);
|
|
49167
|
+
const path2 = truncate(entry.targetPath, 22);
|
|
49168
|
+
const issues = getTotalIssues(entry);
|
|
49169
|
+
const status2 = getStatusBadge(entry);
|
|
49170
|
+
console.log(
|
|
49171
|
+
` ${source_default.cyan(id)} ${source_default.white(date.padEnd(16))} ${source_default.dim(path2.padEnd(22))} ${String(issues).padEnd(6)} ` + status2
|
|
49172
|
+
);
|
|
49173
|
+
}
|
|
49174
|
+
console.log();
|
|
49175
|
+
console.log(source_default.dim("\u2500".repeat(70)));
|
|
49176
|
+
console.log(source_default.dim("Run 'oculum history show <id>' for details"));
|
|
49177
|
+
console.log(source_default.dim("Run 'oculum history clear' to clear all history\n"));
|
|
49178
|
+
}
|
|
49179
|
+
function showScanDetails(id) {
|
|
49180
|
+
const entry = getScanHistoryEntry(id);
|
|
49181
|
+
if (!entry) {
|
|
49182
|
+
const entries = listScanHistory();
|
|
49183
|
+
const partial = entries.find((e2) => e2.id.startsWith(id));
|
|
49184
|
+
if (partial) {
|
|
49185
|
+
showScanDetailsForEntry(partial);
|
|
49186
|
+
return;
|
|
49187
|
+
}
|
|
49188
|
+
console.log(source_default.red(`
|
|
49189
|
+
Scan not found: ${id}`));
|
|
49190
|
+
console.log(source_default.dim("Run 'oculum history' to see available scans\n"));
|
|
49191
|
+
return;
|
|
49192
|
+
}
|
|
49193
|
+
showScanDetailsForEntry(entry);
|
|
49194
|
+
}
|
|
49195
|
+
function showScanDetailsForEntry(entry) {
|
|
49196
|
+
const { result, options, targetPath, createdAt, id } = entry;
|
|
49197
|
+
const { severityCounts, vulnerabilities, filesScanned, scanDuration } = result;
|
|
49198
|
+
console.log(source_default.bold("\nScan Details"));
|
|
49199
|
+
console.log(source_default.dim("\u2500".repeat(50)));
|
|
49200
|
+
console.log();
|
|
49201
|
+
console.log(source_default.white(" ID: ") + source_default.cyan(id.slice(0, 8)));
|
|
49202
|
+
console.log(source_default.white(" Date: ") + formatDate3(createdAt));
|
|
49203
|
+
console.log(source_default.white(" Path: ") + targetPath);
|
|
49204
|
+
console.log(source_default.white(" Depth: ") + options.depth);
|
|
49205
|
+
console.log(source_default.white(" Files: ") + filesScanned);
|
|
49206
|
+
console.log(source_default.white(" Duration: ") + `${(scanDuration / 1e3).toFixed(1)}s`);
|
|
49207
|
+
console.log();
|
|
49208
|
+
console.log(source_default.bold(" Findings"));
|
|
49209
|
+
console.log(source_default.dim(" " + "\u2500".repeat(30)));
|
|
49210
|
+
const severities = [
|
|
49211
|
+
{ name: "Critical", count: severityCounts.critical, color: source_default.red },
|
|
49212
|
+
{ name: "High", count: severityCounts.high, color: source_default.yellow },
|
|
49213
|
+
{ name: "Medium", count: severityCounts.medium, color: source_default.magenta },
|
|
49214
|
+
{ name: "Low", count: severityCounts.low, color: source_default.blue },
|
|
49215
|
+
{ name: "Info", count: severityCounts.info, color: source_default.gray }
|
|
49216
|
+
];
|
|
49217
|
+
for (const { name, count, color } of severities) {
|
|
49218
|
+
if (count > 0) {
|
|
49219
|
+
console.log(` ${color(name.padEnd(10))} ${count}`);
|
|
49220
|
+
}
|
|
49221
|
+
}
|
|
49222
|
+
const total = getTotalIssues(entry);
|
|
49223
|
+
if (total === 0) {
|
|
49224
|
+
console.log(source_default.green(" No issues found"));
|
|
49225
|
+
}
|
|
49226
|
+
if (vulnerabilities.length > 0) {
|
|
49227
|
+
console.log();
|
|
49228
|
+
console.log(source_default.bold(" Top Findings"));
|
|
49229
|
+
console.log(source_default.dim(" " + "\u2500".repeat(30)));
|
|
49230
|
+
const topFindings = vulnerabilities.slice(0, 5);
|
|
49231
|
+
for (const finding of topFindings) {
|
|
49232
|
+
const severityColor = finding.severity === "critical" ? source_default.red : finding.severity === "high" ? source_default.yellow : finding.severity === "medium" ? source_default.magenta : finding.severity === "low" ? source_default.blue : source_default.gray;
|
|
49233
|
+
console.log(
|
|
49234
|
+
` ${severityColor(finding.severity.toUpperCase().padEnd(8))} ` + source_default.white(truncate(finding.title, 40))
|
|
49235
|
+
);
|
|
49236
|
+
console.log(source_default.dim(` ${finding.filePath}:${finding.lineNumber}`));
|
|
49237
|
+
}
|
|
49238
|
+
if (vulnerabilities.length > 5) {
|
|
49239
|
+
console.log(source_default.dim(` ... and ${vulnerabilities.length - 5} more`));
|
|
49240
|
+
}
|
|
49241
|
+
}
|
|
49242
|
+
console.log();
|
|
49243
|
+
console.log(source_default.dim("\u2500".repeat(50)));
|
|
49244
|
+
console.log(source_default.dim(`Run 'oculum history delete ${id.slice(0, 8)}' to remove this scan
|
|
49245
|
+
`));
|
|
49246
|
+
}
|
|
49247
|
+
function clearHistory() {
|
|
49248
|
+
const entries = listScanHistory();
|
|
49249
|
+
if (entries.length === 0) {
|
|
49250
|
+
console.log(source_default.dim("\nNo history to clear.\n"));
|
|
49251
|
+
return;
|
|
49252
|
+
}
|
|
49253
|
+
clearScanHistory();
|
|
49254
|
+
console.log(source_default.green(`
|
|
49255
|
+
Cleared ${entries.length} scan entries.
|
|
49256
|
+
`));
|
|
49257
|
+
}
|
|
49258
|
+
function deleteEntry(id) {
|
|
49259
|
+
const entry = getScanHistoryEntry(id);
|
|
49260
|
+
if (!entry) {
|
|
49261
|
+
const entries = listScanHistory();
|
|
49262
|
+
const partial = entries.find((e2) => e2.id.startsWith(id));
|
|
49263
|
+
if (partial) {
|
|
49264
|
+
deleteScanHistoryEntry(partial.id);
|
|
49265
|
+
console.log(source_default.green(`
|
|
49266
|
+
Deleted scan ${partial.id.slice(0, 8)}
|
|
49267
|
+
`));
|
|
49268
|
+
return;
|
|
49269
|
+
}
|
|
49270
|
+
console.log(source_default.red(`
|
|
49271
|
+
Scan not found: ${id}
|
|
49272
|
+
`));
|
|
49273
|
+
return;
|
|
49274
|
+
}
|
|
49275
|
+
deleteScanHistoryEntry(entry.id);
|
|
49276
|
+
console.log(source_default.green(`
|
|
49277
|
+
Deleted scan ${entry.id.slice(0, 8)}
|
|
49278
|
+
`));
|
|
49279
|
+
}
|
|
49280
|
+
var historyCommand = new Command("history").description("View and manage scan history").argument("[subcommand]", "show, clear, or delete").argument("[id]", "scan ID for show/delete").action((subcommand, id) => {
|
|
49281
|
+
if (!subcommand) {
|
|
49282
|
+
showHistoryList();
|
|
49283
|
+
return;
|
|
49284
|
+
}
|
|
49285
|
+
switch (subcommand) {
|
|
49286
|
+
case "show":
|
|
49287
|
+
if (!id) {
|
|
49288
|
+
console.log(source_default.red("\nPlease provide a scan ID"));
|
|
49289
|
+
console.log(source_default.dim("Usage: oculum history show <id>\n"));
|
|
49290
|
+
return;
|
|
49291
|
+
}
|
|
49292
|
+
showScanDetails(id);
|
|
49293
|
+
break;
|
|
49294
|
+
case "clear":
|
|
49295
|
+
clearHistory();
|
|
49296
|
+
break;
|
|
49297
|
+
case "delete":
|
|
49298
|
+
if (!id) {
|
|
49299
|
+
console.log(source_default.red("\nPlease provide a scan ID"));
|
|
49300
|
+
console.log(source_default.dim("Usage: oculum history delete <id>\n"));
|
|
49301
|
+
return;
|
|
49302
|
+
}
|
|
49303
|
+
deleteEntry(id);
|
|
49304
|
+
break;
|
|
49305
|
+
default:
|
|
49306
|
+
showScanDetails(subcommand);
|
|
49307
|
+
}
|
|
49308
|
+
}).addHelpText("after", `
|
|
49309
|
+
Examples:
|
|
49310
|
+
$ oculum history List recent scans
|
|
49311
|
+
$ oculum history show abc123 View scan details
|
|
49312
|
+
$ oculum history abc123 Quick access (same as show)
|
|
49313
|
+
$ oculum history delete abc Delete a scan by ID
|
|
49314
|
+
$ oculum history clear Clear all history
|
|
49315
|
+
|
|
49316
|
+
Note: History stores up to 25 recent scans locally.
|
|
49317
|
+
`);
|
|
49318
|
+
|
|
48952
49319
|
// src/utils/ci-detect.ts
|
|
48953
49320
|
var CI_ENV_VARS = [
|
|
48954
49321
|
"CI",
|
|
@@ -49019,6 +49386,7 @@ Quick Start:
|
|
|
49019
49386
|
Common Commands:
|
|
49020
49387
|
scan [path] Scan files for security vulnerabilities
|
|
49021
49388
|
watch [path] Watch files and scan on changes
|
|
49389
|
+
history View and manage scan history
|
|
49022
49390
|
ui Interactive terminal UI
|
|
49023
49391
|
login Authenticate with Oculum
|
|
49024
49392
|
status Check authentication status
|
|
@@ -49041,6 +49409,7 @@ program2.addCommand(usageCommand);
|
|
|
49041
49409
|
program2.addCommand(watchCommand);
|
|
49042
49410
|
program2.addCommand(uiCommand);
|
|
49043
49411
|
program2.addCommand(helpCommand);
|
|
49412
|
+
program2.addCommand(historyCommand);
|
|
49044
49413
|
async function main2() {
|
|
49045
49414
|
const interactive = isInteractiveTerminal();
|
|
49046
49415
|
if (interactive && shouldRunUI()) {
|
package/package.json
CHANGED