@oculum/cli 1.0.7 → 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 +147 -16
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -46906,8 +46906,7 @@ function recordScan() {
|
|
|
46906
46906
|
}
|
|
46907
46907
|
|
|
46908
46908
|
// src/ui/onboarding.ts
|
|
46909
|
-
|
|
46910
|
-
console.clear();
|
|
46909
|
+
function showLogo() {
|
|
46911
46910
|
console.log(source_default.cyan(`
|
|
46912
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
|
|
46913
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
|
|
@@ -46916,6 +46915,10 @@ async function showWelcomeScreen() {
|
|
|
46916
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
|
|
46917
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
|
|
46918
46917
|
`));
|
|
46918
|
+
}
|
|
46919
|
+
async function showWelcomeScreen() {
|
|
46920
|
+
console.clear();
|
|
46921
|
+
showLogo();
|
|
46919
46922
|
console.log(source_default.bold.white(" AI-Native Security Scanner for Modern Codebases\n"));
|
|
46920
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"));
|
|
46921
46924
|
console.log(source_default.white(" Oculum detects security vulnerabilities in AI-generated"));
|
|
@@ -47972,11 +47975,136 @@ async function runHistoryFlow() {
|
|
|
47972
47975
|
}
|
|
47973
47976
|
}
|
|
47974
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
|
+
}
|
|
47975
48101
|
async function runUI() {
|
|
48102
|
+
console.clear();
|
|
48103
|
+
showLogo();
|
|
48104
|
+
console.log();
|
|
47976
48105
|
const onboardingResult = await handleOnboarding();
|
|
47977
48106
|
if (onboardingResult.quickStartResult) {
|
|
47978
48107
|
const { path: path2, depth } = onboardingResult.quickStartResult;
|
|
47979
|
-
Ie(source_default.bold("Oculum"));
|
|
47980
48108
|
try {
|
|
47981
48109
|
const { output, exitCode, result } = await runScanOnce(path2, {
|
|
47982
48110
|
depth,
|
|
@@ -48004,8 +48132,6 @@ async function runUI() {
|
|
|
48004
48132
|
} catch (err) {
|
|
48005
48133
|
M2.error(`Scan failed: ${String(err)}`);
|
|
48006
48134
|
}
|
|
48007
|
-
} else {
|
|
48008
|
-
Ie(source_default.bold("Oculum"));
|
|
48009
48135
|
}
|
|
48010
48136
|
let lastScanEntry;
|
|
48011
48137
|
const userState = getUserState();
|
|
@@ -48021,6 +48147,7 @@ async function runUI() {
|
|
|
48021
48147
|
{ value: "scan", label: "\u{1F50D} Custom Scan", hint: "Configure scan options" },
|
|
48022
48148
|
{ value: "history", label: "\u{1F4DC} Scan History", hint: `${listScanHistory().length} past scans` },
|
|
48023
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" },
|
|
48024
48151
|
{ value: "help", label: "\u2753 Help", hint: "Commands and tips" },
|
|
48025
48152
|
{ value: "exit", label: "\u{1F44B} Exit" }
|
|
48026
48153
|
];
|
|
@@ -48102,6 +48229,10 @@ async function runUI() {
|
|
|
48102
48229
|
await runHistoryFlow();
|
|
48103
48230
|
continue;
|
|
48104
48231
|
}
|
|
48232
|
+
if (action === "usage") {
|
|
48233
|
+
await runUsageFlow();
|
|
48234
|
+
continue;
|
|
48235
|
+
}
|
|
48105
48236
|
if (action === "help") {
|
|
48106
48237
|
await showHelpScreen();
|
|
48107
48238
|
continue;
|
|
@@ -48154,11 +48285,11 @@ var uiCommand = new Command("ui").description("Interactive terminal UI").action(
|
|
|
48154
48285
|
});
|
|
48155
48286
|
|
|
48156
48287
|
// src/commands/usage.ts
|
|
48157
|
-
function
|
|
48288
|
+
function formatNumber2(num) {
|
|
48158
48289
|
if (num === -1) return "unlimited";
|
|
48159
48290
|
return num.toLocaleString();
|
|
48160
48291
|
}
|
|
48161
|
-
function
|
|
48292
|
+
function createProgressBar2(percentage, width = 20) {
|
|
48162
48293
|
const filled = Math.round(percentage / 100 * width);
|
|
48163
48294
|
const empty = width - filled;
|
|
48164
48295
|
const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
@@ -48166,7 +48297,7 @@ function createProgressBar(percentage, width = 20) {
|
|
|
48166
48297
|
if (percentage >= 70) return source_default.yellow(bar);
|
|
48167
48298
|
return source_default.green(bar);
|
|
48168
48299
|
}
|
|
48169
|
-
function
|
|
48300
|
+
function formatDate2(dateStr) {
|
|
48170
48301
|
const date = new Date(dateStr);
|
|
48171
48302
|
return date.toLocaleDateString("en-US", {
|
|
48172
48303
|
month: "short",
|
|
@@ -48211,20 +48342,20 @@ async function usage(options) {
|
|
|
48211
48342
|
console.log("");
|
|
48212
48343
|
console.log(source_default.bold(" Credits Usage"));
|
|
48213
48344
|
console.log("");
|
|
48214
|
-
const creditsDisplay = usageData.creditsLimit === -1 ? `${
|
|
48345
|
+
const creditsDisplay = usageData.creditsLimit === -1 ? `${formatNumber2(usageData.creditsUsed)} / unlimited` : `${formatNumber2(usageData.creditsUsed)} / ${formatNumber2(usageData.creditsLimit)}`;
|
|
48215
48346
|
console.log(source_default.dim(" Used: ") + source_default.white(creditsDisplay));
|
|
48216
48347
|
if (usageData.creditsLimit !== -1) {
|
|
48217
|
-
console.log(source_default.dim(" Remaining: ") + source_default.white(
|
|
48348
|
+
console.log(source_default.dim(" Remaining: ") + source_default.white(formatNumber2(usageData.creditsRemaining)));
|
|
48218
48349
|
console.log("");
|
|
48219
|
-
console.log(source_default.dim(" ") +
|
|
48350
|
+
console.log(source_default.dim(" ") + createProgressBar2(usageData.usagePercentage) + source_default.dim(` ${usageData.usagePercentage.toFixed(1)}%`));
|
|
48220
48351
|
}
|
|
48221
48352
|
console.log("");
|
|
48222
48353
|
console.log(source_default.bold(" This Month"));
|
|
48223
48354
|
console.log("");
|
|
48224
|
-
console.log(source_default.dim(" Scans: ") + source_default.white(
|
|
48225
|
-
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)));
|
|
48226
48357
|
console.log("");
|
|
48227
|
-
console.log(source_default.dim(" Resets on: ") + source_default.white(
|
|
48358
|
+
console.log(source_default.dim(" Resets on: ") + source_default.white(formatDate2(usageData.resetDate)));
|
|
48228
48359
|
console.log("");
|
|
48229
48360
|
if (result.recentScans && result.recentScans.length > 0) {
|
|
48230
48361
|
console.log(source_default.bold(" Recent Scans"));
|
|
@@ -48232,7 +48363,7 @@ async function usage(options) {
|
|
|
48232
48363
|
const recentToShow = result.recentScans.slice(0, 5);
|
|
48233
48364
|
for (const scan of recentToShow) {
|
|
48234
48365
|
const date = new Date(scan.createdAt);
|
|
48235
|
-
const timeAgo =
|
|
48366
|
+
const timeAgo = getTimeAgo2(date);
|
|
48236
48367
|
console.log(source_default.dim(" \u2022 ") + source_default.white(scan.repoName || "unknown") + source_default.dim(` (${timeAgo})`));
|
|
48237
48368
|
console.log(source_default.dim(" ") + source_default.dim(`${scan.filesScanned} files, ${scan.findingsCount} findings, ${scan.creditsUsed} credits`));
|
|
48238
48369
|
}
|
|
@@ -48252,7 +48383,7 @@ async function usage(options) {
|
|
|
48252
48383
|
process.exit(1);
|
|
48253
48384
|
}
|
|
48254
48385
|
}
|
|
48255
|
-
function
|
|
48386
|
+
function getTimeAgo2(date) {
|
|
48256
48387
|
const now = /* @__PURE__ */ new Date();
|
|
48257
48388
|
const diffMs = now.getTime() - date.getTime();
|
|
48258
48389
|
const diffMins = Math.floor(diffMs / 6e4);
|
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",
|