@oculum/cli 1.0.0 → 1.0.1

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 +146 -57
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -42892,14 +42892,18 @@ function enhanceAPIError(error) {
42892
42892
  ]
42893
42893
  };
42894
42894
  case 429:
42895
+ const rateLimitInfo = error.reason === "quota_exceeded" ? "You've reached your monthly scan quota." : "Too many requests in a short period.";
42896
+ 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
42897
  return {
42896
- message: "Rate limit exceeded",
42897
- suggestion: "Wait a moment and try again. Upgrade to Pro for higher limits.",
42898
+ message: rateLimitInfo,
42899
+ suggestion: rateLimitSuggestion,
42898
42900
  category: "server",
42899
42901
  recoveryActions: [
42900
- { label: "Retry in 30 seconds", action: "retry" },
42901
- { label: "View pricing", action: "upgrade" }
42902
- ]
42902
+ { label: "Use free local scan", command: "oculum scan . --depth cheap", action: "fallback" },
42903
+ { label: "View usage & upgrade", action: "upgrade" },
42904
+ { label: "Retry in 30 seconds", action: "retry" }
42905
+ ],
42906
+ details: error.reason === "quota_exceeded" ? "Check your usage at https://oculum.dev/dashboard/usage" : void 0
42903
42907
  };
42904
42908
  case 500:
42905
42909
  case 502:
