dumpfs-ts 0.1.0

Node.js bindings for dumpfs - A tool for dumping codebase information for LLMs
Documentation
use dumpfs::tk;
use std::str::FromStr;

#[napi(object)]
pub struct ScanOpts {
  /// Whether to skip .gitignore files (default: false)
  pub no_gitignore: Option<bool>,
  /// Whether to skip global git ignore file (default: false)
  pub no_git_global: Option<bool>,
  /// Whether to skip git exclude file (default: false)
  pub no_git_exclude: Option<bool>,
  /// Path to a custom ignore file (default: None)
  pub custom_ignore_path: Option<String>,
  /// Patterns to ignore (default: empty list)
  pub ignore_patterns: Option<Vec<String>>,
  /// Patterns to include (default: empty list)
  pub include_patterns: Option<Vec<String>>,
  /// Whether to follow symlinks (default: false)
  pub follow_symlinks: Option<bool>,
  /// Whether to include hidden files/dirs (default: false, meaning hidden files *are* included)
  pub hidden: Option<bool>,
  /// Maximum depth to traverse (default: None, unlimited)
  pub max_depth: Option<u32>,
  /// Only include files with Unix permissions matching this mask (default: None)
  pub permissions_filter: Option<u32>,
  /// Maximum file size in bytes to attempt reading content (default: 2MB)
  pub max_file_size_for_content: Option<u32>,
  /// Skip reading file content (default: false)
  pub skip_content: Option<bool>,
  /// Ratio of non-control characters required to classify a file as text (default: 0.9)
  pub text_detection_ratio: Option<u32>, // Note: Changed to f32 to match core type
  /// Number of bytes to read for text detection heuristic (default: 8192)
  pub text_detection_buffer_size: Option<u32>,
  /// Number of threads for filesystem walking (default: num_cpus)
  pub num_walker_threads: Option<u32>,
  /// Number of threads for processing (default: Rayon default/num_cpus)
  pub num_processor_threads: Option<u32>,
  /// Capacity of the channel between walker and processors (default: 1000)
  pub channel_capacity: Option<u32>,
  /// Only include files modified after this Unix timestamp (default: None)
  pub modified_after_timestamp: Option<u32>,
  /// Only include files modified before this Unix timestamp (default: None)
  pub modified_before_timestamp: Option<u32>,
  /// Count tokens using the specified model (requires content scanning) (default: None)
  pub model: Option<String>,
}

#[napi(object)]
pub struct LlmTextOpts {
  /// Output format to generate (default: Md).
  pub format: Option<LlmTextFormat>,
  /// Whether to show Unix permission bits (within metadata) (default: false).
  pub show_permissions: Option<bool>,
  /// Whether to show file sizes (within metadata) (default: true).
  pub show_size: Option<bool>,
  /// Whether to show modification times (within metadata) (default: true).
  pub show_modified: Option<bool>,
  /// Whether to exclude file contents from output (e.g., for structure-only views). Controlled via scanner skip_content (default: false).
  pub omit_file_contents: Option<bool>,
  /// Whether to include a tree outline of directory structure at the beginning (default: true).
  pub include_tree_outline: Option<bool>,
  /// Whether to ensure that output format is parsable for given schema (default: false).
  pub parsable_format: Option<bool>,
  /// Whether to omit custom summary for llms (default: false).
  pub omit_summary: Option<bool>,
  /// Whether to include source details in output (default: true).
  pub include_source_details: Option<bool>,
}

