@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.
Files changed (2) hide show
  1. package/dist/index.js +169 -29
  2. 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((usage) => usage.context === "api_boundary" || usage.context === "database_layer" || usage.context === "auth_handler");
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 usage of cappedAny) {
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}-${usage.lineNumber}`,
17718
+ id: `ai-fingerprint-any-${filePath}-${usage2.lineNumber}`,
17719
17719
  filePath,
17720
- lineNumber: usage.lineNumber,
17721
- lineContent: usage.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[usage.context] || usage.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[usage.context] || usage.context}.`,
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 (usage) {
29416
- total.completion_tokens += usage.completion_tokens;
29417
- total.prompt_tokens += usage.prompt_tokens;
29418
- total.total_tokens += usage.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 usage = response.usage;
37005
- if (usage) {
37006
- const promptTokens = usage.prompt_tokens || 0;
37007
- const completionTokens = usage.completion_tokens || 0;
37008
- const cachedTokens = usage.prompt_tokens_details?.cached_tokens || 0;
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 usage = response.usage;
37230
- if (usage) {
37229
+ const usage2 = response.usage;
37230
+ if (usage2) {
37231
37231
  console.log(`[DEBUG] Batch ${batchNum} - Full API Response Usage:`);
37232
- console.log(JSON.stringify(usage, null, 2));
37232
+ console.log(JSON.stringify(usage2, null, 2));
37233
37233
  console.log(`[DEBUG] Breakdown:`);
37234
- console.log(` - input_tokens: ${usage.input_tokens || 0}`);
37235
- console.log(` - output_tokens: ${usage.output_tokens || 0}`);
37236
- console.log(` - cache_creation_input_tokens: ${usage.cache_creation_input_tokens || 0}`);
37237
- console.log(` - cache_read_input_tokens: ${usage.cache_read_input_tokens || 0}`);
37238
- stats.estimatedInputTokens += usage.input_tokens || 0;
37239
- stats.estimatedOutputTokens += usage.output_tokens || 0;
37240
- const cacheCreation = usage.cache_creation_input_tokens || 0;
37241
- const cacheRead = usage.cache_read_input_tokens || 0;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oculum/cli",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
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": {