@@ -43443,11 +43447,17 @@ async function runScanOnce(targetPath, options) {
43443
43447
  const noColor = options.color === false;
43444
43448
  if ((options.depth === "validated" || options.depth === "deep") && !isAuthenticated()) {
43445
43449
  if (!options.quiet) {
43446
- console.log(source_default.yellow("\nNote: validated and deep scans require authentication."));
43447
- console.log(
43448
- source_default.dim("Run `oculum login` to authenticate, or use `--depth cheap` for free local scans.\n")
43449
- );
43450
- console.log(source_default.dim("Falling back to cheap scan...\n"));
43450
+ console.log("");
43451
+ console.log(source_default.yellow("\u26A0\uFE0F AI-powered scans require authentication"));
43452
+ console.log("");
43453
+ console.log(source_default.dim(" You requested: ") + source_default.white(options.depth) + source_default.dim(" scan"));
43454
+ console.log(source_default.dim(" This requires: ") + source_default.white("Pro subscription"));
43455
+ console.log("");
43456
+ console.log(source_default.dim(" Options:"));
43457
+ console.log(source_default.cyan(" \u2022 oculum login") + source_default.dim(" - Authenticate to unlock Pro features"));
43458
+ console.log(source_default.cyan(" \u2022 --depth cheap") + source_default.dim(" - Use free local pattern matching"));
43459
+ console.log("");
43460
+ console.log(source_default.dim(" Continuing with ") + source_default.green("cheap") + source_default.dim(" scan (free)...\n"));
43451
43461
  }
43452
43462
  options.depth = "cheap";
43453
43463
  }
@@ -43469,19 +43479,24 @@ async function runScanOnce(targetPath, options) {
43469
43479
  const onProgress = (progress) => {
43470
43480
  switch (progress.status) {
43471
43481
  case "layer1":
43472
- spinner.text = `Layer 1: Pattern matching... (${progress.vulnerabilitiesFound} findings)`;
43482
+ spinner.text = `Scanning patterns... ${source_default.dim(`(${progress.vulnerabilitiesFound} potential issues)`)}`;
43473
43483
  break;
43474
43484
  case "layer2":
43475
- spinner.text = `Layer 2: Structural analysis... (${progress.vulnerabilitiesFound} findings)`;
43485
+ spinner.text = `Analyzing code structure... ${source_default.dim(`(${progress.vulnerabilitiesFound} findings)`)}`;
43476
43486
  break;
43477
43487
  case "validating":
43478
- spinner.text = `AI validation... (${progress.vulnerabilitiesFound} candidates)`;
43488
+ spinner.text = `AI validating findings... ${source_default.dim(`(${progress.vulnerabilitiesFound} candidates)`)}`;
43479
43489
  break;
43480
43490
  case "layer3":
43481
- spinner.text = `Layer 3: AI semantic analysis...`;
43491
+ spinner.text = `Deep AI analysis in progress...`;
43482
43492
  break;
43483
43493
  case "complete":
43484
- spinner.succeed(`Scan complete: ${progress.vulnerabilitiesFound} issues found`);
43494
+ const issueText = progress.vulnerabilitiesFound === 1 ? "issue" : "issues";
43495
+ if (progress.vulnerabilitiesFound === 0) {
43496
+ spinner.succeed(source_default.green("Scan complete: No security issues found! \u2713"));
43497
+ } else {
43498
+ spinner.succeed(`Scan complete: ${progress.vulnerabilitiesFound} ${issueText} found`);
43499
+ }
43485
43500
  break;
43486
43501
  case "failed":
43487
43502
  spinner.fail(progress.message);
@@ -43614,23 +43629,41 @@ async function login(options) {
43614
43629
  const result = await verifyApiKey(options.apiKey);
43615
43630
  if (!result.valid) {
43616
43631
  spinner.fail("Invalid API key");
43632
+ console.log("");
43633
+ console.log(source_default.dim(" The API key could not be verified. Please check:"));
43634
+ console.log(source_default.dim(" \u2022 The key is copied correctly (no extra spaces)"));
43635
+ console.log(source_default.dim(" \u2022 The key has not expired"));
43636
+ console.log(source_default.dim(" \u2022 Generate a new key at: ") + source_default.cyan("https://oculum.dev/dashboard/api-keys"));
43637
+ console.log("");
43617
43638
  process.exit(1);
43618
43639
  }
43619
43640
  setAuthCredentials(options.apiKey, result.email, result.tier);
43620
43641
  spinner.succeed("Logged in successfully!");
43621
- console.log(source_default.dim(` Email: ${result.email}`));
43622
- console.log(source_default.dim(` Tier: ${result.tier}`));
43642
+ console.log("");
43643
+ console.log(source_default.dim(" Email: ") + source_default.white(result.email));
43644
+ console.log(source_default.dim(" Plan: ") + source_default.white(result.tier));
43645
+ console.log("");
43646
+ console.log(source_default.green(" You can now use AI-powered scans:"));
43647
+ console.log(source_default.cyan(" oculum scan . --depth validated"));
43648
+ console.log("");
43623
43649
  return;
43624
43650
  }
43625
43651
  try {
43652
+ console.log("");
43653
+ console.log(source_default.bold(" \u{1F510} Oculum Login"));
43654
+ console.log(source_default.dim(" " + "\u2500".repeat(38)));
43655
+ console.log("");
43626
43656
  spinner.start("Initiating login...");
43627
43657
  const { authUrl, deviceCode } = await initiateLogin();
43628
43658
  spinner.stop();
43629
- console.log("\nTo complete login, open this URL in your browser:");
43630
- console.log(source_default.cyan(`
43631
- ${authUrl}`));
43632
- console.log(source_default.dim("\nWaiting for you to authenticate..."));
43633
- spinner.start("Waiting for browser authorization...");
43659
+ console.log(source_default.white(" Open this URL in your browser to login:"));
43660
+ console.log("");
43661
+ console.log(source_default.cyan.bold(` ${authUrl}`));
43662
+ console.log("");
43663
+ console.log(source_default.dim(" Waiting for browser authorization..."));
43664
+ console.log(source_default.dim(" (This will timeout after 5 minutes)"));
43665
+ console.log("");
43666
+ spinner.start("Waiting for authorization...");
43634
43667
  const maxAttempts = 60;
43635
43668
  for (let i = 0; i < maxAttempts; i++) {
43636
43669
  await new Promise((resolve7) => setTimeout(resolve7, 5e3));
@@ -43638,15 +43671,35 @@ async function login(options) {
43638
43671
  if (result.complete) {
43639
43672
  setAuthCredentials(result.apiKey, result.email, result.tier);
43640
43673
  spinner.succeed("Logged in successfully!");
43641
- console.log(source_default.dim(` Email: ${result.email}`));
43642
- console.log(source_default.dim(` Tier: ${result.tier}`));
43674
+ console.log("");
43675
+ console.log(source_default.dim(" Email: ") + source_default.white(result.email));
43676
+ console.log(source_default.dim(" Plan: ") + source_default.white(result.tier));
43677
+ console.log("");
43678
+ console.log(source_default.green(" You can now use AI-powered scans:"));
43679
+ console.log(source_default.cyan(" oculum scan . --depth validated"));
43680
+ console.log("");
43643
43681
  return;
43644
43682
  }
43683
+ if (result.expired) {
43684
+ spinner.fail("Login session expired");
43685
+ console.log("");
43686
+ console.log(source_default.dim(" Please try again: ") + source_default.cyan("oculum login"));
43687
+ console.log("");
43688
+ process.exit(1);
43689
+ }
43645
43690
  }
43646
- spinner.fail("Login timed out. Please try again.");
43691
+ spinner.fail("Login timed out");
43692
+ console.log("");
43693
+ console.log(source_default.dim(" The login session expired. Please try again:"));
43694
+ console.log(source_default.cyan(" oculum login"));
43695
+ console.log("");
43647
43696
  process.exit(1);
43648
43697
  } catch (err) {
43649
- spinner.fail(`Login failed: ${err}`);
43698
+ spinner.fail("Login failed");
43699
+ console.log("");
43700
+ console.log(source_default.dim(" Error: ") + source_default.red(String(err)));
43701
+ console.log(source_default.dim(" Please check your internet connection and try again."));
43702
+ console.log("");
43650
43703
  process.exit(1);
43651
43704
  }
43652
43705
  }
@@ -43660,40 +43713,67 @@ function logout() {
43660
43713
  }
43661
43714
  async function status() {
43662
43715
  const config = getConfig();
43663
- console.log("\n" + source_default.bold("Oculum CLI Status"));
43664
- console.log(source_default.dim("\u2500".repeat(40)));
43716
+ console.log("");
43717
+ console.log(source_default.bold(" \u{1F510} Oculum Authentication Status"));
43718
+ console.log(source_default.dim(" " + "\u2500".repeat(38)));
43665
43719
  if (!isAuthenticated()) {
43666
- console.log(source_default.yellow("\nNot logged in."));
43667
- console.log(source_default.dim("\nRun `oculum login` to authenticate."));
43668
- console.log(source_default.dim("Free tier allows cheap (local) scans only."));
43669
- console.log(source_default.dim("Paid tiers unlock AI-powered validated and deep scans.\n"));
43720
+ console.log("");
43721
+ console.log(source_default.yellow(" Status: ") + source_default.white("Not logged in"));
43722
+ console.log("");
43723
+ console.log(source_default.dim(" You can use Oculum without logging in for free local scans."));
43724
+ console.log(source_default.dim(" Login to unlock AI-powered validation and deep analysis."));
43725
+ console.log("");
43726
+ console.log(source_default.bold(" Quick Start:"));
43727
+ console.log(source_default.cyan(" oculum scan .") + source_default.dim(" Free pattern-based scan"));
43728
+ console.log(source_default.cyan(" oculum login") + source_default.dim(" Authenticate for Pro features"));
43729
+ console.log("");
43670
43730
  return;
43671
43731
  }
43672
- const spinner = ora("Verifying credentials...").start();
43732
+ const spinner = ora(" Verifying credentials...").start();
43673
43733
  const result = await verifyApiKey(config.apiKey);
43674
43734
  if (!result.valid) {
43675
- spinner.fail("Stored credentials are invalid or expired.");
43676
- console.log(source_default.dim("Run `oculum login` to re-authenticate."));
43735
+ spinner.fail(" Stored credentials are invalid or expired");
43736
+ console.log("");
43737
+ console.log(source_default.dim(" Your session may have expired. Please login again:"));
43738
+ console.log(source_default.cyan(" oculum login"));
43739
+ console.log("");
43677
43740
  return;
43678
43741
  }
43679
- spinner.succeed("Authenticated");
43680
- console.log(source_default.dim(` Email: ${result.email || config.email}`));
43681
- console.log(source_default.dim(` Tier: ${result.tier || config.tier}`));
43682
- console.log("\n" + source_default.bold("Available Scan Depths:"));
43742
+ spinner.succeed(" Authenticated");
43743
+ console.log("");
43744
+ const email = result.email || config.email || "unknown";
43683
43745
  const tier = result.tier || config.tier || "free";
43684
- console.log(source_default.green(" cheap - Fast pattern matching (always available)"));
43746
+ const tierBadge = tier === "pro" ? source_default.bgBlue.white(" PRO ") : tier === "enterprise" ? source_default.bgMagenta.white(" ENTERPRISE ") : source_default.bgGray.white(" FREE ");
43747
+ console.log(source_default.dim(" Email: ") + source_default.white(email));
43748
+ console.log(source_default.dim(" Plan: ") + tierBadge);
43749
+ console.log("");
43750
+ console.log(source_default.bold(" Available Scan Depths:"));
43751
+ console.log("");
43752
+ console.log(source_default.green(" \u2713 ") + source_default.white("cheap") + source_default.dim(" Fast pattern matching (always free)"));
43685
43753
  if (tier === "pro" || tier === "enterprise") {
43686
- console.log(source_default.green(" validated - AI-powered validation"));
43687
- console.log(source_default.green(" deep - Full AI semantic analysis"));
43754
+ console.log(source_default.green(" \u2713 ") + source_default.white("validated") + source_default.dim(" AI validation (~70% fewer false positives)"));
43755
+ console.log(source_default.dim(" \u{1F512} ") + source_default.white("deep") + source_default.dim(" Multi-agent analysis (coming soon)"));
43688
43756
  } else {
43689
- console.log(source_default.dim(" validated - AI-powered validation (requires Pro)"));
43690
- console.log(source_default.dim(" deep - Full AI semantic analysis (requires Pro)"));
43757
+ console.log(source_default.dim(" \u{1F512} ") + source_default.white("validated") + source_default.dim(" AI validation (requires Pro)"));
43758
+ console.log(source_default.dim(" \u{1F512} ") + source_default.white("deep") + source_default.dim(" Multi-agent analysis (requires Pro)"));
43691
43759
  }
43692
43760
  console.log("");
43761
+ console.log(source_default.dim(" Manage subscription: ") + source_default.cyan("https://oculum.dev/billing"));
43762
+ console.log("");
43693
43763
  }
43694
43764
  function upgrade() {
43695
- console.log(source_default.cyan("\nTo upgrade your subscription, visit:"));
43696
- console.log(source_default.bold(" https://oculum.dev/billing\n"));
43765
+ console.log("");
43766
+ console.log(source_default.bold(" \u{1F680} Upgrade to Oculum Pro"));
43767
+ console.log(source_default.dim(" " + "\u2500".repeat(38)));
43768
+ console.log("");
43769
+ console.log(source_default.white(" Pro features include:"));
43770
+ console.log(source_default.green(" \u2713 ") + source_default.white("AI-validated scans") + source_default.dim(" - ~70% fewer false positives"));
43771
+ console.log(source_default.green(" \u2713 ") + source_default.white("Higher scan limits") + source_default.dim(" - More scans per month"));
43772
+ console.log(source_default.green(" \u2713 ") + source_default.white("Priority support") + source_default.dim(" - Get help when you need it"));
43773
+ console.log(source_default.green(" \u2713 ") + source_default.white("GitHub Action") + source_default.dim(" - Automated PR scanning"));
43774
+ console.log("");
43775
+ console.log(source_default.dim(" Visit: ") + source_default.cyan.underline("https://oculum.dev/pricing"));
43776
+ console.log("");
43697
43777
  }
43698
43778
  var authCommand = new Command("auth").description("Manage authentication");
43699
43779
  authCommand.command("login").description("Log in to Oculum").option("-k, --api-key <key>", "API key (skip browser auth)").action(login);
@@ -45454,18 +45534,23 @@ async function watch2(targetPath, options) {
45454
45534
  const config = getConfig();
45455
45535
  if ((options.depth === "validated" || options.depth === "deep") && !isAuthenticated()) {
45456
45536
  if (!options.quiet) {
45457
- console.log(source_default.yellow("\nNote: validated and deep scans require authentication."));
45458
- console.log(source_default.dim("Run `oculum login` to authenticate.\n"));
45537
+ console.log("");
45538
+ console.log(source_default.yellow("\u26A0\uFE0F AI-powered scans require authentication"));
45539
+ console.log(source_default.dim(" Using free local scan instead. Run `oculum login` to unlock Pro features."));
45459
45540
  }
45460
45541
  options.depth = "cheap";
45461
45542
  }
45462
45543
  if (!options.quiet) {
45463
- console.log(source_default.bold("\nOculum Watch Mode"));
45464
- console.log(source_default.dim("\u2500".repeat(40)));
45465
- console.log(source_default.dim(`Watching: ${absolutePath}`));
45466
- console.log(source_default.dim(`Depth: ${options.depth}`));
45467
- console.log(source_default.dim(`Debounce: ${options.debounce}ms`));
45468
- console.log(source_default.dim("\nPress Ctrl+C to stop\n"));
45544
+ console.log("");
45545
+ console.log(source_default.bold(" \u{1F441}\uFE0F Oculum Watch Mode"));
45546
+ console.log(source_default.dim(" " + "\u2500".repeat(38)));
45547
+ console.log("");
45548
+ console.log(source_default.dim(" Watching: ") + source_default.white(absolutePath));
45549
+ console.log(source_default.dim(" Depth: ") + source_default.white(options.depth === "cheap" ? "Quick (pattern matching)" : options.depth));
45550
+ console.log(source_default.dim(" Debounce: ") + source_default.white(`${options.debounce}ms`));
45551
+ console.log("");
45552
+ console.log(source_default.dim(" Press ") + source_default.white("Ctrl+C") + source_default.dim(" to stop watching"));
45553
+ console.log("");
45469
45554
  }
45470
45555
  const changedFiles = /* @__PURE__ */ new Set();
45471
45556
  let isScanning = false;
@@ -45478,8 +45563,9 @@ async function watch2(targetPath, options) {
45478
45563
  console.clear();
45479
45564
  }
45480
45565
  if (!options.quiet) {
45481
- console.log(source_default.cyan(`
45482
- [${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] Scanning ${filesToScan.length} changed file(s)...`));
45566
+ const fileText = filesToScan.length === 1 ? "file" : "files";
45567
+ console.log("");
45568
+ console.log(source_default.cyan(` \u27F3 [${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] Scanning ${filesToScan.length} changed ${fileText}...`));
45483
45569
  }
45484
45570
  const scanFiles = [];
45485
45571
  for (const filePath of filesToScan) {
@@ -45509,9 +45595,12 @@ async function watch2(targetPath, options) {
45509
45595
  );
45510
45596
  if (result.vulnerabilities.length === 0) {
45511
45597
  if (!options.quiet) {
45512
- console.log(source_default.green("No issues found."));
45598
+ console.log(source_default.green(" \u2713 No issues found"));
45513
45599
  }
45514
45600
  } else {
45601
+ const issueCount = result.vulnerabilities.length;
45602
+ const issueText = issueCount === 1 ? "issue" : "issues";
45603
+ console.log(source_default.yellow(` \u26A0 Found ${issueCount} ${issueText}:`));
45515
45604
  console.log((0, import_formatters2.formatTerminalOutput)(result, {
45516
45605
  maxFindingsPerGroup: 5
45517
45606
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oculum/cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
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": {