seaworthycode 1.2.3 → 1.2.4

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 CHANGED
@@ -6,10 +6,10 @@ import { existsSync as existsSync6 } from "fs";
6
6
  import { dirname as dirname7, resolve as resolve4 } from "path";
7
7
  import { fileURLToPath as fileURLToPath3 } from "url";
8
8
  import { cac } from "cac";
9
- import { createRequire as createRequire3 } from "module";
9
+ import { createRequire as createRequire4 } from "module";
10
10
 
11
11
  // src/cli-action.ts
12
- import { stat, access as access2 } from "fs/promises";
12
+ import { stat, access as access2, appendFile, mkdir } from "fs/promises";
13
13
  import { resolve as resolve3 } from "path";
14
14
  import { homedir as homedir3 } from "os";
15
15
  import { join as join15 } from "path";
@@ -93,6 +93,7 @@ var CACHE_DIR = join(CONFIG_DIR, "cache");
93
93
  var AST_CACHE_DIR = join(CACHE_DIR, "ast");
94
94
  var DEP_CACHE_DIR = join(CACHE_DIR, "deps");
95
95
  var LLM_CACHE_DIR = join(CACHE_DIR, "llm");
96
+ var LOG_DIR = join(CONFIG_DIR, "logs");
96
97
  var VALID_CATEGORIES = /* @__PURE__ */ new Set([
97
98
  "security",
98
99
  "resilience",
@@ -400,11 +401,11 @@ async function crawl(options) {
400
401
  const ext2 = entry.name.includes(".") ? entry.name.slice(entry.name.lastIndexOf(".")) : "";
401
402
  if (SKIP_EXTENSIONS.has(ext2)) continue;
402
403
  try {
403
- const stat22 = await fs.stat(fullPath);
404
- if (!stat22.isFile()) continue;
405
- if (stat22.size > maxFileSize) continue;
406
- const { isBinary: isBinary2, isGenerated: isGenerated2 } = await peekFileHead(fullPath);
407
- if (isBinary2 || isGenerated2) continue;
404
+ const stat2 = await fs.stat(fullPath);
405
+ if (!stat2.isFile()) continue;
406
+ if (stat2.size > maxFileSize) continue;
407
+ const { isBinary, isGenerated } = await peekFileHead(fullPath);
408
+ if (isBinary || isGenerated) continue;
408
409
  if (await shouldSkipMinifiedJs(fullPath, entry.name)) continue;
409
410
  files.push({
410
411
  path: fullPath,
@@ -420,17 +421,20 @@ async function crawl(options) {
420
421
  if (ig.ignores(relPath)) continue;
421
422
  const ext = entry.name.includes(".") ? entry.name.slice(entry.name.lastIndexOf(".")) : "";
422
423
  if (SKIP_EXTENSIONS.has(ext)) continue;
423
- const stat2 = await fs.stat(fullPath);
424
- if (stat2.size > maxFileSize) continue;
425
- const { isBinary, isGenerated } = await peekFileHead(fullPath);
426
- if (isBinary || isGenerated) continue;
427
- if (await shouldSkipMinifiedJs(fullPath, entry.name)) continue;
428
- files.push({
429
- path: fullPath,
430
- relativePath: relPath,
431
- content: () => fs.readFile(fullPath, "utf-8"),
432
- language: inferLanguage(entry.name)
433
- });
424
+ try {
425
+ const stat2 = await fs.stat(fullPath);
426
+ if (stat2.size > maxFileSize) continue;
427
+ const { isBinary, isGenerated } = await peekFileHead(fullPath);
428
+ if (isBinary || isGenerated) continue;
429
+ if (await shouldSkipMinifiedJs(fullPath, entry.name)) continue;
430
+ files.push({
431
+ path: fullPath,
432
+ relativePath: relPath,
433
+ content: () => fs.readFile(fullPath, "utf-8"),
434
+ language: inferLanguage(entry.name)
435
+ });
436
+ } catch {
437
+ }
434
438
  }
435
439
  }
436
440
  await walk(targetDir);
@@ -779,10 +783,12 @@ var copy = {
779
783
  "report.tierWatermark": (licensedTo) => `\u2693 Pro \xB7 ${licensedTo}`,
780
784
  "report.scanProgress.starting": (targetDir) => `\u2693 Scanning ${targetDir}...`,
781
785
  "report.scanProgress.category": (label, count) => count > 0 ? ` \u2693 ${label} \u2014 ${count} issue(s)` : ` \u2693 ${label} \u2713`,
786
+ "report.licenseNotice": (licensedTo) => `Licensed to ${licensedTo}`,
782
787
  "report.cta.clean": `\u2713 All clear \u2014 your project is shipshape!`,
783
788
  "report.cta.hasFindings": `Fix these before you ship. ${SITE_URL}/docs`,
784
789
  "report.cta.critical": `\u26A0 Critical issues found \u2014 patch before you deploy. ${SITE_URL}/docs`,
785
790
  "report.proUpsellNamed": (names, count) => `\u2693 Pro unlocks ${count} more check${count === 1 ? "" : "s"} (${names}). ${SITE_URL}`,
791
+ "report.preScanNotice": (count) => `\u2693 Pro unlocks ${count} additional checks. Upgrade at ${SITE_URL}`,
786
792
  "report.llmPrivacyWarning": "Pro checks will send code context to your LLM provider via your API key. No data is sent to Seaworthy servers.",
787
793
  "report.confidenceHigh": "high confidence",
788
794
  "report.confidenceMedium": "medium confidence",
@@ -7655,7 +7661,7 @@ registry.register({
7655
7661
  }
7656
7662
  });
7657
7663
  var CHECK_ID10 = "security.xss-surface";
7658
- var CGI_PATH_HINT = /(^|[\/_-])cgi([\/_.-]|$)/i;
7664
+ var CGI_PATH_HINT = new RegExp("(^|[/_-])cgi([/_.-]|$)", "i");
7659
7665
  function isLikelyCgiBashScript(file) {
7660
7666
  return CGI_PATH_HINT.test(file.relativePath);
7661
7667
  }
@@ -15842,7 +15848,7 @@ var copy3 = {
15842
15848
  "cli.setup.apiKeyPrompt": "API key for",
15843
15849
  "cli.setup.modelPrompt": "Model (press Enter for default)",
15844
15850
  "cli.setup.baseUrlPrompt": "Custom API base URL (press Enter to use default)",
15845
- "cli.setup.done": "Config saved. Run `seaworthy .` to scan your project.",
15851
+ "cli.setup.done": "Config saved. Run `npx seaworthycode .` to scan your project.",
15846
15852
  "cli.setup.skipped": "Setup skipped. Running free-tier scan.",
15847
15853
  "cli.firstRun": "First time? Let's get you set up.",
15848
15854
  "cli.category.security": "Security",
@@ -15868,7 +15874,19 @@ var copy3 = {
15868
15874
  "cli.error.config.unknownKey": "Unknown config key. Valid keys: licenseKey, llmProvider, llmApiKey, llmModel, llmApiUrl",
15869
15875
  "cli.error.config.invalidProvider": "Invalid provider. Valid providers: anthropic, openai, kimi, moonshot, deepseek, gemini, ollama, openrouter",
15870
15876
  "cli.error.config.invalidAction": "Invalid config action. Usage: seaworthy config <set|get|list>",
15871
- "cli.config.setSuccess": (key) => `${key} updated.`
15877
+ "cli.config.setSuccess": (key) => `${key} updated.`,
15878
+ "cli.licenseNotice": (licensedTo) => `Licensed to ${licensedTo}`,
15879
+ "cli.error.debugHint": (logPath) => `Re-run with --debug to write a diagnostic log: ${logPath}
15880
+ Report persistent issues: npx seaworthycode bugreport`,
15881
+ "cli.bugreport.intro": "Seaworthy Bug Report \u2014 send an anonymized diagnostic to the Seaworthy team.",
15882
+ "cli.bugreport.noLog": (logFile) => `No diagnostic log found at ${logFile}.
15883
+ Re-run the failing command with --debug first, then run bugreport again.`,
15884
+ "cli.bugreport.preview": "The following diagnostic data will be sent (home directory paths have been anonymized):",
15885
+ "cli.bugreport.confirm": "Send this report to the Seaworthy team?",
15886
+ "cli.bugreport.cancelled": "Cancelled \u2014 no data was sent.",
15887
+ "cli.bugreport.sent": (eventId) => `Report sent. Reference ID: ${eventId}
15888
+ Mention this ID when contacting support at ${SITE_URL}/support`,
15889
+ "cli.bugreport.failed": "Could not reach the error reporting service. Check your connection and try again."
15872
15890
  };
15873
15891
  function getCopy2(key, ...args) {
15874
15892
  const value = copy3[key];
@@ -15893,7 +15911,7 @@ function createCommand(definition, options) {
15893
15911
  return {
15894
15912
  ok: false,
15895
15913
  code: "errors.unexpected",
15896
- detail: err instanceof Error ? err.message : String(err)
15914
+ detail: err instanceof Error ? err.stack ?? err.message : String(err)
15897
15915
  };
15898
15916
  }
15899
15917
  }
@@ -15955,6 +15973,11 @@ async function executeScan(args, ctx) {
15955
15973
  }
15956
15974
  const showProgress = args.format === "terminal" && !args.sarifStdout;
15957
15975
  if (showProgress) {
15976
+ if (ctx.tier === "free") {
15977
+ const skippedCount = registry.getSkippedChecks("free").length;
15978
+ process.stderr.write(getCopy("report.preScanNotice", skippedCount) + "\n");
15979
+ process.stderr.write(getCopy2("cli.licenseNotice", ctx.licensedTo ?? "free tier") + "\n");
15980
+ }
15958
15981
  process.stderr.write(getCopy("report.scanProgress.starting", args.targetDir) + "\n");
15959
15982
  }
15960
15983
  const runner = new ScanRunner();
@@ -16340,7 +16363,21 @@ async function runCli(options) {
16340
16363
  return { code: resolveExitCode("scan.failed"), error: message };
16341
16364
  }
16342
16365
  if (!result.ok) {
16343
- const message = resolveErrorMessage(result.code);
16366
+ let message = resolveErrorMessage(result.code);
16367
+ if (result.code === "errors.unexpected") {
16368
+ const logFile = join15(LOG_DIR, "seaworthy.log");
16369
+ message += "\n" + getCopy2("cli.error.debugHint", logFile);
16370
+ if (options.debug && result.detail) {
16371
+ try {
16372
+ await mkdir(LOG_DIR, { recursive: true });
16373
+ await appendFile(logFile, `[${(/* @__PURE__ */ new Date()).toISOString()}] unexpected error
16374
+ ${result.detail}
16375
+
16376
+ `);
16377
+ } catch {
16378
+ }
16379
+ }
16380
+ }
16344
16381
  return { code: resolveExitCode(result.code), error: message };
16345
16382
  }
16346
16383
  const remaining = Math.max(0, 3e3 - (Date.now() - updateStart));
@@ -16405,13 +16442,89 @@ var configCommand = createCommand(
16405
16442
  { requireLicense: false }
16406
16443
  );
16407
16444
 
16445
+ // src/commands/bugreport.ts
16446
+ import { readFile as readFile2 } from "fs/promises";
16447
+ import { randomUUID } from "crypto";
16448
+ import { platform, arch, homedir as homedir4 } from "os";
16449
+ import { join as join16 } from "path";
16450
+ import { createRequire as createRequire3 } from "module";
16451
+ import { intro as intro2, outro as outro2, confirm, isCancel as isCancel2 } from "@clack/prompts";
16452
+ var _require2 = createRequire3(import.meta.url);
16453
+ var { version: CLI_VERSION2 } = _require2("../../package.json");
16454
+ var LOG_FILE = join16(LOG_DIR, "seaworthy.log");
16455
+ var SENTRY_PROJECT_ID = "4511394796666960";
16456
+ var SENTRY_INGEST = "https://o4508597942026240.ingest.de.sentry.io";
16457
+ var SENTRY_KEY = "3c8dc912769032c73a306cb571805658";
16458
+ function sanitize(text3) {
16459
+ return text3.split(homedir4()).join("<home>");
16460
+ }
16461
+ async function sendReport(log) {
16462
+ const eventId = randomUUID().replace(/-/g, "");
16463
+ const payload = {
16464
+ event_id: eventId,
16465
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
16466
+ platform: "node",
16467
+ level: "error",
16468
+ environment: "cli",
16469
+ release: CLI_VERSION2,
16470
+ message: log.split("\n")[0] ?? "Unexpected error",
16471
+ extra: { log },
16472
+ contexts: {
16473
+ runtime: { name: "node", version: process.version },
16474
+ os: { name: platform(), arch: arch() }
16475
+ },
16476
+ tags: { source: "cli-bugreport" }
16477
+ };
16478
+ try {
16479
+ const res = await fetch(`${SENTRY_INGEST}/api/${SENTRY_PROJECT_ID}/store/`, {
16480
+ method: "POST",
16481
+ headers: {
16482
+ "Content-Type": "application/json",
16483
+ "X-Sentry-Auth": `Sentry sentry_version=7, sentry_key=${SENTRY_KEY}, sentry_client=seaworthycode/${CLI_VERSION2}`
16484
+ },
16485
+ body: JSON.stringify(payload),
16486
+ signal: AbortSignal.timeout(1e4)
16487
+ });
16488
+ return res.ok ? eventId : null;
16489
+ } catch {
16490
+ return null;
16491
+ }
16492
+ }
16493
+ async function executeBugReport() {
16494
+ intro2(getCopy2("cli.bugreport.intro"));
16495
+ let log;
16496
+ try {
16497
+ const raw = await readFile2(LOG_FILE, "utf-8");
16498
+ if (!raw.trim()) throw new Error("empty");
16499
+ log = sanitize(raw.length > 8192 ? raw.slice(-8192) : raw);
16500
+ } catch {
16501
+ return { ok: true, output: getCopy2("cli.bugreport.noLog", LOG_FILE) };
16502
+ }
16503
+ process.stderr.write(getCopy2("cli.bugreport.preview") + "\n---\n" + log + "---\n\n");
16504
+ const confirmed = await confirm({ message: getCopy2("cli.bugreport.confirm") });
16505
+ if (isCancel2(confirmed) || !confirmed) {
16506
+ outro2(getCopy2("cli.bugreport.cancelled"));
16507
+ return { ok: true };
16508
+ }
16509
+ const eventId = await sendReport(log);
16510
+ outro2(eventId ? getCopy2("cli.bugreport.sent", eventId) : getCopy2("cli.bugreport.failed"));
16511
+ return { ok: true };
16512
+ }
16513
+ var bugReportCommand = createCommand(
16514
+ {
16515
+ name: "bugreport",
16516
+ description: "Send an anonymized bug report to the Seaworthy team",
16517
+ handler: async () => executeBugReport()
16518
+ },
16519
+ { requireLicense: false }
16520
+ );
16521
+
16408
16522
  // src/index.ts
16409
- loadEnv({ path: resolve4(process.cwd(), ".env") });
16410
16523
  var repoRootEnv = resolve4(dirname7(fileURLToPath3(import.meta.url)), "../../..", ".env");
16411
16524
  if (existsSync6(repoRootEnv)) {
16412
16525
  loadEnv({ path: repoRootEnv, override: false });
16413
16526
  }
16414
- var require5 = createRequire3(import.meta.url);
16527
+ var require5 = createRequire4(import.meta.url);
16415
16528
  var { version } = require5("../package.json");
16416
16529
  var cli = cac("seaworthy");
16417
16530
  cli.version(version);
@@ -16451,6 +16564,16 @@ cli.command("setup", "Interactive setup for license key and LLM provider").actio
16451
16564
  process.exit(2);
16452
16565
  }
16453
16566
  });
16567
+ cli.command("bugreport", "Send an anonymized bug report to the Seaworthy team").action(async () => {
16568
+ const result = await bugReportCommand.handler({ action: "run" }, { tier: "free", licensedTo: "free tier" });
16569
+ if (!result.ok) {
16570
+ console.error(resolveErrorMessage(result.code));
16571
+ process.exit(2);
16572
+ }
16573
+ if (result.output) {
16574
+ console.log(result.output);
16575
+ }
16576
+ });
16454
16577
  cli.help(() => {
16455
16578
  return [
16456
16579
  {
@@ -16463,7 +16586,8 @@ cli.help(() => {
16463
16586
  },
16464
16587
  {
16465
16588
  title: "Commands",
16466
- body: ` npx seaworthycode setup Interactive setup for license key and LLM provider`
16589
+ body: ` npx seaworthycode setup Interactive setup for license key and LLM provider
16590
+ npx seaworthycode bugreport Send an anonymized bug report to the Seaworthy team`
16467
16591
  },
16468
16592
  {
16469
16593
  title: "Options",