seaworthycode 1.2.7 → 1.2.9

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
@@ -782,6 +782,7 @@ var copy = {
782
782
  "report.bySeverity": "By severity",
783
783
  "report.tierWatermark": (licensedTo) => `\u2693 Pro \xB7 ${licensedTo}`,
784
784
  "report.scanProgress.starting": (targetDir) => `\u2693 Scanning ${targetDir}...`,
785
+ "report.scanProgress.checking": (completed, total) => `\u2693 Checking... [${completed}/${total}]`,
785
786
  "report.scanProgress.category": (label, count) => count > 0 ? ` \u2693 ${label} \u2014 ${count} issue(s)` : ` \u2693 ${label} \u2713`,
786
787
  "report.licenseNotice": (licensedTo) => `Licensed to ${licensedTo}`,
787
788
  "report.cta.clean": `\u2713 All clear \u2014 your project is shipshape!`,
@@ -1330,7 +1331,7 @@ var copy = {
1330
1331
  "debug.cache_write_failed": (key) => `Cache write failed for key ${key}, skipping.`,
1331
1332
  // CLI
1332
1333
  "cli.help.description": "Seaworthy \u2014 static analysis for vibe coders",
1333
- "cli.help.usage": "Usage: seaworthy [path] [options]",
1334
+ "cli.help.usage": "Usage: seaworthycode [path] [options]",
1334
1335
  "cli.help.options": "Options",
1335
1336
  "cli.scanning": "Scanning...",
1336
1337
  "cli.reportWritten": (path) => `Report written to ${path}`,
@@ -1621,6 +1622,12 @@ var ScanRunner = class {
1621
1622
  };
1622
1623
  const results = [];
1623
1624
  const checkResults = /* @__PURE__ */ new Map();
1625
+ const categoryTotals = /* @__PURE__ */ new Map();
1626
+ for (const check2 of checks) {
1627
+ categoryTotals.set(check2.category, (categoryTotals.get(check2.category) ?? 0) + 1);
1628
+ }
1629
+ const categoryCompleted = /* @__PURE__ */ new Map();
1630
+ let completedCount = 0;
1624
1631
  await runWithConcurrency(
1625
1632
  checks,
1626
1633
  async (check2) => {
@@ -1630,26 +1637,26 @@ var ScanRunner = class {
1630
1637
  } catch (err) {
1631
1638
  console.error(`Check "${check2.id}" failed: ${String(err)}`);
1632
1639
  }
1640
+ options.onCheckComplete?.(++completedCount, checks.length);
1641
+ if (options.onCategoryComplete) {
1642
+ const catDone = (categoryCompleted.get(check2.category) ?? 0) + 1;
1643
+ categoryCompleted.set(check2.category, catDone);
1644
+ if (catDone === categoryTotals.get(check2.category)) {
1645
+ let catFindingsCount = 0;
1646
+ for (const c of checks) {
1647
+ if (c.category === check2.category) {
1648
+ catFindingsCount += (checkResults.get(c.id) ?? []).length;
1649
+ }
1650
+ }
1651
+ options.onCategoryComplete(check2.category, catFindingsCount);
1652
+ }
1653
+ }
1633
1654
  },
1634
1655
  concurrency
1635
1656
  );
1636
1657
  for (const check2 of checks) {
1637
1658
  results.push(...checkResults.get(check2.id) ?? []);
1638
1659
  }
1639
- if (options.onCategoryComplete) {
1640
- const findingsPerCategory = /* @__PURE__ */ new Map();
1641
- for (const check2 of checks) {
1642
- const count = (checkResults.get(check2.id) ?? []).length;
1643
- findingsPerCategory.set(check2.category, (findingsPerCategory.get(check2.category) ?? 0) + count);
1644
- }
1645
- const seen = /* @__PURE__ */ new Set();
1646
- for (const check2 of checks) {
1647
- if (!seen.has(check2.category)) {
1648
- seen.add(check2.category);
1649
- options.onCategoryComplete(check2.category, findingsPerCategory.get(check2.category) ?? 0);
1650
- }
1651
- }
1652
- }
1653
1660
  ctx.astCache?.clear();
1654
1661
  for (const [, promise] of ctx.bashAstCache ?? []) {
1655
1662
  try {
@@ -15914,7 +15921,7 @@ import { dirname as dirname5, resolve as resolve2, relative as relative7, isAbso
15914
15921
  // src/copy/index.ts
15915
15922
  var copy3 = {
15916
15923
  "cli.help.description": "Seaworthy \u2014 static analysis for vibe coders",
15917
- "cli.help.usage": "Usage: seaworthy [path] [options]",
15924
+ "cli.help.usage": "Usage: seaworthycode [path] [options]",
15918
15925
  "cli.help.options": "Options",
15919
15926
  "cli.help.learnMore": `Documentation: ${SITE_URL}/docs`,
15920
15927
  "cli.setup.intro": "Welcome to Seaworthy! Let's get you set up.",
@@ -15943,14 +15950,14 @@ var copy3 = {
15943
15950
  "cli.help.flag.sarifStdout": "Write SARIF report to stdout",
15944
15951
  "cli.sarif.mutually_exclusive": "Flags --sarif and --sarif-stdout cannot be used together.",
15945
15952
  "cli.sarif.stdout_conflict": "Flags --sarif-stdout and --json cannot be used together. Use --output to send JSON to a file.",
15946
- "cli.error.llmRequired": "LLM API key required for Pro checks. Set it with seaworthy config or the SEAWORTHY_LLM_API_KEY environment variable.",
15947
- "cli.warn.llmSkipped": "Pro checks skipped: no LLM API key configured. Set it with seaworthy config or the SEAWORTHY_LLM_API_KEY environment variable to enable semantic analysis.",
15953
+ "cli.error.llmRequired": "LLM API key required for Pro checks. Set it with seaworthycode config or the SEAWORTHY_LLM_API_KEY environment variable.",
15954
+ "cli.warn.llmSkipped": "Pro checks skipped: no LLM API key configured. Set it with seaworthycode config or the SEAWORTHY_LLM_API_KEY environment variable to enable semantic analysis.",
15948
15955
  "cli.warn.licenseInvalid": "Your license key is invalid or has been revoked. Running in free tier.\nTo re-enter your key, run: npx seaworthycode setup",
15949
- "cli.error.config.missingKey": "Missing config key. Usage: seaworthy config <set|get> <key>",
15950
- "cli.error.config.missingValue": "Missing config value. Usage: seaworthy config set <key> <value>",
15956
+ "cli.error.config.missingKey": "Missing config key. Usage: seaworthycode config <set|get> <key>",
15957
+ "cli.error.config.missingValue": "Missing config value. Usage: seaworthycode config set <key> <value>",
15951
15958
  "cli.error.config.unknownKey": "Unknown config key. Valid keys: licenseKey, llmProvider, llmApiKey, llmModel, llmApiUrl",
15952
15959
  "cli.error.config.invalidProvider": "Invalid provider. Valid providers: anthropic, openai, kimi, moonshot, deepseek, gemini, ollama, openrouter",
15953
- "cli.error.config.invalidAction": "Invalid config action. Usage: seaworthy config <set|get|list>",
15960
+ "cli.error.config.invalidAction": "Invalid config action. Usage: seaworthycode config <set|get|list>",
15954
15961
  "cli.config.setSuccess": (key) => `${key} updated.`,
15955
15962
  "cli.licenseNotice": (licensedTo) => `Licensed to ${licensedTo}`,
15956
15963
  "cli.error.debugHint": (logPath) => `Re-run with --debug to write a diagnostic log: ${logPath}
@@ -15996,9 +16003,9 @@ function createCommand(definition, options) {
15996
16003
  }
15997
16004
 
15998
16005
  // src/commands/scan.ts
15999
- var FREE_SCAN_TIMEOUT_MS = 6e4;
16000
- var PRO_SCAN_TIMEOUT_MS = 6e5;
16001
- function resolveScanTimeoutMs(tier) {
16006
+ var FREE_CHECK_IDLE_MS = 6e4;
16007
+ var PRO_CHECK_IDLE_MS = 3e5;
16008
+ function resolveCheckIdleMs(tier) {
16002
16009
  const env2 = process.env.SEAWORTHY_SCAN_TIMEOUT_MS;
16003
16010
  if (env2) {
16004
16011
  const parsed = Number(env2);
@@ -16006,7 +16013,7 @@ function resolveScanTimeoutMs(tier) {
16006
16013
  return parsed;
16007
16014
  }
16008
16015
  }
16009
- return tier === "free" ? FREE_SCAN_TIMEOUT_MS : PRO_SCAN_TIMEOUT_MS;
16016
+ return tier === "free" ? FREE_CHECK_IDLE_MS : PRO_CHECK_IDLE_MS;
16010
16017
  }
16011
16018
  var CATEGORY_LABELS = {
16012
16019
  security: getCopy2("cli.category.security"),
@@ -16016,17 +16023,29 @@ var CATEGORY_LABELS = {
16016
16023
  configuration: getCopy2("cli.category.configuration"),
16017
16024
  "data-exposure": getCopy2("cli.category.data-exposure")
16018
16025
  };
16019
- function withTimeout(promise, ms, message) {
16020
- return new Promise((resolve5, reject) => {
16021
- const timer = setTimeout(() => reject(new Error(message)), ms);
16022
- promise.then((value) => {
16023
- clearTimeout(timer);
16024
- resolve5(value);
16025
- }).catch((err) => {
16026
- clearTimeout(timer);
16027
- reject(err);
16028
- });
16026
+ function withIdleTimeout(promise, idleMs, message) {
16027
+ let timerId;
16028
+ let rejectFn = () => {
16029
+ };
16030
+ const heartbeat = () => {
16031
+ clearTimeout(timerId);
16032
+ timerId = setTimeout(() => rejectFn(new Error(message)), idleMs);
16033
+ };
16034
+ const wrappedPromise = new Promise((resolve5, reject) => {
16035
+ rejectFn = reject;
16036
+ heartbeat();
16037
+ promise.then(
16038
+ (value) => {
16039
+ clearTimeout(timerId);
16040
+ resolve5(value);
16041
+ },
16042
+ (err) => {
16043
+ clearTimeout(timerId);
16044
+ reject(err);
16045
+ }
16046
+ );
16029
16047
  });
16048
+ return { promise: wrappedPromise, heartbeat };
16030
16049
  }
16031
16050
  function isInsideDirectory(outputPath, targetDir) {
16032
16051
  const resolvedOutput = resolve2(outputPath);
@@ -16058,24 +16077,36 @@ async function executeScan(args, ctx) {
16058
16077
  process.stderr.write(getCopy("report.scanProgress.starting", args.targetDir) + "\n");
16059
16078
  }
16060
16079
  const runner = new ScanRunner();
16061
- const scanTimeoutMs = resolveScanTimeoutMs(ctx.tier);
16062
- const result = await withTimeout(
16063
- runner.run({
16064
- targetDir: args.targetDir,
16065
- tier: ctx.tier,
16066
- llm,
16067
- maxFileSize: args.maxFileSize,
16068
- debug: args.debug,
16069
- baseline: args.baseline,
16070
- minConfidence: args.minConfidence,
16071
- onCategoryComplete: showProgress ? (category, count) => {
16072
- const label = CATEGORY_LABELS[category] ?? category;
16073
- process.stderr.write(getCopy("report.scanProgress.category", label, count) + "\n");
16074
- } : void 0
16075
- }),
16076
- scanTimeoutMs,
16077
- getCopy("errors.scan.timed_out", Math.round(scanTimeoutMs / 1e3))
16080
+ const checkIdleMs = resolveCheckIdleMs(ctx.tier);
16081
+ let heartbeat = () => {
16082
+ };
16083
+ const scanPromise = runner.run({
16084
+ targetDir: args.targetDir,
16085
+ tier: ctx.tier,
16086
+ llm,
16087
+ maxFileSize: args.maxFileSize,
16088
+ debug: args.debug,
16089
+ baseline: args.baseline,
16090
+ minConfidence: args.minConfidence,
16091
+ onCheckComplete: (completed, total) => {
16092
+ heartbeat();
16093
+ if (showProgress) {
16094
+ process.stderr.write(`\r${getCopy("report.scanProgress.checking", completed, total)}\x1B[K`);
16095
+ }
16096
+ },
16097
+ onCategoryComplete: showProgress ? (category, count) => {
16098
+ const label = CATEGORY_LABELS[category] ?? category;
16099
+ process.stderr.write(`\r\x1B[K${getCopy("report.scanProgress.category", label, count)}
16100
+ `);
16101
+ } : void 0
16102
+ });
16103
+ const { promise: timedScanPromise, heartbeat: hb } = withIdleTimeout(
16104
+ scanPromise,
16105
+ checkIdleMs,
16106
+ getCopy("errors.scan.timed_out", Math.round(checkIdleMs / 1e3))
16078
16107
  );
16108
+ heartbeat = hb;
16109
+ const result = await timedScanPromise;
16079
16110
  const reportInput = {
16080
16111
  findings: result.findings,
16081
16112
  targetDir: args.targetDir,
@@ -16267,7 +16298,7 @@ var FsCredentialProvider = class {
16267
16298
  import { readFile } from "fs/promises";
16268
16299
  import { fileURLToPath as fileURLToPath2 } from "url";
16269
16300
  import { dirname as dirname6, join as join14 } from "path";
16270
- var NPM_REGISTRY_URL = "https://registry.npmjs.org/seaworthy";
16301
+ var NPM_REGISTRY_URL = "https://registry.npmjs.org/seaworthycode";
16271
16302
  var UPDATE_TIMEOUT_MS = 3e3;
16272
16303
  function isLowerVersion(current, latest) {
16273
16304
  const parse2 = (v) => v.split(".").map((n) => Number(n));
@@ -16300,7 +16331,7 @@ async function checkUpdate() {
16300
16331
  }
16301
16332
  const installed = await getInstalledVersion();
16302
16333
  if (isLowerVersion(installed, latest)) {
16303
- return `Update available: ${installed} \u2192 ${latest}. Run npm i -g seaworthy to update.`;
16334
+ return `Update available: ${installed} \u2192 ${latest}. Run npm i -g seaworthycode to update.`;
16304
16335
  }
16305
16336
  return void 0;
16306
16337
  } catch {
@@ -16605,7 +16636,7 @@ if (existsSync6(repoRootEnv)) {
16605
16636
  }
16606
16637
  var require5 = createRequire4(import.meta.url);
16607
16638
  var { version } = require5("../package.json");
16608
- var cli = cac("seaworthy");
16639
+ var cli = cac("seaworthycode");
16609
16640
  cli.version(version);
16610
16641
  cli.command("[path]", "Scan a directory for issues").option("--json", "Output JSON to stdout").option("--output <path>", "Write HTML report to file").option("--sarif <path>", "Write SARIF report to file").option("--sarif-stdout", "Write SARIF report to stdout").option("--fail-on-findings", "Exit with code 5 when findings are found").option("--tier <tier>", "Select tier (free, pro)").option("--provider <provider>", "LLM provider (anthropic, openai, kimi, moonshot, deepseek, gemini, minimax, ollama, openrouter)").option("--max-file-size <bytes>", "Skip files larger than this (default: 1 MB)").option("--show-ids", "Show check IDs alongside names in terminal output").option("--debug", "Enable debug logging").option("--baseline [ref]", "Compare against a baseline file or save current findings as baseline (--baseline save)").option("--min-confidence <level>", "Hide findings below this confidence (high, medium, low)").option("--all", "List every finding; do not collapse low-severity findings").action(async (targetDir, options) => {
16611
16642
  const maxFileSize = options.maxFileSize ? Number(options.maxFileSize) : void 0;