/** * Unified composition layer for @npm-safe/core. * * {@link NpmSafeEngine} combines the database, cache, registry client, rate * limiter, static analyzer, and refresh scheduler into a single facade that * exposes the full public API of the engine. * * @module index */ import type { SearchResult } from './registry/types.js'; import { type RuleDescriptor } from './scanner/static-rules.js'; import type { ScanRule } from './scanner/types.js'; import { SecurityLevel, Severity } from './scanner/types.js'; import type { StaticScanReport } from './scanner/types.js'; import type { LlmScanReport } from './scanner/types.js'; import { RuleConfigManager } from './scanner/rule-config.js'; import type { CiReport, CiScanOptions } from './scanner/ci-scan.js'; import type { LlmProviderOptions } from './llm/provider.js'; import type { LlmConfig, LlmStatus } from './llm/llm-config.js'; /** * Options accepted by the {@link NpmSafeEngine} constructor. */ export interface NpmSafeEngineOptions { /** * Filesystem path to the SQLite database file. * @default './npm-safe.db' */ readonly dbPath?: string; /** * Base URL of the npm registry. * @default 'https://registry.npmjs.org' */ readonly registryUrl?: string; /** * Token bucket refill rate (tokens per second). * @default 5 */ readonly rateLimit?: number; /** * Maximum burst size for the token bucket. * @default 10 */ readonly rateLimitBurst?: number; /** * Cache TTL for package metadata in milliseconds. * @default 3600000 (1 hour) */ readonly cacheTtlMs?: number; /** * HTTP proxy URL used for registry requests (e.g. * `http://127.0.0.1:7897`). When omitted, the conventional environment * variables (`HTTPS_PROXY`, `HTTP_PROXY`, `ALL_PROXY`) are consulted. */ readonly proxy?: string; /** Optional LLM security scanner configuration (OpenAI / Gemini / Anthropic). */ readonly llm?: LlmProviderOptions; /** * Path to the LLM provider configuration JSON file. * @default '~/.npm-safe/llm.json' */ readonly llmConfigPath?: string; /** * Path to the per-rule configuration JSON file. * @default '~/.npm-safe/rules.json' */ readonly rulesConfigPath?: string; /** * Directory scanned for third-party rule plugin files (`*.mjs` / `*.js`). * @default '~/.npm-safe/rules/' */ readonly rulesDir?: string; } /** * Result of a {@link NpmSafeEngine.checkPackage} call. */ export interface CheckResult { /** Name of the checked package. */ readonly packageName: string; /** Whether the package exists on the registry. */ readonly exists: boolean; /** The latest available version, or an empty string when `exists` is `false`. */ readonly latestVersion: string; /** Security assessment of the package. */ readonly security: { /** Overall security level derived from the combined scan. */ readonly overallLevel: SecurityLevel; /** Numeric security score (0–100, higher is safer). */ readonly overallScore: number; /** Full static scan report, or `null` if one is not available. */ readonly staticScan: StaticScanReport | null; /** Optional semantic analysis from the configured LLM provider. */ readonly llmScan?: LlmScanReport; }; /** * Basic registry metadata, or `null` when the package does not exist. */ readonly registryInfo: { /** Human-readable description of the package. */ readonly description: string; /** URL to the package's homepage. */ readonly homepage: string; /** Repository descriptor as a string (e.g. `"github:user/repo"`). */ readonly repository: string; } | null; /** * ISO-8601 timestamp of when this result was last cached, or `null` when * the exact cached-at time is not known (e.g. served from an earlier cache * hit whose timestamp was not recorded). */ readonly cachedAt: string | null; } /** * Options accepted by {@link NpmSafeEngine.checkPackage}. */ export interface CheckPackageOptions { /** Download and inspect the published package tarball in memory. */ readonly deep?: boolean; /** * When `true`, skip the ENTIRE cache-hit fast path (including * `cache.getPackage`, `cache.getSecurityReport`, and * `cache.getLlmScanReport`) and always execute the cache-miss path: fetch * fresh metadata from the registry, persist it, re-run the static analyzer, * persist the security report, and run the LLM scan. In other words, * `forceRefresh` mirrors the stale-fetch path exactly. Useful when the * caller knows the cached data is stale or wants to re-validate against the * live registry regardless of TTL. * @default false */ readonly forceRefresh?: boolean; /** * Optional external abort signal. When supplied, the signal is forwarded * to the registry fetch (`client.getPackageMetadata`) and the LLM scan so * an abort during in-flight work settles the promise promptly with * `DOMException('The operation was aborted.', 'AbortError')` (cooperative * cancellation). */ readonly signal?: AbortSignal; } /** * Options accepted by {@link NpmSafeEngine.checkPackages}. */ export interface BatchCheckOptions { /** * Maximum number of concurrent checks. Every check still consumes one * token from the rate limiter. * @default 5 */ readonly concurrency?: number; /** Download and inspect every package tarball. */ readonly deep?: boolean; /** * Optional external abort signal forwarded to each worker's * {@link NpmSafeEngine.checkPackage} call. The worker loop short-circuits * at the top of each iteration when this signal is aborted, so a long * batch settles promptly instead of continuing to iterate. */ readonly signal?: AbortSignal; /** * Progress callback invoked after each package completes. `done` counts * completed packages (both successes and failures); `total` is the input * length. */ readonly onProgress?: (done: number, total: number, entry: BatchPackageResult) => void; } /** * One entry of the result array returned by * {@link NpmSafeEngine.checkPackages}, in input order. */ export interface BatchPackageResult { /** Input package name. */ readonly name: string; /** Whether the check succeeded. */ readonly ok: boolean; /** The check result when `ok` is `true`. */ readonly result?: CheckResult; /** Error message when `ok` is `false`. */ readonly error?: string; } /** * Unified facade that composes every @npm-safe/core module into a single * public API surface. * * Construct an instance with optional {@link NpmSafeEngineOptions}, then use * its methods to check, search, watch, refresh, and configure npm packages. * * @example * ```ts * const engine = new NpmSafeEngine({ dbPath: './my-cache.db' }); * const result = await engine.checkPackage('lodash'); * console.log(result.security.overallLevel); * await engine.close(); * ``` */ export declare class NpmSafeEngine { /** Database connection manager. */ private readonly database; /** Cache read/write layer. */ private readonly cache; /** HTTP client for the npm registry. */ private readonly client; /** Token bucket rate limiter. */ private readonly limiter; /** Static analysis engine. */ private readonly analyzer; /** Per-rule configuration (enabled / severity / options). */ private readonly ruleConfig; /** Auto-refresh scheduler. */ private readonly scheduler; /** LLM provider configuration manager. */ private readonly llmConfig; /** Optional semantic security scanner. */ private llmProvider?; /** * @param options - Optional configuration overrides; see * {@link NpmSafeEngineOptions} for available options. */ constructor(options?: NpmSafeEngineOptions); /** * Check a package by name, returning cached data if still fresh, or fetching * from the registry, running static analysis, and caching the result. * * When the package does not exist on the registry (HTTP 404) the returned * {@link CheckResult.exists} is `false` and the security / registry info * fields are empty. All other errors (network failure, timeout, …) are * rethrown so the caller can handle them appropriately. * * Pass `{ forceRefresh: true }` to skip the cache-hit fast path entirely * and always re-fetch from the registry. Pass `{ signal }` to make the * check cooperatively cancellable — an abort settles the promise promptly * with `DOMException('The operation was aborted.', 'AbortError')`. * * @param name - Fully-qualified package name (scope included when scoped). * @param options - Optional check options (forceRefresh, signal). * @returns A promise that resolves to the check result. */ checkPackage(name: string, options?: CheckPackageOptions): Promise; /** * Search the npm registry for packages matching a text query. * * @param query - Free-text search query. * @param options - Optional search options. `options.size` caps the number * of results (defaults to 20); `options.signal` is forwarded to the * registry client for cooperative cancellation. Search is not cached, so * there is no `forceRefresh` option here. * @returns An array of search-result hits, ordered by relevance. */ searchPackages(query: string, options?: { readonly size?: number; readonly signal?: AbortSignal; }): Promise; /** * Check many packages in parallel with a shared concurrency cap. * * Every check consumes one token from the rate limiter, so the batch * respects the configured request budget even when running concurrently. * Individual failures are isolated: a package that throws (network error, * timeout, …) yields a `{ ok: false, error }` entry instead of rejecting * the whole batch. Use `checkPackage` when the raw error must propagate. * * @param names - Package names to check. * @param options - Batch options (concurrency, progress callback). * @returns One entry per input name, in input order. */ checkPackages(names: readonly string[], options?: BatchCheckOptions): Promise; /** * Scan a project's dependencies and return a CI report with a failure gate. * * Reads dependencies from `package.json` (or `package-lock.json` when * `options.lockfile` is set), checks each one via {@link checkPackage}, and * aggregates the results into a {@link CiReport}. The report's `failed` flag * is `true` when any dependency reaches `options.failLevel` (inclusive) or * when any check throws. * * This method is a pure computation — it does NOT log, format, or set process * exit codes. The caller (CLI or plugin) renders the report and maps `failed` * to an exit code. * * When `options.signal` is supplied, the loop short-circuits at the top of * each iteration on abort and rejects with * `DOMException('The operation was aborted.', 'AbortError')` so an aborted * scan does not keep iterating deps (cooperative cancellation). * * @param options - Optional scan configuration. Defaults: * `dir = process.cwd()`, `failLevel = SecurityLevel.Dangerous`. * @returns A {@link CiReport} describing every checked dependency. */ ciScan(options?: CiScanOptions): Promise; /** * Returns the list of package names currently on the watchlist. * * @returns All watched package names, in insertion order. */ getWatchlist(): Promise; /** * Add a package to the watchlist. Idempotent — adding a name that is already * watched is a no-op. * * @param name - Fully-qualified package name to watch. */ addToWatchlist(name: string): Promise; /** * Remove a package from the watchlist. No-op if the name was not watched. * * @param name - Fully-qualified package name to stop watching. */ removeFromWatchlist(name: string): Promise; /** * Refresh a single package: fetch its latest metadata from the registry, * re-run static analysis, and persist the results. * * Per-package failures are surfaced via the scheduler's `refresh:error` * event and represented by a `false` result rather than thrown, so a * failing package does not abort a batch. An external abort * (`options.signal` aborted) propagates as * `DOMException('The operation was aborted.', 'AbortError')`. * * @param name - Fully-qualified package name to refresh. * @param options - Optional refresh options. `options.signal` is forwarded * to the registry fetch and LLM scan. */ refreshPackage(name: string, options?: { readonly signal?: AbortSignal; }): Promise; /** * Refresh every package whose cached metadata has passed its TTL. * * Packages are processed sequentially so the rate limiter is respected. * When `options.signal` is supplied, the loop short-circuits at the top of * each iteration on abort and rejects with * `DOMException('The operation was aborted.', 'AbortError')`. * * @param options - Optional refresh options. `options.signal` is forwarded * to each per-package refresh. */ refreshAll(options?: { readonly signal?: AbortSignal; }): Promise; /** * Retrieve a setting value by key. * * @param key - Settings key. * @returns The stored value, or `null` if the key is unset. */ getSetting(key: string): Promise; /** * Upsert a setting value by key. * * @param key - Settings key. * @param value - Value to persist. */ setSetting(key: string, value: string): Promise; /** * Record a check into the persistent history database. Used by the CLI and * the desktop extension so history is shared across both frontends. * * @param result - A check result for an existing package. */ recordCheckHistory(result: CheckResult): Promise; /** * Append a raw history entry (newest-first, capped at 1000). */ recordHistoryEntry(entry: { readonly packageName: string; readonly level: string; readonly score: number; readonly timestamp: string; }): Promise; /** * Return the persistent check history, newest first (capped at 1000). */ getCheckHistory(limit?: number): Promise>; /** * Clear the persistent check history. */ clearCheckHistory(): Promise; /** * Register a scan rule at runtime. A rule with the same id replaces the * existing one. * * @param rule - The rule to register. */ registerRule(rule: ScanRule): void; /** * Remove a scan rule by id. * * @param ruleId - Id of the rule to remove. * @returns `true` if a rule was removed, `false` if no such rule exists. */ unregisterRule(ruleId: string): boolean; /** * Describe every registered rule with its effective status. * * @returns Rule descriptors in registration order. */ listRules(): RuleDescriptor[]; /** * Enable or disable a rule (persisted in the rules config file). * * @param ruleId - Id of the rule. * @param enabled - Whether the rule should run. */ setRuleEnabled(ruleId: string, enabled: boolean): void; /** * Override a rule's severity (persisted). Pass `undefined` to clear the * override and return to the rule's default severity. * * @param ruleId - Id of the rule. * @param severity - Severity override, or `undefined` to clear. */ setRuleSeverity(ruleId: string, severity: Severity | undefined): void; /** * Set free-form options for a rule (persisted). Rule implementations can * read these via the rule config manager. * * @param ruleId - Id of the rule. * @param options - Free-form options. */ setRuleOptions(ruleId: string, options: Readonly>): void; /** * Access the rule configuration manager for low-level inspection. * * @returns The rule configuration manager backing this engine. */ getRuleConfig(): RuleConfigManager; /** * Load third-party rules from a directory of ES module files. * * Each `*.mjs` / `*.js` file may export a `rule`, `rules`, or `default` * binding holding one or more {@link ScanRule}s. Files that fail to load * are skipped. * * @param dir - Directory to scan. Defaults to `~/.npm-safe/rules/`. * @returns The number of rules loaded. */ loadRulePlugins(dir?: string): Promise; /** * Get the raw LLM configuration, including the API key. * * @returns The current persisted LLM config. */ getLlmConfig(): LlmConfig; /** * Get a masked, display-safe view of the LLM status. * * @returns Status object safe to render in a UI. */ getLlmStatus(): LlmStatus; /** * Update the LLM configuration and recreate the provider. * * The change is persisted immediately. If LLM scanning is disabled or no * API key is available, the provider is set to `undefined` so the rest of the * engine continues unaffected. * * @param update - Partial config update. Pass `{ enabled: false }` to disable. */ setLlmConfig(update: Partial): void; /** * Test whether the current LLM configuration can connect to its provider. * * @returns `true` if the provider is enabled, configured, and the test call * succeeds; `false` otherwise. */ testLlmConnection(): Promise; /** * Start the periodic auto-refresh loop. * * The first refresh cycle kicks off immediately in the background; subsequent * cycles repeat at `intervalMs`. Safe to call multiple times — calling while * already running resets the interval. * * @param intervalMs - Milliseconds between refresh cycles. * Defaults to 1 hour. */ startAutoRefresh(intervalMs?: number): void; /** * Stop the periodic auto-refresh loop. * * Safe to call when the scheduler is not running. Any in-flight refresh * continues to completion. */ stopAutoRefresh(): void; /** * Release all resources held by the engine. * * Stops the auto-refresh scheduler, disposes the rate-limiter timer, and * closes the database connection. After calling this method the engine * instance must not be used for further operations. */ close(): void; /** * Assemble a {@link CheckResult} from its constituent parts. */ private buildCheckResult; /** Build a metadata-only or deep static report for one published version. */ private createStaticReport; /** Download and inspect a tarball without letting ordinary failures hide metadata results. */ private scanPackageContent; private scanWithLlm; } export { createLlmProvider, LlmProviderError } from './llm/provider.js'; export type { LlmProviderOptions, LlmScanInput, LlmScanProvider, } from './llm/provider.js'; export { LlmConfigManager, getDefaultLlmConfigPath } from './llm/llm-config.js'; export type { LlmConfig, LlmStatus } from './llm/llm-config.js'; export { RuleConfigManager } from './scanner/rule-config.js'; export type { RuleConfig, RuleConfigFile, RuleOptions } from './scanner/rule-config.js'; export { loadRulesFromDirectory } from './scanner/rule-loader.js'; export type { LoadedRuleFile, RuleModuleExport } from './scanner/rule-loader.js'; export type { RuleDescriptor } from './scanner/static-rules.js'; export { analyzePackageTarball, DEFAULT_MAX_ARCHIVE_ENTRIES, DEFAULT_MAX_FILE_BYTES, DEFAULT_MAX_SCANNED_BYTES, DEFAULT_MAX_UNPACKED_BYTES, CONTENT_SCAN_RULES, } from './scanner/package-content.js'; export type { PackageContentScanOptions, PackageContentScanResult, } from './scanner/package-content.js'; export type { ContentScanSummary, ScanFinding, StaticScanReport } from './scanner/types.js'; export { DatabaseManager } from './store/database.js'; export { CacheManager, DEFAULT_CACHE_TTL_MS, MAX_CHECK_HISTORY } from './store/cache-manager.js'; export type { CacheManagerOptions } from './store/cache-manager.js'; export { SecurityLevel, Severity } from './scanner/types.js'; export { LEVEL_RANK, readDependencies, readLockfileDependencies } from './scanner/ci-scan.js'; export type { CiReport, CiScanOptions, PackageResult, Dependency } from './scanner/ci-scan.js'; //# sourceMappingURL=index.d.ts.map