@oculum/cli 1.0.1 → 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 +169 -29
- 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) {
|
|
@@ -43654,12 +43675,14 @@ async function login(options) {
|
|
|
43654
43675
|
console.log(source_default.dim(" " + "\u2500".repeat(38)));
|
|
43655
43676
|
console.log("");
|
|
43656
43677
|
spinner.start("Initiating login...");
|
|
43657
|
-
const { authUrl, deviceCode } = await initiateLogin();
|
|
43678
|
+
const { authUrl, deviceCode, userCode } = await initiateLogin();
|
|
43658
43679
|
spinner.stop();
|
|
43659
43680
|
console.log(source_default.white(" Open this URL in your browser to login:"));
|
|
43660
43681
|
console.log("");
|
|
43661
43682
|
console.log(source_default.cyan.bold(` ${authUrl}`));
|
|
43662
43683
|
console.log("");
|
|
43684
|
+
console.log(source_default.white(" Or enter this code manually: ") + source_default.yellow.bold(userCode));
|
|
43685
|
+
console.log("");
|
|
43663
43686
|
console.log(source_default.dim(" Waiting for browser authorization..."));
|
|
43664
43687
|
console.log(source_default.dim(" (This will timeout after 5 minutes)"));
|
|
43665
43688
|
console.log("");
|
|
@@ -46587,6 +46610,7 @@ async function showFeatureExploration() {
|
|
|
46587
46610
|
console.log(source_default.dim(" oculum watch . ") + source_default.white("Watch for changes"));
|
|
46588
46611
|
console.log(source_default.dim(" oculum ui ") + source_default.white("Interactive mode"));
|
|
46589
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"));
|
|
46590
46614
|
console.log(source_default.dim(" oculum --help ") + source_default.white("Show all commands\n"));
|
|
46591
46615
|
await ve({
|
|
46592
46616
|
message: "Press Enter to continue",
|
|
@@ -47677,6 +47701,120 @@ var uiCommand = new Command("ui").description("Interactive terminal UI").action(
|
|
|
47677
47701
|
await runUI();
|
|
47678
47702
|
});
|
|
47679
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
|
+
|
|
47680
47818
|
// src/index.ts
|
|
47681
47819
|
var program2 = new Command();
|
|
47682
47820
|
program2.name("oculum").description("AI-native security scanner for detecting vulnerabilities in LLM-generated code").version("1.0.0").addHelpText("after", `
|
|
@@ -47691,6 +47829,7 @@ Common Commands:
|
|
|
47691
47829
|
ui Interactive terminal UI
|
|
47692
47830
|
login Authenticate with Oculum
|
|
47693
47831
|
status Check authentication status
|
|
47832
|
+
usage View credits and quota
|
|
47694
47833
|
|
|
47695
47834
|
Learn More:
|
|
47696
47835
|
$ oculum scan --help Detailed scan options
|
|
@@ -47703,6 +47842,7 @@ program2.addCommand(authCommand);
|
|
|
47703
47842
|
program2.addCommand(loginCommand);
|
|
47704
47843
|
program2.addCommand(statusCommand);
|
|
47705
47844
|
program2.addCommand(upgradeCommand);
|
|
47845
|
+
program2.addCommand(usageCommand);
|
|
47706
47846
|
program2.addCommand(watchCommand);
|
|
47707
47847
|
program2.addCommand(uiCommand);
|
|
47708
47848
|
program2.parse();
|
package/package.json
CHANGED