@oculum/cli 1.0.0 → 1.0.2
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 +315 -86
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -17678,7 +17678,7 @@ var require_ai_fingerprinting = __commonJS({
|
|
|
17678
17678
|
return vulnerabilities;
|
|
17679
17679
|
}
|
|
17680
17680
|
const anyUsageByContext = categorizeAnyUsage(lines, filePath);
|
|
17681
|
-
const priorityAny = anyUsageByContext.filter((
|
|
17681
|
+
const priorityAny = anyUsageByContext.filter((usage2) => usage2.context === "api_boundary" || usage2.context === "database_layer" || usage2.context === "auth_handler");
|
|
17682
17682
|
const cappedAny = priorityAny.slice(0, 5);
|
|
17683
17683
|
if (cappedAny.length === 0) {
|
|
17684
17684
|
return vulnerabilities;
|
|
@@ -17708,21 +17708,21 @@ var require_ai_fingerprinting = __commonJS({
|
|
|
17708
17708
|
layer: 2
|
|
17709
17709
|
});
|
|
17710
17710
|
} else {
|
|
17711
|
-
for (const
|
|
17711
|
+
for (const usage2 of cappedAny) {
|
|
17712
17712
|
const contextNames = {
|
|
17713
17713
|
"api_boundary": "API request/response handler",
|
|
17714
17714
|
"database_layer": "Database query",
|
|
17715
17715
|
"auth_handler": "Authentication logic"
|
|
17716
17716
|
};
|
|
17717
17717
|
vulnerabilities.push({
|
|
17718
|
-
id: `ai-fingerprint-any-${filePath}-${
|
|
17718
|
+
id: `ai-fingerprint-any-${filePath}-${usage2.lineNumber}`,
|
|
17719
17719
|
filePath,
|
|
17720
|
-
lineNumber:
|
|
17721
|
-
lineContent:
|
|
17720
|
+
lineNumber: usage2.lineNumber,
|
|
17721
|
+
lineContent: usage2.lineContent,
|
|
17722
17722
|
severity: "low",
|
|
17723
17723
|
category: "ai_pattern",
|
|
17724
|
-
title: `[AI Pattern] TypeScript 'any' in ${contextNames[
|
|
17725
|
-
description: `Using 'any' type at a security boundary bypasses type checking and can lead to type confusion vulnerabilities. This is especially risky in ${contextNames[
|
|
17724
|
+
title: `[AI Pattern] TypeScript 'any' in ${contextNames[usage2.context] || usage2.context}`,
|
|
17725
|
+
description: `Using 'any' type at a security boundary bypasses type checking and can lead to type confusion vulnerabilities. This is especially risky in ${contextNames[usage2.context] || usage2.context}.`,
|
|
17726
17726
|
suggestedFix: 'Replace "any" with an explicit type. Use typed request schemas, ORM models, or interface definitions.',
|
|
17727
17727
|
confidence: "medium",
|
|
17728
17728
|
layer: 2
|
|
@@ -29411,11 +29411,11 @@ var require_AbstractChatCompletionRunner = __commonJS({
|
|
|
29411
29411
|
prompt_tokens: 0,
|
|
29412
29412
|
total_tokens: 0
|
|
29413
29413
|
};
|
|
29414
|
-
for (const { usage } of this._chatCompletions) {
|
|
29415
|
-
if (
|
|
29416
|
-
total.completion_tokens +=
|
|
29417
|
-
total.prompt_tokens +=
|
|
29418
|
-
total.total_tokens +=
|
|
29414
|
+
for (const { usage: usage2 } of this._chatCompletions) {
|
|
29415
|
+
if (usage2) {
|
|
29416
|
+
total.completion_tokens += usage2.completion_tokens;
|
|
29417
|
+
total.prompt_tokens += usage2.prompt_tokens;
|
|
29418
|
+
total.total_tokens += usage2.total_tokens;
|
|
29419
29419
|
}
|
|
29420
29420
|
}
|
|
29421
29421
|
return total;
|
|
@@ -37001,11 +37001,11 @@ For each candidate finding, return:
|
|
|
37001
37001
|
max_completion_tokens: 4096
|
|
37002
37002
|
}));
|
|
37003
37003
|
statsLock.apiCalls++;
|
|
37004
|
-
const
|
|
37005
|
-
if (
|
|
37006
|
-
const promptTokens =
|
|
37007
|
-
const completionTokens =
|
|
37008
|
-
const cachedTokens =
|
|
37004
|
+
const usage2 = response.usage;
|
|
37005
|
+
if (usage2) {
|
|
37006
|
+
const promptTokens = usage2.prompt_tokens || 0;
|
|
37007
|
+
const completionTokens = usage2.completion_tokens || 0;
|
|
37008
|
+
const cachedTokens = usage2.prompt_tokens_details?.cached_tokens || 0;
|
|
37009
37009
|
const freshInputTokens = promptTokens - cachedTokens;
|
|
37010
37010
|
statsLock.estimatedInputTokens += freshInputTokens;
|
|
37011
37011
|
statsLock.estimatedOutputTokens += completionTokens;
|
|
@@ -37226,19 +37226,19 @@ For each candidate finding, return:
|
|
|
37226
37226
|
}));
|
|
37227
37227
|
stats.apiCalls++;
|
|
37228
37228
|
totalApiBatches++;
|
|
37229
|
-
const
|
|
37230
|
-
if (
|
|
37229
|
+
const usage2 = response.usage;
|
|
37230
|
+
if (usage2) {
|
|
37231
37231
|
console.log(`[DEBUG] Batch ${batchNum} - Full API Response Usage:`);
|
|
37232
|
-
console.log(JSON.stringify(
|
|
37232
|
+
console.log(JSON.stringify(usage2, null, 2));
|
|
37233
37233
|
console.log(`[DEBUG] Breakdown:`);
|
|
37234
|
-
console.log(` - input_tokens: ${
|
|
37235
|
-
console.log(` - output_tokens: ${
|
|
37236
|
-
console.log(` - cache_creation_input_tokens: ${
|
|
37237
|
-
console.log(` - cache_read_input_tokens: ${
|
|
37238
|
-
stats.estimatedInputTokens +=
|
|
37239
|
-
stats.estimatedOutputTokens +=
|
|
37240
|
-
const cacheCreation =
|
|
37241
|
-
const cacheRead =
|
|
37234
|
+
console.log(` - input_tokens: ${usage2.input_tokens || 0}`);
|
|
37235
|
+
console.log(` - output_tokens: ${usage2.output_tokens || 0}`);
|
|
37236
|
+
console.log(` - cache_creation_input_tokens: ${usage2.cache_creation_input_tokens || 0}`);
|
|
37237
|
+
console.log(` - cache_read_input_tokens: ${usage2.cache_read_input_tokens || 0}`);
|
|
37238
|
+
stats.estimatedInputTokens += usage2.input_tokens || 0;
|
|
37239
|
+
stats.estimatedOutputTokens += usage2.output_tokens || 0;
|
|
37240
|
+
const cacheCreation = usage2.cache_creation_input_tokens || 0;
|
|
37241
|
+
const cacheRead = usage2.cache_read_input_tokens || 0;
|
|
37242
37242
|
stats.cacheCreationTokens += cacheCreation;
|
|
37243
37243
|
stats.cacheReadTokens += cacheRead;
|
|
37244
37244
|
}
|
|
@@ -42827,6 +42827,27 @@ async function pollForLogin(deviceCode) {
|
|
|
42827
42827
|
tier: result.tier
|
|
42828
42828
|
};
|
|
42829
42829
|
}
|
|
42830
|
+
async function getUsage(apiKey) {
|
|
42831
|
+
const baseUrl = getApiBaseUrl();
|
|
42832
|
+
const url = `${baseUrl}/v1/usage`;
|
|
42833
|
+
try {
|
|
42834
|
+
const response = await fetch(url, {
|
|
42835
|
+
method: "GET",
|
|
42836
|
+
headers: {
|
|
42837
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
42838
|
+
"X-Oculum-Client": "cli",
|
|
42839
|
+
"X-Oculum-Version": "1.0.0"
|
|
42840
|
+
}
|
|
42841
|
+
});
|
|
42842
|
+
const result = await response.json();
|
|
42843
|
+
return result;
|
|
42844
|
+
} catch (error) {
|
|
42845
|
+
return {
|
|
42846
|
+
success: false,
|
|
42847
|
+
error: "Failed to fetch usage data. Please check your internet connection."
|
|
42848
|
+
};
|
|
42849
|
+
}
|
|
42850
|
+
}
|
|
42830
42851
|
|
|
42831
42852
|
// src/utils/errors.ts
|
|
42832
42853
|
function enhanceError(error) {
|
|
@@ -42892,14 +42913,18 @@ function enhanceAPIError(error) {
|
|
|
42892
42913
|
]
|
|
42893
42914
|
};
|
|
42894
42915
|
case 429:
|
|
42916
|
+
const rateLimitInfo = error.reason === "quota_exceeded" ? "You've reached your monthly scan quota." : "Too many requests in a short period.";
|
|
42917
|
+
const rateLimitSuggestion = error.reason === "quota_exceeded" ? "Your quota resets at the start of next month. Upgrade to Pro for higher limits." : "Wait a moment and try again. Consider using --depth cheap for unlimited local scans.";
|
|
42895
42918
|
return {
|
|
42896
|
-
message:
|
|
42897
|
-
suggestion:
|
|
42919
|
+
message: rateLimitInfo,
|
|
42920
|
+
suggestion: rateLimitSuggestion,
|
|
42898
42921
|
category: "server",
|
|
42899
42922
|
recoveryActions: [
|
|
42900
|
-
{ label: "
|
|
42901
|
-
{ label: "View
|
|
42902
|
-
|
|
42923
|
+
{ label: "Use free local scan", command: "oculum scan . --depth cheap", action: "fallback" },
|
|
42924
|
+
{ label: "View usage & upgrade", action: "upgrade" },
|
|
42925
|
+
{ label: "Retry in 30 seconds", action: "retry" }
|
|
42926
|
+
],
|
|
42927
|
+
details: error.reason === "quota_exceeded" ? "Check your usage at https://oculum.dev/dashboard/usage" : void 0
|
|
42903
42928
|
};
|
|
42904
42929
|
case 500:
|
|
42905
42930
|
case 502:
|
|
@@ -43443,11 +43468,17 @@ async function runScanOnce(targetPath, options) {
|
|
|
43443
43468
|
const noColor = options.color === false;
|
|
43444
43469
|
if ((options.depth === "validated" || options.depth === "deep") && !isAuthenticated()) {
|
|
43445
43470
|
if (!options.quiet) {
|
|
43446
|
-
console.log(
|
|
43447
|
-
console.log(
|
|
43448
|
-
|
|
43449
|
-
);
|
|
43450
|
-
console.log(source_default.dim("
|
|
43471
|
+
console.log("");
|
|
43472
|
+
console.log(source_default.yellow("\u26A0\uFE0F AI-powered scans require authentication"));
|
|
43473
|
+
console.log("");
|
|
43474
|
+
console.log(source_default.dim(" You requested: ") + source_default.white(options.depth) + source_default.dim(" scan"));
|
|
43475
|
+
console.log(source_default.dim(" This requires: ") + source_default.white("Pro subscription"));
|
|
43476
|
+
console.log("");
|
|
43477
|
+
console.log(source_default.dim(" Options:"));
|
|
43478
|
+
console.log(source_default.cyan(" \u2022 oculum login") + source_default.dim(" - Authenticate to unlock Pro features"));
|
|
43479
|
+
console.log(source_default.cyan(" \u2022 --depth cheap") + source_default.dim(" - Use free local pattern matching"));
|
|
43480
|
+
console.log("");
|
|
43481
|
+
console.log(source_default.dim(" Continuing with ") + source_default.green("cheap") + source_default.dim(" scan (free)...\n"));
|
|
43451
43482
|
}
|
|
43452
43483
|
options.depth = "cheap";
|
|
43453
43484
|
}
|
|
@@ -43469,19 +43500,24 @@ async function runScanOnce(targetPath, options) {
|
|
|
43469
43500
|
const onProgress = (progress) => {
|
|
43470
43501
|
switch (progress.status) {
|
|
43471
43502
|
case "layer1":
|
|
43472
|
-
spinner.text = `
|
|
43503
|
+
spinner.text = `Scanning patterns... ${source_default.dim(`(${progress.vulnerabilitiesFound} potential issues)`)}`;
|
|
43473
43504
|
break;
|
|
43474
43505
|
case "layer2":
|
|
43475
|
-
spinner.text = `
|
|
43506
|
+
spinner.text = `Analyzing code structure... ${source_default.dim(`(${progress.vulnerabilitiesFound} findings)`)}`;
|
|
43476
43507
|
break;
|
|
43477
43508
|
case "validating":
|
|
43478
|
-
spinner.text = `AI
|
|
43509
|
+
spinner.text = `AI validating findings... ${source_default.dim(`(${progress.vulnerabilitiesFound} candidates)`)}`;
|
|
43479
43510
|
break;
|
|
43480
43511
|
case "layer3":
|
|
43481
|
-
spinner.text = `
|
|
43512
|
+
spinner.text = `Deep AI analysis in progress...`;
|
|
43482
43513
|
break;
|
|
43483
43514
|
case "complete":
|
|
43484
|
-
|
|
43515
|
+
const issueText = progress.vulnerabilitiesFound === 1 ? "issue" : "issues";
|
|
43516
|
+
if (progress.vulnerabilitiesFound === 0) {
|
|
43517
|
+
spinner.succeed(source_default.green("Scan complete: No security issues found! \u2713"));
|
|
43518
|
+
} else {
|
|
43519
|
+
spinner.succeed(`Scan complete: ${progress.vulnerabilitiesFound} ${issueText} found`);
|
|
43520
|
+
}
|
|
43485
43521
|
break;
|
|
43486
43522
|
case "failed":
|
|
43487
43523
|
spinner.fail(progress.message);
|
|
@@ -43614,23 +43650,43 @@ async function login(options) {
|
|
|
43614
43650
|
const result = await verifyApiKey(options.apiKey);
|
|
43615
43651
|
if (!result.valid) {
|
|
43616
43652
|
spinner.fail("Invalid API key");
|
|
43653
|
+
console.log("");
|
|
43654
|
+
console.log(source_default.dim(" The API key could not be verified. Please check:"));
|
|
43655
|
+
console.log(source_default.dim(" \u2022 The key is copied correctly (no extra spaces)"));
|
|
43656
|
+
console.log(source_default.dim(" \u2022 The key has not expired"));
|
|
43657
|
+
console.log(source_default.dim(" \u2022 Generate a new key at: ") + source_default.cyan("https://oculum.dev/dashboard/api-keys"));
|
|
43658
|
+
console.log("");
|
|
43617
43659
|
process.exit(1);
|
|
43618
43660
|
}
|
|
43619
43661
|
setAuthCredentials(options.apiKey, result.email, result.tier);
|
|
43620
43662
|
spinner.succeed("Logged in successfully!");
|
|
43621
|
-
console.log(
|
|
43622
|
-
console.log(source_default.dim(
|
|
43663
|
+
console.log("");
|
|
43664
|
+
console.log(source_default.dim(" Email: ") + source_default.white(result.email));
|
|
43665
|
+
console.log(source_default.dim(" Plan: ") + source_default.white(result.tier));
|
|
43666
|
+
console.log("");
|
|
43667
|
+
console.log(source_default.green(" You can now use AI-powered scans:"));
|
|
43668
|
+
console.log(source_default.cyan(" oculum scan . --depth validated"));
|
|
43669
|
+
console.log("");
|
|
43623
43670
|
return;
|
|
43624
43671
|
}
|
|
43625
43672
|
try {
|
|
43673
|
+
console.log("");
|
|
43674
|
+
console.log(source_default.bold(" \u{1F510} Oculum Login"));
|
|
43675
|
+
console.log(source_default.dim(" " + "\u2500".repeat(38)));
|
|
43676
|
+
console.log("");
|
|
43626
43677
|
spinner.start("Initiating login...");
|
|
43627
|
-
const { authUrl, deviceCode } = await initiateLogin();
|
|
43678
|
+
const { authUrl, deviceCode, userCode } = await initiateLogin();
|
|
43628
43679
|
spinner.stop();
|
|
43629
|
-
console.log("
|
|
43630
|
-
console.log(
|
|
43631
|
-
|
|
43632
|
-
console.log(
|
|
43633
|
-
|
|
43680
|
+
console.log(source_default.white(" Open this URL in your browser to login:"));
|
|
43681
|
+
console.log("");
|
|
43682
|
+
console.log(source_default.cyan.bold(` ${authUrl}`));
|
|
43683
|
+
console.log("");
|
|
43684
|
+
console.log(source_default.white(" Or enter this code manually: ") + source_default.yellow.bold(userCode));
|
|
43685
|
+
console.log("");
|
|
43686
|
+
console.log(source_default.dim(" Waiting for browser authorization..."));
|
|
43687
|
+
console.log(source_default.dim(" (This will timeout after 5 minutes)"));
|
|
43688
|
+
console.log("");
|
|
43689
|
+
spinner.start("Waiting for authorization...");
|
|
43634
43690
|
const maxAttempts = 60;
|
|
43635
43691
|
for (let i = 0; i < maxAttempts; i++) {
|
|
43636
43692
|
await new Promise((resolve7) => setTimeout(resolve7, 5e3));
|
|
@@ -43638,15 +43694,35 @@ async function login(options) {
|
|
|
43638
43694
|
if (result.complete) {
|
|
43639
43695
|
setAuthCredentials(result.apiKey, result.email, result.tier);
|
|
43640
43696
|
spinner.succeed("Logged in successfully!");
|
|
43641
|
-
console.log(
|
|
43642
|
-
console.log(source_default.dim(
|
|
43697
|
+
console.log("");
|
|
43698
|
+
console.log(source_default.dim(" Email: ") + source_default.white(result.email));
|
|
43699
|
+
console.log(source_default.dim(" Plan: ") + source_default.white(result.tier));
|
|
43700
|
+
console.log("");
|
|
43701
|
+
console.log(source_default.green(" You can now use AI-powered scans:"));
|
|
43702
|
+
console.log(source_default.cyan(" oculum scan . --depth validated"));
|
|
43703
|
+
console.log("");
|
|
43643
43704
|
return;
|
|
43644
43705
|
}
|
|
43706
|
+
if (result.expired) {
|
|
43707
|
+
spinner.fail("Login session expired");
|
|
43708
|
+
console.log("");
|
|
43709
|
+
console.log(source_default.dim(" Please try again: ") + source_default.cyan("oculum login"));
|
|
43710
|
+
console.log("");
|
|
43711
|
+
process.exit(1);
|
|
43712
|
+
}
|
|
43645
43713
|
}
|
|
43646
|
-
spinner.fail("Login timed out
|
|
43714
|
+
spinner.fail("Login timed out");
|
|
43715
|
+
console.log("");
|
|
43716
|
+
console.log(source_default.dim(" The login session expired. Please try again:"));
|
|
43717
|
+
console.log(source_default.cyan(" oculum login"));
|
|
43718
|
+
console.log("");
|
|
43647
43719
|
process.exit(1);
|
|
43648
43720
|
} catch (err) {
|
|
43649
|
-
spinner.fail(
|
|
43721
|
+
spinner.fail("Login failed");
|
|
43722
|
+
console.log("");
|
|
43723
|
+
console.log(source_default.dim(" Error: ") + source_default.red(String(err)));
|
|
43724
|
+
console.log(source_default.dim(" Please check your internet connection and try again."));
|
|
43725
|
+
console.log("");
|
|
43650
43726
|
process.exit(1);
|
|
43651
43727
|
}
|
|
43652
43728
|
}
|
|
@@ -43660,40 +43736,67 @@ function logout() {
|
|
|
43660
43736
|
}
|
|
43661
43737
|
async function status() {
|
|
43662
43738
|
const config = getConfig();
|
|
43663
|
-
console.log("
|
|
43664
|
-
console.log(source_default.
|
|
43739
|
+
console.log("");
|
|
43740
|
+
console.log(source_default.bold(" \u{1F510} Oculum Authentication Status"));
|
|
43741
|
+
console.log(source_default.dim(" " + "\u2500".repeat(38)));
|
|
43665
43742
|
if (!isAuthenticated()) {
|
|
43666
|
-
console.log(
|
|
43667
|
-
console.log(source_default.
|
|
43668
|
-
console.log(
|
|
43669
|
-
console.log(source_default.dim("
|
|
43743
|
+
console.log("");
|
|
43744
|
+
console.log(source_default.yellow(" Status: ") + source_default.white("Not logged in"));
|
|
43745
|
+
console.log("");
|
|
43746
|
+
console.log(source_default.dim(" You can use Oculum without logging in for free local scans."));
|
|
43747
|
+
console.log(source_default.dim(" Login to unlock AI-powered validation and deep analysis."));
|
|
43748
|
+
console.log("");
|
|
43749
|
+
console.log(source_default.bold(" Quick Start:"));
|
|
43750
|
+
console.log(source_default.cyan(" oculum scan .") + source_default.dim(" Free pattern-based scan"));
|
|
43751
|
+
console.log(source_default.cyan(" oculum login") + source_default.dim(" Authenticate for Pro features"));
|
|
43752
|
+
console.log("");
|
|
43670
43753
|
return;
|
|
43671
43754
|
}
|
|
43672
|
-
const spinner = ora("Verifying credentials...").start();
|
|
43755
|
+
const spinner = ora(" Verifying credentials...").start();
|
|
43673
43756
|
const result = await verifyApiKey(config.apiKey);
|
|
43674
43757
|
if (!result.valid) {
|
|
43675
|
-
spinner.fail("Stored credentials are invalid or expired
|
|
43676
|
-
console.log(
|
|
43758
|
+
spinner.fail(" Stored credentials are invalid or expired");
|
|
43759
|
+
console.log("");
|
|
43760
|
+
console.log(source_default.dim(" Your session may have expired. Please login again:"));
|
|
43761
|
+
console.log(source_default.cyan(" oculum login"));
|
|
43762
|
+
console.log("");
|
|
43677
43763
|
return;
|
|
43678
43764
|
}
|
|
43679
|
-
spinner.succeed("Authenticated");
|
|
43680
|
-
console.log(
|
|
43681
|
-
|
|
43682
|
-
console.log("\n" + source_default.bold("Available Scan Depths:"));
|
|
43765
|
+
spinner.succeed(" Authenticated");
|
|
43766
|
+
console.log("");
|
|
43767
|
+
const email = result.email || config.email || "unknown";
|
|
43683
43768
|
const tier = result.tier || config.tier || "free";
|
|
43684
|
-
|
|
43769
|
+
const tierBadge = tier === "pro" ? source_default.bgBlue.white(" PRO ") : tier === "enterprise" ? source_default.bgMagenta.white(" ENTERPRISE ") : source_default.bgGray.white(" FREE ");
|
|
43770
|
+
console.log(source_default.dim(" Email: ") + source_default.white(email));
|
|
43771
|
+
console.log(source_default.dim(" Plan: ") + tierBadge);
|
|
43772
|
+
console.log("");
|
|
43773
|
+
console.log(source_default.bold(" Available Scan Depths:"));
|
|
43774
|
+
console.log("");
|
|
43775
|
+
console.log(source_default.green(" \u2713 ") + source_default.white("cheap") + source_default.dim(" Fast pattern matching (always free)"));
|
|
43685
43776
|
if (tier === "pro" || tier === "enterprise") {
|
|
43686
|
-
console.log(source_default.green(" validated
|
|
43687
|
-
console.log(source_default.
|
|
43777
|
+
console.log(source_default.green(" \u2713 ") + source_default.white("validated") + source_default.dim(" AI validation (~70% fewer false positives)"));
|
|
43778
|
+
console.log(source_default.dim(" \u{1F512} ") + source_default.white("deep") + source_default.dim(" Multi-agent analysis (coming soon)"));
|
|
43688
43779
|
} else {
|
|
43689
|
-
console.log(source_default.dim(" validated
|
|
43690
|
-
console.log(source_default.dim(" deep
|
|
43780
|
+
console.log(source_default.dim(" \u{1F512} ") + source_default.white("validated") + source_default.dim(" AI validation (requires Pro)"));
|
|
43781
|
+
console.log(source_default.dim(" \u{1F512} ") + source_default.white("deep") + source_default.dim(" Multi-agent analysis (requires Pro)"));
|
|
43691
43782
|
}
|
|
43692
43783
|
console.log("");
|
|
43784
|
+
console.log(source_default.dim(" Manage subscription: ") + source_default.cyan("https://oculum.dev/billing"));
|
|
43785
|
+
console.log("");
|
|
43693
43786
|
}
|
|
43694
43787
|
function upgrade() {
|
|
43695
|
-
console.log(
|
|
43696
|
-
console.log(source_default.bold("
|
|
43788
|
+
console.log("");
|
|
43789
|
+
console.log(source_default.bold(" \u{1F680} Upgrade to Oculum Pro"));
|
|
43790
|
+
console.log(source_default.dim(" " + "\u2500".repeat(38)));
|
|
43791
|
+
console.log("");
|
|
43792
|
+
console.log(source_default.white(" Pro features include:"));
|
|
43793
|
+
console.log(source_default.green(" \u2713 ") + source_default.white("AI-validated scans") + source_default.dim(" - ~70% fewer false positives"));
|
|
43794
|
+
console.log(source_default.green(" \u2713 ") + source_default.white("Higher scan limits") + source_default.dim(" - More scans per month"));
|
|
43795
|
+
console.log(source_default.green(" \u2713 ") + source_default.white("Priority support") + source_default.dim(" - Get help when you need it"));
|
|
43796
|
+
console.log(source_default.green(" \u2713 ") + source_default.white("GitHub Action") + source_default.dim(" - Automated PR scanning"));
|
|
43797
|
+
console.log("");
|
|
43798
|
+
console.log(source_default.dim(" Visit: ") + source_default.cyan.underline("https://oculum.dev/pricing"));
|
|
43799
|
+
console.log("");
|
|
43697
43800
|
}
|
|
43698
43801
|
var authCommand = new Command("auth").description("Manage authentication");
|
|
43699
43802
|
authCommand.command("login").description("Log in to Oculum").option("-k, --api-key <key>", "API key (skip browser auth)").action(login);
|
|
@@ -45454,18 +45557,23 @@ async function watch2(targetPath, options) {
|
|
|
45454
45557
|
const config = getConfig();
|
|
45455
45558
|
if ((options.depth === "validated" || options.depth === "deep") && !isAuthenticated()) {
|
|
45456
45559
|
if (!options.quiet) {
|
|
45457
|
-
console.log(
|
|
45458
|
-
console.log(source_default.
|
|
45560
|
+
console.log("");
|
|
45561
|
+
console.log(source_default.yellow("\u26A0\uFE0F AI-powered scans require authentication"));
|
|
45562
|
+
console.log(source_default.dim(" Using free local scan instead. Run `oculum login` to unlock Pro features."));
|
|
45459
45563
|
}
|
|
45460
45564
|
options.depth = "cheap";
|
|
45461
45565
|
}
|
|
45462
45566
|
if (!options.quiet) {
|
|
45463
|
-
console.log(
|
|
45464
|
-
console.log(source_default.
|
|
45465
|
-
console.log(source_default.dim(
|
|
45466
|
-
console.log(
|
|
45467
|
-
console.log(source_default.dim(
|
|
45468
|
-
console.log(source_default.dim("
|
|
45567
|
+
console.log("");
|
|
45568
|
+
console.log(source_default.bold(" \u{1F441}\uFE0F Oculum Watch Mode"));
|
|
45569
|
+
console.log(source_default.dim(" " + "\u2500".repeat(38)));
|
|
45570
|
+
console.log("");
|
|
45571
|
+
console.log(source_default.dim(" Watching: ") + source_default.white(absolutePath));
|
|
45572
|
+
console.log(source_default.dim(" Depth: ") + source_default.white(options.depth === "cheap" ? "Quick (pattern matching)" : options.depth));
|
|
45573
|
+
console.log(source_default.dim(" Debounce: ") + source_default.white(`${options.debounce}ms`));
|
|
45574
|
+
console.log("");
|
|
45575
|
+
console.log(source_default.dim(" Press ") + source_default.white("Ctrl+C") + source_default.dim(" to stop watching"));
|
|
45576
|
+
console.log("");
|
|
45469
45577
|
}
|
|
45470
45578
|
const changedFiles = /* @__PURE__ */ new Set();
|
|
45471
45579
|
let isScanning = false;
|
|
@@ -45478,8 +45586,9 @@ async function watch2(targetPath, options) {
|
|
|
45478
45586
|
console.clear();
|
|
45479
45587
|
}
|
|
45480
45588
|
if (!options.quiet) {
|
|
45481
|
-
|
|
45482
|
-
|
|
45589
|
+
const fileText = filesToScan.length === 1 ? "file" : "files";
|
|
45590
|
+
console.log("");
|
|
45591
|
+
console.log(source_default.cyan(` \u27F3 [${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] Scanning ${filesToScan.length} changed ${fileText}...`));
|
|
45483
45592
|
}
|
|
45484
45593
|
const scanFiles = [];
|
|
45485
45594
|
for (const filePath of filesToScan) {
|
|
@@ -45509,9 +45618,12 @@ async function watch2(targetPath, options) {
|
|
|
45509
45618
|
);
|
|
45510
45619
|
if (result.vulnerabilities.length === 0) {
|
|
45511
45620
|
if (!options.quiet) {
|
|
45512
|
-
console.log(source_default.green("No issues found
|
|
45621
|
+
console.log(source_default.green(" \u2713 No issues found"));
|
|
45513
45622
|
}
|
|
45514
45623
|
} else {
|
|
45624
|
+
const issueCount = result.vulnerabilities.length;
|
|
45625
|
+
const issueText = issueCount === 1 ? "issue" : "issues";
|
|
45626
|
+
console.log(source_default.yellow(` \u26A0 Found ${issueCount} ${issueText}:`));
|
|
45515
45627
|
console.log((0, import_formatters2.formatTerminalOutput)(result, {
|
|
45516
45628
|
maxFindingsPerGroup: 5
|
|
45517
45629
|
}));
|
|
@@ -46498,6 +46610,7 @@ async function showFeatureExploration() {
|
|
|
46498
46610
|
console.log(source_default.dim(" oculum watch . ") + source_default.white("Watch for changes"));
|
|
46499
46611
|
console.log(source_default.dim(" oculum ui ") + source_default.white("Interactive mode"));
|
|
46500
46612
|
console.log(source_default.dim(" oculum login ") + source_default.white("Authenticate for Pro features"));
|
|
46613
|
+
console.log(source_default.dim(" oculum usage ") + source_default.white("View credits and quota"));
|
|
46501
46614
|
console.log(source_default.dim(" oculum --help ") + source_default.white("Show all commands\n"));
|
|
46502
46615
|
await ve({
|
|
46503
46616
|
message: "Press Enter to continue",
|
|
@@ -47588,6 +47701,120 @@ var uiCommand = new Command("ui").description("Interactive terminal UI").action(
|
|
|
47588
47701
|
await runUI();
|
|
47589
47702
|
});
|
|
47590
47703
|
|
|
47704
|
+
// src/commands/usage.ts
|
|
47705
|
+
function formatNumber(num) {
|
|
47706
|
+
if (num === -1) return "unlimited";
|
|
47707
|
+
return num.toLocaleString();
|
|
47708
|
+
}
|
|
47709
|
+
function createProgressBar(percentage, width = 20) {
|
|
47710
|
+
const filled = Math.round(percentage / 100 * width);
|
|
47711
|
+
const empty = width - filled;
|
|
47712
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
47713
|
+
if (percentage >= 90) return source_default.red(bar);
|
|
47714
|
+
if (percentage >= 70) return source_default.yellow(bar);
|
|
47715
|
+
return source_default.green(bar);
|
|
47716
|
+
}
|
|
47717
|
+
function formatDate(dateStr) {
|
|
47718
|
+
const date = new Date(dateStr);
|
|
47719
|
+
return date.toLocaleDateString("en-US", {
|
|
47720
|
+
month: "short",
|
|
47721
|
+
day: "numeric",
|
|
47722
|
+
year: "numeric"
|
|
47723
|
+
});
|
|
47724
|
+
}
|
|
47725
|
+
async function usage(options) {
|
|
47726
|
+
const config = getConfig();
|
|
47727
|
+
if (!isAuthenticated()) {
|
|
47728
|
+
console.log("");
|
|
47729
|
+
console.log(source_default.yellow(" \u26A0 Not logged in"));
|
|
47730
|
+
console.log("");
|
|
47731
|
+
console.log(source_default.dim(" Login to view your usage and quota:"));
|
|
47732
|
+
console.log(source_default.cyan(" oculum login"));
|
|
47733
|
+
console.log("");
|
|
47734
|
+
process.exit(1);
|
|
47735
|
+
}
|
|
47736
|
+
const spinner = ora("Fetching usage data...").start();
|
|
47737
|
+
try {
|
|
47738
|
+
const result = await getUsage(config.apiKey);
|
|
47739
|
+
if (!result.success || !result.usage || !result.plan) {
|
|
47740
|
+
spinner.fail("Failed to fetch usage data");
|
|
47741
|
+
console.log("");
|
|
47742
|
+
console.log(source_default.dim(" Error: ") + source_default.red(result.error || "Unknown error"));
|
|
47743
|
+
console.log("");
|
|
47744
|
+
process.exit(1);
|
|
47745
|
+
}
|
|
47746
|
+
spinner.stop();
|
|
47747
|
+
if (options.json) {
|
|
47748
|
+
console.log(JSON.stringify(result, null, 2));
|
|
47749
|
+
return;
|
|
47750
|
+
}
|
|
47751
|
+
const { plan, usage: usageData } = result;
|
|
47752
|
+
console.log("");
|
|
47753
|
+
console.log(source_default.bold(" \u{1F4CA} Oculum Usage"));
|
|
47754
|
+
console.log(source_default.dim(" " + "\u2500".repeat(38)));
|
|
47755
|
+
console.log("");
|
|
47756
|
+
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 ");
|
|
47757
|
+
console.log(source_default.dim(" Plan: ") + planBadge + source_default.white(` ${plan.displayName}`));
|
|
47758
|
+
console.log(source_default.dim(" Month: ") + source_default.white(usageData.month));
|
|
47759
|
+
console.log("");
|
|
47760
|
+
console.log(source_default.bold(" Credits Usage"));
|
|
47761
|
+
console.log("");
|
|
47762
|
+
const creditsDisplay = usageData.creditsLimit === -1 ? `${formatNumber(usageData.creditsUsed)} / unlimited` : `${formatNumber(usageData.creditsUsed)} / ${formatNumber(usageData.creditsLimit)}`;
|
|
47763
|
+
console.log(source_default.dim(" Used: ") + source_default.white(creditsDisplay));
|
|
47764
|
+
if (usageData.creditsLimit !== -1) {
|
|
47765
|
+
console.log(source_default.dim(" Remaining: ") + source_default.white(formatNumber(usageData.creditsRemaining)));
|
|
47766
|
+
console.log("");
|
|
47767
|
+
console.log(source_default.dim(" ") + createProgressBar(usageData.usagePercentage) + source_default.dim(` ${usageData.usagePercentage.toFixed(1)}%`));
|
|
47768
|
+
}
|
|
47769
|
+
console.log("");
|
|
47770
|
+
console.log(source_default.bold(" This Month"));
|
|
47771
|
+
console.log("");
|
|
47772
|
+
console.log(source_default.dim(" Scans: ") + source_default.white(formatNumber(usageData.totalScans)));
|
|
47773
|
+
console.log(source_default.dim(" Files: ") + source_default.white(formatNumber(usageData.totalFiles)));
|
|
47774
|
+
console.log(source_default.dim(" Cost: ") + source_default.white(`$${usageData.totalCostDollars.toFixed(4)}`));
|
|
47775
|
+
console.log("");
|
|
47776
|
+
console.log(source_default.dim(" Resets on: ") + source_default.white(formatDate(usageData.resetDate)));
|
|
47777
|
+
console.log("");
|
|
47778
|
+
if (result.recentScans && result.recentScans.length > 0) {
|
|
47779
|
+
console.log(source_default.bold(" Recent Scans"));
|
|
47780
|
+
console.log("");
|
|
47781
|
+
const recentToShow = result.recentScans.slice(0, 5);
|
|
47782
|
+
for (const scan of recentToShow) {
|
|
47783
|
+
const date = new Date(scan.createdAt);
|
|
47784
|
+
const timeAgo = getTimeAgo(date);
|
|
47785
|
+
console.log(source_default.dim(" \u2022 ") + source_default.white(scan.repoName || "unknown") + source_default.dim(` (${timeAgo})`));
|
|
47786
|
+
console.log(source_default.dim(" ") + source_default.dim(`${scan.filesScanned} files, ${scan.findingsCount} findings, ${scan.creditsUsed} credits`));
|
|
47787
|
+
}
|
|
47788
|
+
console.log("");
|
|
47789
|
+
}
|
|
47790
|
+
if (plan.name === "free" || plan.name === "starter") {
|
|
47791
|
+
console.log(source_default.dim(" " + "\u2500".repeat(38)));
|
|
47792
|
+
console.log("");
|
|
47793
|
+
console.log(source_default.dim(" Need more credits? ") + source_default.cyan("oculum upgrade"));
|
|
47794
|
+
console.log("");
|
|
47795
|
+
}
|
|
47796
|
+
} catch (error) {
|
|
47797
|
+
spinner.fail("Failed to fetch usage data");
|
|
47798
|
+
console.log("");
|
|
47799
|
+
console.log(source_default.dim(" Error: ") + source_default.red(String(error)));
|
|
47800
|
+
console.log("");
|
|
47801
|
+
process.exit(1);
|
|
47802
|
+
}
|
|
47803
|
+
}
|
|
47804
|
+
function getTimeAgo(date) {
|
|
47805
|
+
const now = /* @__PURE__ */ new Date();
|
|
47806
|
+
const diffMs = now.getTime() - date.getTime();
|
|
47807
|
+
const diffMins = Math.floor(diffMs / 6e4);
|
|
47808
|
+
const diffHours = Math.floor(diffMs / 36e5);
|
|
47809
|
+
const diffDays = Math.floor(diffMs / 864e5);
|
|
47810
|
+
if (diffMins < 1) return "just now";
|
|
47811
|
+
if (diffMins < 60) return `${diffMins}m ago`;
|
|
47812
|
+
if (diffHours < 24) return `${diffHours}h ago`;
|
|
47813
|
+
if (diffDays < 7) return `${diffDays}d ago`;
|
|
47814
|
+
return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
47815
|
+
}
|
|
47816
|
+
var usageCommand = new Command("usage").description("Show current usage and quota").option("--json", "Output as JSON").action(usage);
|
|
47817
|
+
|
|
47591
47818
|
// src/index.ts
|
|
47592
47819
|
var program2 = new Command();
|
|
47593
47820
|
program2.name("oculum").description("AI-native security scanner for detecting vulnerabilities in LLM-generated code").version("1.0.0").addHelpText("after", `
|
|
@@ -47602,6 +47829,7 @@ Common Commands:
|
|
|
47602
47829
|
ui Interactive terminal UI
|
|
47603
47830
|
login Authenticate with Oculum
|
|
47604
47831
|
status Check authentication status
|
|
47832
|
+
usage View credits and quota
|
|
47605
47833
|
|
|
47606
47834
|
Learn More:
|
|
47607
47835
|
$ oculum scan --help Detailed scan options
|
|
@@ -47614,6 +47842,7 @@ program2.addCommand(authCommand);
|
|
|
47614
47842
|
program2.addCommand(loginCommand);
|
|
47615
47843
|
program2.addCommand(statusCommand);
|
|
47616
47844
|
program2.addCommand(upgradeCommand);
|
|
47845
|
+
program2.addCommand(usageCommand);
|
|
47617
47846
|
program2.addCommand(watchCommand);
|
|
47618
47847
|
program2.addCommand(uiCommand);
|
|
47619
47848
|
program2.parse();
|
package/package.json
CHANGED