@oculum/cli 1.0.5 → 1.0.8
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 +151 -119
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -43131,103 +43131,6 @@ async function callBackendAPI(files, depth, apiKey, repoInfo) {
|
|
|
43131
43131
|
}
|
|
43132
43132
|
return data.result;
|
|
43133
43133
|
}
|
|
43134
|
-
async function callBackendAPIStream(files, depth, apiKey, repoInfo, onProgress) {
|
|
43135
|
-
const baseUrl = getApiBaseUrl();
|
|
43136
|
-
const streamUrl = `${baseUrl}/v1/scan/stream`;
|
|
43137
|
-
try {
|
|
43138
|
-
const response = await fetch(streamUrl, {
|
|
43139
|
-
method: "POST",
|
|
43140
|
-
headers: {
|
|
43141
|
-
"Content-Type": "application/json",
|
|
43142
|
-
"Authorization": `Bearer ${apiKey}`,
|
|
43143
|
-
"X-Oculum-Client": "cli",
|
|
43144
|
-
"X-Oculum-Version": "1.0.0"
|
|
43145
|
-
},
|
|
43146
|
-
body: JSON.stringify({
|
|
43147
|
-
files,
|
|
43148
|
-
depth,
|
|
43149
|
-
repoName: repoInfo.name,
|
|
43150
|
-
repoUrl: repoInfo.url,
|
|
43151
|
-
branch: repoInfo.branch
|
|
43152
|
-
})
|
|
43153
|
-
});
|
|
43154
|
-
if (!response.ok) {
|
|
43155
|
-
console.warn(`[API] SSE endpoint failed with ${response.status}, falling back to regular POST`);
|
|
43156
|
-
return await callBackendAPI(files, depth, apiKey, repoInfo);
|
|
43157
|
-
}
|
|
43158
|
-
if (!response.body) {
|
|
43159
|
-
throw new APIError("Response body is empty", 500);
|
|
43160
|
-
}
|
|
43161
|
-
const reader = response.body.getReader();
|
|
43162
|
-
const decoder = new TextDecoder();
|
|
43163
|
-
let buffer = "";
|
|
43164
|
-
let result = null;
|
|
43165
|
-
while (true) {
|
|
43166
|
-
const { done, value } = await reader.read();
|
|
43167
|
-
if (done) break;
|
|
43168
|
-
buffer += decoder.decode(value, { stream: true });
|
|
43169
|
-
const lines = buffer.split("\n");
|
|
43170
|
-
buffer = lines.pop() || "";
|
|
43171
|
-
let currentEvent = null;
|
|
43172
|
-
let currentData = null;
|
|
43173
|
-
for (const line of lines) {
|
|
43174
|
-
if (line.startsWith("event: ")) {
|
|
43175
|
-
currentEvent = line.slice(7).trim();
|
|
43176
|
-
} else if (line.startsWith("data: ")) {
|
|
43177
|
-
currentData = line.slice(6).trim();
|
|
43178
|
-
} else if (line === "" && currentEvent && currentData) {
|
|
43179
|
-
try {
|
|
43180
|
-
const data = JSON.parse(currentData);
|
|
43181
|
-
switch (currentEvent) {
|
|
43182
|
-
case "progress":
|
|
43183
|
-
if (onProgress) {
|
|
43184
|
-
onProgress({
|
|
43185
|
-
status: data.status,
|
|
43186
|
-
message: data.message || "",
|
|
43187
|
-
filesProcessed: data.filesProcessed || 0,
|
|
43188
|
-
totalFiles: data.totalFiles || 0,
|
|
43189
|
-
vulnerabilitiesFound: data.vulnerabilitiesFound || 0
|
|
43190
|
-
});
|
|
43191
|
-
}
|
|
43192
|
-
break;
|
|
43193
|
-
case "complete":
|
|
43194
|
-
if (!data.result) {
|
|
43195
|
-
console.error("[API] Complete event received but no result in data:", data);
|
|
43196
|
-
throw new APIError("Backend scan completed but returned no result data", 500);
|
|
43197
|
-
}
|
|
43198
|
-
result = data.result;
|
|
43199
|
-
break;
|
|
43200
|
-
case "error":
|
|
43201
|
-
console.error("[API] Backend sent error event:", data);
|
|
43202
|
-
throw new APIError(
|
|
43203
|
-
data.message || data.error || "Scan failed",
|
|
43204
|
-
data.status || 500,
|
|
43205
|
-
data.error
|
|
43206
|
-
);
|
|
43207
|
-
}
|
|
43208
|
-
} catch (parseError) {
|
|
43209
|
-
if (parseError instanceof APIError) throw parseError;
|
|
43210
|
-
console.error("[API] Failed to parse SSE event:", parseError);
|
|
43211
|
-
}
|
|
43212
|
-
currentEvent = null;
|
|
43213
|
-
currentData = null;
|
|
43214
|
-
}
|
|
43215
|
-
}
|
|
43216
|
-
}
|
|
43217
|
-
if (!result) {
|
|
43218
|
-
console.error("[API] Stream ended without receiving complete event");
|
|
43219
|
-
throw new APIError("No result received from stream", 500);
|
|
43220
|
-
}
|
|
43221
|
-
console.log("[API] SSE stream completed successfully");
|
|
43222
|
-
return result;
|
|
43223
|
-
} catch (error) {
|
|
43224
|
-
if (error instanceof APIError) {
|
|
43225
|
-
throw error;
|
|
43226
|
-
}
|
|
43227
|
-
console.warn("[API] SSE streaming failed, falling back to regular POST:", error);
|
|
43228
|
-
return await callBackendAPI(files, depth, apiKey, repoInfo);
|
|
43229
|
-
}
|
|
43230
|
-
}
|
|
43231
43134
|
async function verifyApiKey(apiKey) {
|
|
43232
43135
|
const baseUrl = getApiBaseUrl();
|
|
43233
43136
|
const url = `${baseUrl}/v1/verify-key`;
|
|
@@ -44045,8 +43948,8 @@ async function runScanOnce(targetPath, options) {
|
|
|
44045
43948
|
spinner.start("Starting scan...");
|
|
44046
43949
|
const hasLocalAI = !!process.env.ANTHROPIC_API_KEY;
|
|
44047
43950
|
if (options.depth !== "cheap" && isAuthenticated() && !hasLocalAI) {
|
|
44048
|
-
spinner.text = `Backend ${options.depth} scan
|
|
44049
|
-
result = await
|
|
43951
|
+
spinner.text = `Backend ${options.depth} scan analyzing ${files.length} files...`;
|
|
43952
|
+
result = await callBackendAPI(
|
|
44050
43953
|
files,
|
|
44051
43954
|
options.depth,
|
|
44052
43955
|
config.apiKey,
|
|
@@ -44054,9 +43957,7 @@ async function runScanOnce(targetPath, options) {
|
|
|
44054
43957
|
name: (0, import_path3.basename)((0, import_path3.resolve)(targetPath)),
|
|
44055
43958
|
url: "",
|
|
44056
43959
|
branch: "local"
|
|
44057
|
-
}
|
|
44058
|
-
onProgress
|
|
44059
|
-
// Stream real-time progress from backend
|
|
43960
|
+
}
|
|
44060
43961
|
);
|
|
44061
43962
|
spinner.succeed(`Backend ${options.depth} scan complete`);
|
|
44062
43963
|
} else {
|
|
@@ -47005,8 +46906,7 @@ function recordScan() {
|
|
|
47005
46906
|
}
|
|
47006
46907
|
|
|
47007
46908
|
// src/ui/onboarding.ts
|
|
47008
|
-
|
|
47009
|
-
console.clear();
|
|
46909
|
+
function showLogo() {
|
|
47010
46910
|
console.log(source_default.cyan(`
|
|
47011
46911
|
\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557
|
|
47012
46912
|
\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551
|
|
@@ -47015,6 +46915,10 @@ async function showWelcomeScreen() {
|
|
|
47015
46915
|
\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551
|
|
47016
46916
|
\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D
|
|
47017
46917
|
`));
|
|
46918
|
+
}
|
|
46919
|
+
async function showWelcomeScreen() {
|
|
46920
|
+
console.clear();
|
|
46921
|
+
showLogo();
|
|
47018
46922
|
console.log(source_default.bold.white(" AI-Native Security Scanner for Modern Codebases\n"));
|
|
47019
46923
|
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"));
|
|
47020
46924
|
console.log(source_default.white(" Oculum detects security vulnerabilities in AI-generated"));
|
|
@@ -48071,11 +47975,136 @@ async function runHistoryFlow() {
|
|
|
48071
47975
|
}
|
|
48072
47976
|
}
|
|
48073
47977
|
}
|
|
47978
|
+
function formatNumber(num) {
|
|
47979
|
+
if (num === -1) return "unlimited";
|
|
47980
|
+
return num.toLocaleString();
|
|
47981
|
+
}
|
|
47982
|
+
function createProgressBar(percentage, width = 20) {
|
|
47983
|
+
const filled = Math.round(percentage / 100 * width);
|
|
47984
|
+
const empty = width - filled;
|
|
47985
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
47986
|
+
if (percentage >= 90) return source_default.red(bar);
|
|
47987
|
+
if (percentage >= 70) return source_default.yellow(bar);
|
|
47988
|
+
return source_default.green(bar);
|
|
47989
|
+
}
|
|
47990
|
+
function formatDate(dateStr) {
|
|
47991
|
+
const date = new Date(dateStr);
|
|
47992
|
+
return date.toLocaleDateString("en-US", {
|
|
47993
|
+
month: "short",
|
|
47994
|
+
day: "numeric",
|
|
47995
|
+
year: "numeric"
|
|
47996
|
+
});
|
|
47997
|
+
}
|
|
47998
|
+
function getTimeAgo(date) {
|
|
47999
|
+
const now = /* @__PURE__ */ new Date();
|
|
48000
|
+
const diffMs = now.getTime() - date.getTime();
|
|
48001
|
+
const diffMins = Math.floor(diffMs / 6e4);
|
|
48002
|
+
const diffHours = Math.floor(diffMs / 36e5);
|
|
48003
|
+
const diffDays = Math.floor(diffMs / 864e5);
|
|
48004
|
+
if (diffMins < 1) return "just now";
|
|
48005
|
+
if (diffMins < 60) return `${diffMins}m ago`;
|
|
48006
|
+
if (diffHours < 24) return `${diffHours}h ago`;
|
|
48007
|
+
if (diffDays < 7) return `${diffDays}d ago`;
|
|
48008
|
+
return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
48009
|
+
}
|
|
48010
|
+
async function runUsageFlow() {
|
|
48011
|
+
const config = getConfig();
|
|
48012
|
+
if (!isAuthenticated()) {
|
|
48013
|
+
console.log("");
|
|
48014
|
+
M2.warn("Not logged in");
|
|
48015
|
+
console.log("");
|
|
48016
|
+
console.log(source_default.dim(" Login to view your usage and quota:"));
|
|
48017
|
+
console.log(source_default.cyan(" oculum login"));
|
|
48018
|
+
console.log("");
|
|
48019
|
+
await ve({
|
|
48020
|
+
message: "Press Enter to continue",
|
|
48021
|
+
options: [{ value: "back", label: "\u2190 Back to menu" }]
|
|
48022
|
+
});
|
|
48023
|
+
return;
|
|
48024
|
+
}
|
|
48025
|
+
const spinner = Y2();
|
|
48026
|
+
spinner.start("Fetching usage data...");
|
|
48027
|
+
try {
|
|
48028
|
+
const result = await getUsage(config.apiKey);
|
|
48029
|
+
if (!result.success || !result.usage || !result.plan) {
|
|
48030
|
+
spinner.stop("Failed to fetch usage data");
|
|
48031
|
+
console.log("");
|
|
48032
|
+
M2.error(result.error || "Unknown error");
|
|
48033
|
+
console.log("");
|
|
48034
|
+
await ve({
|
|
48035
|
+
message: "Press Enter to continue",
|
|
48036
|
+
options: [{ value: "back", label: "\u2190 Back to menu" }]
|
|
48037
|
+
});
|
|
48038
|
+
return;
|
|
48039
|
+
}
|
|
48040
|
+
spinner.stop("Usage data loaded");
|
|
48041
|
+
const { plan, usage: usageData } = result;
|
|
48042
|
+
console.clear();
|
|
48043
|
+
console.log("");
|
|
48044
|
+
console.log(source_default.bold(" \u{1F4CA} Oculum Usage"));
|
|
48045
|
+
console.log(source_default.dim(" " + "\u2500".repeat(38)));
|
|
48046
|
+
console.log("");
|
|
48047
|
+
const planBadge = plan.name === "pro" ? source_default.bgBlue.white(" PRO ") : plan.name === "enterprise" || plan.name === "max" ? source_default.bgMagenta.white(" MAX ") : plan.name === "starter" ? source_default.bgCyan.white(" STARTER ") : source_default.bgGray.white(" FREE ");
|
|
48048
|
+
console.log(source_default.dim(" Plan: ") + planBadge + source_default.white(` ${plan.displayName}`));
|
|
48049
|
+
console.log(source_default.dim(" Month: ") + source_default.white(usageData.month));
|
|
48050
|
+
console.log("");
|
|
48051
|
+
console.log(source_default.bold(" Credits Usage"));
|
|
48052
|
+
console.log("");
|
|
48053
|
+
const creditsDisplay = usageData.creditsLimit === -1 ? `${formatNumber(usageData.creditsUsed)} / unlimited` : `${formatNumber(usageData.creditsUsed)} / ${formatNumber(usageData.creditsLimit)}`;
|
|
48054
|
+
console.log(source_default.dim(" Used: ") + source_default.white(creditsDisplay));
|
|
48055
|
+
if (usageData.creditsLimit !== -1) {
|
|
48056
|
+
console.log(source_default.dim(" Remaining: ") + source_default.white(formatNumber(usageData.creditsRemaining)));
|
|
48057
|
+
console.log("");
|
|
48058
|
+
console.log(source_default.dim(" ") + createProgressBar(usageData.usagePercentage) + source_default.dim(` ${usageData.usagePercentage.toFixed(1)}%`));
|
|
48059
|
+
}
|
|
48060
|
+
console.log("");
|
|
48061
|
+
console.log(source_default.bold(" This Month"));
|
|
48062
|
+
console.log("");
|
|
48063
|
+
console.log(source_default.dim(" Scans: ") + source_default.white(formatNumber(usageData.totalScans)));
|
|
48064
|
+
console.log(source_default.dim(" Files: ") + source_default.white(formatNumber(usageData.totalFiles)));
|
|
48065
|
+
console.log("");
|
|
48066
|
+
console.log(source_default.dim(" Resets on: ") + source_default.white(formatDate(usageData.resetDate)));
|
|
48067
|
+
console.log("");
|
|
48068
|
+
if (result.recentScans && result.recentScans.length > 0) {
|
|
48069
|
+
console.log(source_default.bold(" Recent Scans"));
|
|
48070
|
+
console.log("");
|
|
48071
|
+
const recentToShow = result.recentScans.slice(0, 5);
|
|
48072
|
+
for (const scan of recentToShow) {
|
|
48073
|
+
const date = new Date(scan.createdAt);
|
|
48074
|
+
const timeAgo = getTimeAgo(date);
|
|
48075
|
+
console.log(source_default.dim(" \u2022 ") + source_default.white(scan.repoName || "unknown") + source_default.dim(` (${timeAgo})`));
|
|
48076
|
+
console.log(source_default.dim(" ") + source_default.dim(`${scan.filesScanned} files, ${scan.findingsCount} findings, ${scan.creditsUsed} credits`));
|
|
48077
|
+
}
|
|
48078
|
+
console.log("");
|
|
48079
|
+
}
|
|
48080
|
+
if (plan.name === "free" || plan.name === "starter") {
|
|
48081
|
+
console.log(source_default.dim(" " + "\u2500".repeat(38)));
|
|
48082
|
+
console.log("");
|
|
48083
|
+
console.log(source_default.dim(" Need more credits? Upgrade your plan"));
|
|
48084
|
+
console.log("");
|
|
48085
|
+
}
|
|
48086
|
+
await ve({
|
|
48087
|
+
message: "Press Enter to continue",
|
|
48088
|
+
options: [{ value: "back", label: "\u2190 Back to menu" }]
|
|
48089
|
+
});
|
|
48090
|
+
} catch (error) {
|
|
48091
|
+
spinner.stop("Failed to fetch usage data");
|
|
48092
|
+
console.log("");
|
|
48093
|
+
M2.error(String(error));
|
|
48094
|
+
console.log("");
|
|
48095
|
+
await ve({
|
|
48096
|
+
message: "Press Enter to continue",
|
|
48097
|
+
options: [{ value: "back", label: "\u2190 Back to menu" }]
|
|
48098
|
+
});
|
|
48099
|
+
}
|
|
48100
|
+
}
|
|
48074
48101
|
async function runUI() {
|
|
48102
|
+
console.clear();
|
|
48103
|
+
showLogo();
|
|
48104
|
+
console.log();
|
|
48075
48105
|
const onboardingResult = await handleOnboarding();
|
|
48076
48106
|
if (onboardingResult.quickStartResult) {
|
|
48077
48107
|
const { path: path2, depth } = onboardingResult.quickStartResult;
|
|
48078
|
-
Ie(source_default.bold("Oculum"));
|
|
48079
48108
|
try {
|
|
48080
48109
|
const { output, exitCode, result } = await runScanOnce(path2, {
|
|
48081
48110
|
depth,
|
|
@@ -48103,8 +48132,6 @@ async function runUI() {
|
|
|
48103
48132
|
} catch (err) {
|
|
48104
48133
|
M2.error(`Scan failed: ${String(err)}`);
|
|
48105
48134
|
}
|
|
48106
|
-
} else {
|
|
48107
|
-
Ie(source_default.bold("Oculum"));
|
|
48108
48135
|
}
|
|
48109
48136
|
let lastScanEntry;
|
|
48110
48137
|
const userState = getUserState();
|
|
@@ -48120,6 +48147,7 @@ async function runUI() {
|
|
|
48120
48147
|
{ value: "scan", label: "\u{1F50D} Custom Scan", hint: "Configure scan options" },
|
|
48121
48148
|
{ value: "history", label: "\u{1F4DC} Scan History", hint: `${listScanHistory().length} past scans` },
|
|
48122
48149
|
{ value: "auth", label: "\u{1F510} Auth", hint: isAuthenticated() ? `Logged in (${getConfig().tier || "free"})` : "Not logged in" },
|
|
48150
|
+
{ value: "usage", label: "\u{1F4CA} Usage", hint: isAuthenticated() ? "View credits and quota" : "Requires login" },
|
|
48123
48151
|
{ value: "help", label: "\u2753 Help", hint: "Commands and tips" },
|
|
48124
48152
|
{ value: "exit", label: "\u{1F44B} Exit" }
|
|
48125
48153
|
];
|
|
@@ -48201,6 +48229,10 @@ async function runUI() {
|
|
|
48201
48229
|
await runHistoryFlow();
|
|
48202
48230
|
continue;
|
|
48203
48231
|
}
|
|
48232
|
+
if (action === "usage") {
|
|
48233
|
+
await runUsageFlow();
|
|
48234
|
+
continue;
|
|
48235
|
+
}
|
|
48204
48236
|
if (action === "help") {
|
|
48205
48237
|
await showHelpScreen();
|
|
48206
48238
|
continue;
|
|
@@ -48253,11 +48285,11 @@ var uiCommand = new Command("ui").description("Interactive terminal UI").action(
|
|
|
48253
48285
|
});
|
|
48254
48286
|
|
|
48255
48287
|
// src/commands/usage.ts
|
|
48256
|
-
function
|
|
48288
|
+
function formatNumber2(num) {
|
|
48257
48289
|
if (num === -1) return "unlimited";
|
|
48258
48290
|
return num.toLocaleString();
|
|
48259
48291
|
}
|
|
48260
|
-
function
|
|
48292
|
+
function createProgressBar2(percentage, width = 20) {
|
|
48261
48293
|
const filled = Math.round(percentage / 100 * width);
|
|
48262
48294
|
const empty = width - filled;
|
|
48263
48295
|
const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
@@ -48265,7 +48297,7 @@ function createProgressBar(percentage, width = 20) {
|
|
|
48265
48297
|
if (percentage >= 70) return source_default.yellow(bar);
|
|
48266
48298
|
return source_default.green(bar);
|
|
48267
48299
|
}
|
|
48268
|
-
function
|
|
48300
|
+
function formatDate2(dateStr) {
|
|
48269
48301
|
const date = new Date(dateStr);
|
|
48270
48302
|
return date.toLocaleDateString("en-US", {
|
|
48271
48303
|
month: "short",
|
|
@@ -48310,20 +48342,20 @@ async function usage(options) {
|
|
|
48310
48342
|
console.log("");
|
|
48311
48343
|
console.log(source_default.bold(" Credits Usage"));
|
|
48312
48344
|
console.log("");
|
|
48313
|
-
const creditsDisplay = usageData.creditsLimit === -1 ? `${
|
|
48345
|
+
const creditsDisplay = usageData.creditsLimit === -1 ? `${formatNumber2(usageData.creditsUsed)} / unlimited` : `${formatNumber2(usageData.creditsUsed)} / ${formatNumber2(usageData.creditsLimit)}`;
|
|
48314
48346
|
console.log(source_default.dim(" Used: ") + source_default.white(creditsDisplay));
|
|
48315
48347
|
if (usageData.creditsLimit !== -1) {
|
|
48316
|
-
console.log(source_default.dim(" Remaining: ") + source_default.white(
|
|
48348
|
+
console.log(source_default.dim(" Remaining: ") + source_default.white(formatNumber2(usageData.creditsRemaining)));
|
|
48317
48349
|
console.log("");
|
|
48318
|
-
console.log(source_default.dim(" ") +
|
|
48350
|
+
console.log(source_default.dim(" ") + createProgressBar2(usageData.usagePercentage) + source_default.dim(` ${usageData.usagePercentage.toFixed(1)}%`));
|
|
48319
48351
|
}
|
|
48320
48352
|
console.log("");
|
|
48321
48353
|
console.log(source_default.bold(" This Month"));
|
|
48322
48354
|
console.log("");
|
|
48323
|
-
console.log(source_default.dim(" Scans: ") + source_default.white(
|
|
48324
|
-
console.log(source_default.dim(" Files: ") + source_default.white(
|
|
48355
|
+
console.log(source_default.dim(" Scans: ") + source_default.white(formatNumber2(usageData.totalScans)));
|
|
48356
|
+
console.log(source_default.dim(" Files: ") + source_default.white(formatNumber2(usageData.totalFiles)));
|
|
48325
48357
|
console.log("");
|
|
48326
|
-
console.log(source_default.dim(" Resets on: ") + source_default.white(
|
|
48358
|
+
console.log(source_default.dim(" Resets on: ") + source_default.white(formatDate2(usageData.resetDate)));
|
|
48327
48359
|
console.log("");
|
|
48328
48360
|
if (result.recentScans && result.recentScans.length > 0) {
|
|
48329
48361
|
console.log(source_default.bold(" Recent Scans"));
|
|
@@ -48331,7 +48363,7 @@ async function usage(options) {
|
|
|
48331
48363
|
const recentToShow = result.recentScans.slice(0, 5);
|
|
48332
48364
|
for (const scan of recentToShow) {
|
|
48333
48365
|
const date = new Date(scan.createdAt);
|
|
48334
|
-
const timeAgo =
|
|
48366
|
+
const timeAgo = getTimeAgo2(date);
|
|
48335
48367
|
console.log(source_default.dim(" \u2022 ") + source_default.white(scan.repoName || "unknown") + source_default.dim(` (${timeAgo})`));
|
|
48336
48368
|
console.log(source_default.dim(" ") + source_default.dim(`${scan.filesScanned} files, ${scan.findingsCount} findings, ${scan.creditsUsed} credits`));
|
|
48337
48369
|
}
|
|
@@ -48351,7 +48383,7 @@ async function usage(options) {
|
|
|
48351
48383
|
process.exit(1);
|
|
48352
48384
|
}
|
|
48353
48385
|
}
|
|
48354
|
-
function
|
|
48386
|
+
function getTimeAgo2(date) {
|
|
48355
48387
|
const now = /* @__PURE__ */ new Date();
|
|
48356
48388
|
const diffMs = now.getTime() - date.getTime();
|
|
48357
48389
|
const diffMins = Math.floor(diffMs / 6e4);
|
|
@@ -48367,7 +48399,7 @@ var usageCommand = new Command("usage").description("Show current usage and quot
|
|
|
48367
48399
|
|
|
48368
48400
|
// src/index.ts
|
|
48369
48401
|
var program2 = new Command();
|
|
48370
|
-
program2.name("oculum").description("AI-native security scanner for detecting vulnerabilities in LLM-generated code").version("1.0.
|
|
48402
|
+
program2.name("oculum").description("AI-native security scanner for detecting vulnerabilities in LLM-generated code").version("1.0.7").addHelpText("after", `
|
|
48371
48403
|
Quick Start:
|
|
48372
48404
|
$ oculum scan . Scan current directory (free)
|
|
48373
48405
|
$ oculum ui Interactive mode with guided setup
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oculum/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "AI-native security scanner CLI for detecting vulnerabilities in AI-generated code, BYOK patterns, and modern web applications",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -18,14 +18,14 @@
|
|
|
18
18
|
"url": "https://github.com/flexipie/oculum/issues"
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
21
|
-
"build": "esbuild src/index.ts --bundle --platform=node --target=node18 --outfile=dist/index.js --banner:js=\"#!/usr/bin/env node\" --define:process.env.OCULUM_API_URL='undefined' --define:VERSION='\"1.0.
|
|
21
|
+
"build": "esbuild src/index.ts --bundle --platform=node --target=node18 --outfile=dist/index.js --banner:js=\"#!/usr/bin/env node\" --define:process.env.OCULUM_API_URL='undefined' --define:VERSION='\"1.0.8\"'",
|
|
22
22
|
"dev": "npm run build -- --watch",
|
|
23
23
|
"test": "echo \"No tests configured yet\"",
|
|
24
24
|
"lint": "eslint src/"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@oculum/scanner": "^1.0.
|
|
28
|
-
"@oculum/shared": "^1.0.
|
|
27
|
+
"@oculum/scanner": "^1.0.3",
|
|
28
|
+
"@oculum/shared": "^1.0.2",
|
|
29
29
|
"commander": "^12.1.0",
|
|
30
30
|
"chalk": "^5.3.0",
|
|
31
31
|
"ora": "^8.1.1",
|