impl From<ScanOpts> for dumpfs::fs::FsScannerOpts {
  fn from(value: ScanOpts) -> Self {
    use std::time::{Duration, SystemTime};

    let mut opts = Self::default();

    if let Some(no_gitignore) = value.no_gitignore {
      opts.no_gitignore = no_gitignore;
    }
    if let Some(no_git_global) = value.no_git_global {
      opts.no_git_global = no_git_global;
    }
    if let Some(no_git_exclude) = value.no_git_exclude {
      opts.no_git_exclude = no_git_exclude;
    }
    if let Some(custom_ignore_path) = value.custom_ignore_path {
      if !custom_ignore_path.is_empty() {
        opts.custom_ignore_path = Some(std::path::PathBuf::from(custom_ignore_path));
      }
    }
    if let Some(ignore_patterns) = value.ignore_patterns {
      opts.ignore_patterns = ignore_patterns;
    }
    if let Some(include_patterns) = value.include_patterns {
      opts.include_patterns = include_patterns;
    }
    if let Some(follow_symlinks) = value.follow_symlinks {
      opts.follow_symlinks = follow_symlinks;
    }
    if let Some(hidden) = value.hidden {
      opts.hidden = hidden;
    }
    if let Some(max_depth) = value.max_depth {
      opts.max_depth = Some(max_depth as usize);
    }
    if let Some(modified_after_timestamp) = value.modified_after_timestamp {
      opts.modified_after =
        Some(SystemTime::UNIX_EPOCH + Duration::from_secs(modified_after_timestamp as u64));
      opts.modified_after_timestamp = Some(modified_after_timestamp as u64);
    }
    if let Some(modified_before_timestamp) = value.modified_before_timestamp {
      opts.modified_before =
        Some(SystemTime::UNIX_EPOCH + Duration::from_secs(modified_before_timestamp as u64));
      opts.modified_before_timestamp = Some(modified_before_timestamp as u64);
    }
    if let Some(permissions_filter) = value.permissions_filter {
      opts.permissions_filter = Some(permissions_filter);
    }
    if let Some(max_file_size_for_content) = value.max_file_size_for_content {
      opts.max_file_size_for_content = Some(max_file_size_for_content as u64);
    }
    if let Some(skip_content) = value.skip_content {
      opts.skip_content = skip_content;
    }
    if let Some(text_detection_ratio) = value.text_detection_ratio {
      opts.text_detection_ratio = text_detection_ratio as f32;
    }
    if let Some(text_detection_buffer_size) = value.text_detection_buffer_size {
      opts.text_detection_buffer_size = text_detection_buffer_size as usize;
    }
    if let Some(num_walker_threads) = value.num_walker_threads {
      opts.num_walker_threads = Some(num_walker_threads as usize);
    }
    if let Some(num_processor_threads) = value.num_processor_threads {
      opts.num_processor_threads = Some(num_processor_threads as usize);
    }
    if let Some(channel_capacity) = value.channel_capacity {
      opts.channel_capacity = channel_capacity as usize;
    }
    if let Some(model) = value.model {
      opts.model = tk::Model::from_str(model.as_str()).ok();
    }

    opts
  }
}

impl From<LlmTextOpts> for dumpfs::fs::FsFmtOpts {
  fn from(value: LlmTextOpts) -> Self {
    let mut opts = Self::default();

    if let Some(format) = value.format {
      opts.format = format.into();
    }
    if let Some(show_permissions) = value.show_permissions {
      opts.show_permissions = show_permissions;
    }
    if let Some(show_size) = value.show_size {
      opts.show_size = show_size;
    }
    if let Some(show_modified) = value.show_modified {
      opts.show_modified = show_modified;
    }
    if let Some(omit_file_contents) = value.omit_file_contents {
      opts.omit_file_contents = omit_file_contents;
    }
    if let Some(include_tree_outline) = value.include_tree_outline {
      opts.include_tree_outline = include_tree_outline;
    }
    if let Some(parsable_format) = value.parsable_format {
      opts.parsable_format = parsable_format;
    }
    if let Some(omit_summary) = value.omit_summary {
      opts.omit_summary = omit_summary;
    }
    if let Some(include_source_details) = value.include_source_details {
      opts.include_source_details = include_source_details;
    }

    opts
  }
}

#[derive(Default)]
#[napi(string_enum)]
pub enum LlmTextFormat {
  /// Markdown format
  #[default]
  Md,
  /// XML format
  Xml,
  /// JSON format
  Json,
}

impl From<LlmTextFormat> for dumpfs::fs::FsWriteFormat {
  fn from(value: LlmTextFormat) -> Self {
    match value {
      LlmTextFormat::Md => Self::Md,
      LlmTextFormat::Xml => Self::Xml,
      LlmTextFormat::Json => Self::Json,
    }
  }
}