Auto-generated from tool definitions
Version: 5.0.0
Generated: 2026-02-13T16:45:00.000Z
- file_organizer_analyze_duplicates
- file_organizer_batch_read_files
- file_organizer_batch_rename
- file_organizer_categorize_by_type
- file_organizer_delete_duplicates
- file_organizer_disk_usage_by_category
- file_organizer_doctor
- file_organizer_export_config
- file_organizer_export_report
- file_organizer_find_broken_symlinks
- file_organizer_find_empty_directories
- file_organizer_find_duplicate_files
- file_organizer_find_largest_files
- file_organizer_find_old_files
- file_organizer_get_categories
- file_organizer_inspect_metadata
- file_organizer_list_files
- file_organizer_organize_by_date
- file_organizer_organize_by_project
- file_organizer_organize_files
- file_organizer_organize_music
- file_organizer_organize_photos
- file_organizer_preview_delete_duplicates
- file_organizer_preview_organization
- file_organizer_quarantine_files
- file_organizer_read_file
- file_organizer_restore_quarantine
- file_organizer_scan_directory
- file_organizer_search_history
- file_organizer_set_custom_rules
- file_organizer_sensitive_scan
- file_organizer_smart_suggest
- file_organizer_system_organize
- file_organizer_undo_last_operation
- file_organizer_validate_organization_plan
- file_organizer_verify_integrity
- file_organizer_view_history
Note: The watch tools (
file_organizer_watch_directory,file_organizer_unwatch_directory,file_organizer_list_watches) are no longer part of the MCP server. Scheduled organization runs as a standalone process — seefile-organizer-watch(bin/file-organizer-watch.mjs) withadd/remove/list/once/runsubcommands.onceruns a single organization pass and exits, so an OS timer can be the scheduler. It takes--jsonfor one parseable object on stdout and exits 0 (nothing to do), 1 (failed), or 2 (moved files) — see README.md for the full contract. No MCP tool shape changed.
Description: Finds duplicate files and suggests which to keep/delete based on location, name quality, and age.
| Parameter | Type | Description | Default |
|---|---|---|---|
directory |
string | - | - |
recommendation_strategy |
string | - | 'best_location' |
auto_select_keep |
boolean | - | false |
response_format |
string | - | 'markdown' |
file_organizer_analyze_duplicates({
directory: "value",
recommendation_strategy: "value",
auto_select_keep: true,
response_format: "value",
});Description: Rename multiple files using rules (find/replace, case, add text, numbering). The whole plan is checked for name collisions before the first file moves. Closes #44.
Two files aimed at one name, or one file aimed at a name a different file already holds, are reported as collisions:
| Kind | Meaning |
|---|---|
duplicate_target |
Two or more sources collapse onto the same destination name |
destination_exists |
A different file already holds the destination name |
A dry run reports collisions and changes nothing. A real run (dry_run: false) with any collision is rejected before the first rename, so no file is renamed at all, even the ones with no clash. Adjust the rules or move the files already holding those names, then run again.
Collisions are returned as structured data in both response formats, and name the files by base name only, so a rejected plan does not echo directory layout back to the caller.
| Parameter | Type | Description | Default |
|---|---|---|---|
files |
array | List of absolute file paths | - |
items |
string | - | - |
directory |
string | Directory to scan (optional) | - |
rules |
array | List of renaming rules. See specific rule schemas. | - |
items |
object | - | - |
dry_run |
boolean | Simulate renaming | true |
response_format |
string | - | 'markdown' |
| Field | Type | Description |
|---|---|---|
dry_run |
boolean | Whether the call was a simulation |
rejected |
boolean | true only when a real run was stopped before the first rename |
renamed |
number | Files renamed. 0 on a dry run and on a rejection |
processed |
number | Files the rules were evaluated against |
conflicts[] |
array | Collisions found. Empty when the plan is clear |
conflicts[].kind |
string | duplicate_target or destination_exists |
conflicts[].destination |
string | Contested destination file name |
conflicts[].sources |
array | Base names of the files aimed at that destination |
previews[] |
array | Per-file plan. Present on a dry run |
result |
object | Execution statistics and errors. Present on a real run |
file_organizer_batch_rename({
files: [],
items: "value",
directory: "value",
rules: [],
items: value,
dry_run: true,
response_format: "value",
});Description: Categorize files by their type (Executables, Videos, Documents, etc.) and show statistics for each category.
| Parameter | Type | Description | Default |
|---|---|---|---|
directory |
string | Full path to the directory to categorize | - |
include_subdirs |
boolean | Include subdirectories | false |
use_content_analysis |
boolean | Enable magic-byte content inspection | false |
response_format |
string | Output format (markdown/json) | 'markdown' |
file_organizer_categorize_by_type({
directory: "value",
include_subdirs: true,
use_content_analysis: false,
response_format: "value",
});Description: Deletes specified duplicate files. DESTRUCTIVE. Every candidate is hashed and checked against surviving copies before anything is removed. The search walks each candidate's parent and grandparent directory recursively, up to 10 levels deep and 10000 files, skipping dot-entries and node_modules/.git/__pycache__/.venv; a copy kept outside those roots is not found and the deletion is refused. Files over the hashing size cap are checked by size plus sampled content, which is weaker than a full hash and is reported as partially verified. Deleted files go to a recoverable backup dir; pass the returned manifest_id to file_organizer_undo_last_operation to restore them.
| Parameter | Type | Description | Default |
|---|---|---|---|
files_to_delete |
array | - | - |
items |
string | - | - |
create_backup_manifest |
boolean | - | true |
verify_before_delete |
boolean | Hash each candidate and refuse to delete a file with no surviving copy | true |
candidate_directories |
array | Extra directories to search for surviving copies during verification, walked the same way as the candidate's parent and grandparent | [] |
response_format |
string | - | 'markdown' |
| Field | Type | Description |
|---|---|---|
deleted_count |
number | Files removed |
failed_count |
number | Files refused or errored |
verified |
boolean | Whether the surviving-copy check ran |
manifest_id |
string | Pass to file_organizer_undo_last_operation to restore |
partially_verified_files |
array | Deleted files whose check used a sampled identity (see below) |
Verification walks each file's parent and grandparent directory recursively, so a
copy in a subfolder of the parent is found. The walk is bounded by the scanner's
own limits: maxScanDepth (10) levels, maxFilesPerOperation (10000) files, and
dot-entries plus node_modules/.git/__pycache__/.venv are skipped. When the
only surviving copy lives outside those roots, pass its directory in
candidate_directories, otherwise it will be treated as a last copy and the
deletion refused.
Files above the 100MB hashing cap cannot be fully hashed, so their surviving-copy
check compares size plus the first and last 64KB instead of the whole file. Those
files are listed in partially_verified_files. That is a weaker check, not proof
of equality, and it is reported rather than implied.
file_organizer_delete_duplicates({
files_to_delete: [],
items: "value",
create_backup_manifest: true,
verify_before_delete: true,
candidate_directories: [],
response_format: "value",
});Description: Report how much space a directory holds per category: bytes, file count, and share of the total. Categories come from the same categorizer categorize_by_type and organize_files use, custom rules included, and sizes are summed from the scan rather than from a second directory walk. include_subdirs defaults to true because the space a category holds usually sits below the directory you point at. Read-only. Closes #43.
| Parameter | Type | Description | Default |
|---|---|---|---|
directory |
string | Full path to the directory | - |
include_subdirs |
boolean | Include subdirectories | true |
response_format |
string | json or markdown |
'markdown' |
| Field | Type | Description |
|---|---|---|
directory |
string | The measured directory |
total_files |
number | Files counted across all categories |
total_size |
number | Total bytes counted |
total_size_readable |
string | Total bytes as a readable size |
categories[] |
array | One entry per category, largest first |
categories[].category |
string | Category name from the categorizer |
categories[].file_count |
number | Files in that category |
categories[].total_size |
number | Bytes in that category |
categories[].total_size_readable |
string | Bytes as a readable size |
categories[].percent_of_total |
number | Share of the total, rounded to two decimals |
percent_of_total is rounded per category, so the shares of a directory with
many small categories add up to 100 within a rounding error rather than exactly.
Empty directories report zero totals instead of dividing by zero.
file_organizer_disk_usage_by_category({
directory: "value",
include_subdirs: true,
response_format: "value",
});Description: Audit a directory for symlinks that dangle (target missing) or resolve outside the allowed directories. Recurses into real subdirectories but never follows a symlink, so it cannot read outside the allowed roots. Symlink loops are reported as their own kind. Read-only, so it is safe to run before organizing a directory. Closes #33.
| Parameter | Type | Description | Default |
|---|---|---|---|
directory |
string | Full path to the directory | - |
response_format |
string | json or markdown |
'markdown' |
| Field | Type | Description |
|---|---|---|
directory |
string | The audited directory |
scanned_count |
number | Symlinks examined |
total_count |
number | Findings reported |
dangling_count |
number | Findings whose kind is dangling |
escaping_count |
number | Findings whose kind is escapes_allowed_roots |
circular_count |
number | Findings whose kind is circular |
findings[].path |
string | Full path of the link itself, not its target |
findings[].link_target |
string | Raw link value as stored on disk |
findings[].kind |
string | dangling, escapes_allowed_roots, or circular |
findings[].detail |
string | Plain-English explanation |
findings[].resolved_target |
string | Canonical absolute target. Absent for a loop |
Containment is decided by the same whitelist check the validation layer uses, so this tool and organize_files agree on what "outside the allowed roots" means.
file_organizer_find_broken_symlinks({
directory: "value",
response_format: "value",
});Description: List directories under a root that contain no entries at all, for cleanup after a scan. Recurses by default, bounded by the configured max scan depth and a result cap. Emptiness is literal: a directory holding only dotfiles, only a subdirectory, or only a symlink has entries and is not reported, so a directory that merely looks idle is never proposed for removal. Read-only, so it is safe to run before organizing. Closes #37.
| Parameter | Type | Description | Default |
|---|---|---|---|
directory |
string | Full path to the directory | - |
include_subdirs |
boolean | Recurse into subdirectories | true |
max_depth |
number | Levels below the root to walk (0 = root only) | configured max |
limit |
number | Maximum number of empty directories to return | 100 |
response_format |
string | json or markdown |
'markdown' |
| Field | Type | Description |
|---|---|---|
directory |
string | The root that was walked |
scanned_count |
number | Directories whose entries were listed |
depth_limited |
boolean | True when a subdirectory past the depth cap was skipped |
result_limited |
boolean | True when the result cap left a subdirectory unexplored |
limit |
number | The result cap that applied |
total_count |
number | Empty directories found |
empty_dirs[] |
string[] | Full paths, sorted |
Read depth_limited and result_limited before treating a short list as complete. A directory is only reported when its listing came back with zero entries.
file_organizer_find_empty_directories({
directory: "value",
include_subdirs: true,
limit: 100,
response_format: "value",
});Description: Find duplicate files in a directory based on their content (SHA-256 hash). Shows potential wasted space. Files above the hashing size cap (100MB by default) and empty files are not compared; any such file is reported back in skipped so the result is never mistaken for exhaustive.
| Parameter | Type | Description | Default |
|---|---|---|---|
directory |
string | Full path to the directory | - |
limit |
number | Max groups to return | 100 |
offset |
number | Groups to skip | 0 |
response_format |
string | - | 'markdown' |
Duplicate detection cannot compare every file. Anything left out is reported rather than dropped, so a partial analysis is never returned as an exhaustive one.
| Field | Type | Description |
|---|---|---|
skipped |
array | One entry per unanalyzed file |
skipped[].path |
string | Full path |
skipped[].name |
string | File name |
skipped[].size_bytes |
number | Size in bytes |
skipped[].reason |
string | empty_file, exceeds_size_cap, hash_failed, or timed_out |
skipped[].detail |
string | Plain-English explanation of the skip |
skipped_bytes |
number | Total bytes belonging to skipped files |
file_organizer_find_duplicate_files({
directory: "value",
limit: 123,
offset: 123,
response_format: "value",
});Description: Find the largest files in a directory. Useful for identifying space-consuming files and cleanup opportunities.
| Parameter | Type | Description | Default |
|---|---|---|---|
directory |
string | Full path to the directory | - |
include_subdirs |
boolean | Include subdirectories | false |
top_n |
number | Number of files to return | 10 |
response_format |
string | - | 'markdown' |
file_organizer_find_largest_files({
directory: "value",
include_subdirs: true,
top_n: 123,
response_format: "value",
});Description: Find files in a directory that have not been touched for N days, oldest first. Age is measured from the last modification time by default, or from the last access time when asked. Read-only: nothing is moved or deleted.
| Parameter | Type | Description | Default |
|---|---|---|---|
directory |
string | Full path to the directory | - |
include_subdirs |
boolean | Include subdirectories | false |
older_than_days |
number | Only return files untouched for at least this many days (1-36500) | 365 |
age_source |
string | Timestamp to measure age from: mtime (last modified) or atime (last accessed) |
mtime |
top_n |
number | Number of oldest files to return | 10 |
response_format |
string | json or markdown |
'markdown' |
file_organizer_find_old_files({
directory: "value",
include_subdirs: true,
older_than_days: 365,
age_source: "mtime",
top_n: 10,
response_format: "json",
});| Field | Type | Description |
|---|---|---|
directory |
string | Validated directory that was searched |
age_source |
string | Timestamp the ages were measured from |
older_than_days |
number | Threshold that was applied |
total_count |
number | Files that matched, before top_n cut the list |
returned_count |
number | Entries in old_files |
old_files[] |
array | Oldest first |
old_files[].name |
string | File name |
old_files[].path |
string | Full path |
old_files[].size |
number | Size in bytes |
old_files[].size_readable |
string | Human-readable size |
old_files[].age_days |
number | Whole days since the chosen timestamp |
old_files[].accessed_or_modified |
string | ISO 8601 timestamp the age was measured from |
Description: Returns the list of categories used for file organization
| Parameter | Type | Description | Default |
|---|---|---|---|
response_format |
string | - | 'markdown' |
file_organizer_get_categories({
response_format: "value",
});Description: Inspects a file and returns comprehensive but privacy-safe metadata. For images, extracts EXIF data (date, camera, dimensions). For audio, extracts ID3 tags (artist, album, title). Excludes sensitive data like GPS coordinates.
| Parameter | Type | Description | Default |
|---|---|---|---|
file |
string | Full path to the file to inspect | - |
response_format |
string | - | 'markdown' |
file_organizer_inspect_metadata({
file: "value",
response_format: "value",
});Description: List all files in a directory with basic information. Returns file names and paths. Does not recurse into subdirectories.
| Parameter | Type | Description | Default |
|---|---|---|---|
directory |
string | Full path to the directory | - |
limit |
number | Max items to return | 100 |
offset |
number | Items to skip | 0 |
response_format |
string | - | 'markdown' |
file_organizer_list_files({
directory: "value",
limit: 123,
offset: 123,
response_format: "value",
});Description: Automatically organize files into categorized folders. Use dry_run=true to preview changes. A completed organize returns manifest_id, the rollback manifest it wrote; pass it to file_organizer_undo_last_operation to undo that one operation, or read it from the Undo Manifest column of file_organizer_view_history. A dry run writes no manifest and returns none.
| Parameter | Type | Description | Default |
|---|---|---|---|
directory |
string | Full path to the directory | - |
dry_run |
boolean | Simulate organization | true |
conflict_strategy |
string | How to handle file conflicts (rename/skip/overwrite). Uses config default if not specified | - |
use_content_analysis |
boolean | Enable magic-byte content inspection | false |
response_format |
string | Output format (markdown/json) | 'markdown' |
manifest_id is the rollback manifest this batch wrote, and is what
file_organizer_undo_last_operation takes to undo this operation specifically.
It is present only when files actually moved and a manifest was written, so it is
absent on a dry run and when no manifest could be written.
{
directory: string;
dry_run: boolean;
total_files: number;
statistics: Record<string, number>;
actions: Array<{
file: string;
from: string;
to: string;
category: string;
}>;
errors: string[];
errorCount: number;
successCount: number;
aborted: boolean;
content_analysis_enabled?: boolean;
manifest_id?: string;
}file_organizer_organize_files({
directory: "value",
dry_run: true,
conflict_strategy: "value",
use_content_analysis: false,
response_format: "value",
});Description: Dry-run for file_organizer_delete_duplicates. Groups duplicates and names the single copy that would survive under the keep_strategy you pick (newest, oldest, or keep_first), plus the flat list of files that would be deleted. Read-only: nothing is moved or removed. Pass files_to_delete to file_organizer_delete_duplicates to act on it. Files the scan could not compare are listed under skipped, so a 'nothing to delete' answer can still be partial.
| Parameter | Type | Description | Default |
|---|---|---|---|
directory |
string | Full path to the directory | - |
keep_strategy |
string | Survivor per group: newest (most recently modified), oldest, or keep_first (first found by the scan) |
'newest' |
response_format |
string | json or markdown |
'markdown' |
keep_strategy names the survivor outright. It is deliberately not
recommendation_strategy: analyze_duplicates blends path depth and location
quality into a score, so a preview labelled newest has to mean the most
recently modified copy and nothing else.
| Field | Type | Description |
|---|---|---|
dry_run |
boolean | Always true. Nothing was moved or removed |
keep_strategy |
string | The strategy that was applied |
summary.total_duplicate_groups |
number | Duplicate groups found |
summary.total_files_to_delete |
number | Files a delete would remove |
summary.total_wasted_space_bytes |
number | Bytes those deletions would reclaim |
not_analyzed_files / not_analyzed_bytes |
number | Files the scan could not compare — the blind spot of this answer |
duplicate_groups[].keep |
string | The one copy in the group that survives |
duplicate_groups[].would_delete |
array | Every other copy in the group |
files_to_delete |
array | Flat, de-duplicated list, ready to pass to file_organizer_delete_duplicates |
skipped lists files the scan could not compare (empty files, files over the
hashing cap, unreadable files, files dropped by the scan timeout). An empty
files_to_delete alongside a non-empty skipped means "nothing found", not
"nothing wrong" — the two are reported separately for that reason.
const preview = file_organizer_preview_delete_duplicates({
directory: "~/Downloads",
keep_strategy: "newest",
response_format: "json",
});
// Nothing deleted yet. Act on exactly what was previewed:
file_organizer_delete_duplicates({
files_to_delete: preview.files_to_delete,
});Description: Shows what would happen if files were organized, WITHOUT making any changes. Shows moves, conflicts, and skip reasons. Pass since_last_run=true to report only the files touched at or after the scheduler's last successful run of this directory, so "what is left to do" does not require reading the whole directory. The window comes from scheduler-state, which file-organizer-watch writes after each successful run. A directory the scheduler has never run is not filtered, so the whole directory is reported. Files whose mtime the scanner could not read are always included: a narrow window never hides a file merely because it could not be dated.
| Parameter | Type | Description | Default |
|---|---|---|---|
directory |
string | Full path to the directory | - |
show_conflicts_only |
boolean | - | false |
response_format |
string | - | 'markdown' |
conflict_strategy |
string | How to handle file conflicts for preview (rename/skip/overwrite). Uses config default if not specified | - |
since_last_run |
boolean | Only include files modified at or after the last successful scheduler run of this directory. A directory with no recorded run is not filtered | false |
file_organizer_preview_organization({
directory: "value",
show_conflicts_only: true,
response_format: "value",
conflict_strategy: "value",
since_last_run: true,
});Description: Sets flagged files aside in a quarantine directory so they can be reviewed without being deleted. Nothing is removed from disk: each file is moved into a hidden quarantine directory and recorded in a rollback manifest, so file_organizer_undo_last_operation or file_organizer_restore_quarantine puts every file back where it came from. Defaults to dry_run=true, which lists what would be quarantined and changes nothing. Same-basename files never overwrite each other; a collision becomes name_1.ext.
| Parameter | Type | Description | Default |
|---|---|---|---|
directory |
string | Directory the flagged files live in | - |
files |
string[] | Absolute paths of the flagged files, all inside directory |
- |
quarantine_dir |
string | Where to move them. Defaults to a hidden .file-organizer-quarantine directory inside directory |
- |
reason |
string | Note recorded in the manifest, e.g. why these were flagged | - |
dry_run |
boolean | List what would be quarantined without moving anything | true |
response_format |
string | markdown for human-readable, json for programmatic use |
'markdown' |
| Field | Type | Description |
|---|---|---|
directory |
string | Validated directory the files were taken from |
quarantine_dir |
string | Validated directory the files were (or would be) moved into |
dry_run |
boolean | True when nothing was moved |
requested |
number | How many file paths the caller asked for |
planned |
number | How many files have a destination (the whole plan on a dry run) |
quarantined |
number | Files actually moved. Always 0 on a dry run |
items |
object[] | { file, from, to } per file: the plan on a dry run, the moves that landed |
skipped |
object[] | { path, reason } for files left out |
errors |
string[] | Per-file failures and manifest-write failures |
manifest_id |
string | Rollback manifest covering the moves. Absent on a dry run |
reason |
string | The caller's note, when supplied |
Every path field above is an absolute path: directory, quarantine_dir,
items[].from, items[].to, and skipped[].path. That matches
file_organizer_organize_files, whose actions[].from / actions[].to are
absolute too. Absolute paths are canonicalised through the path validator, so
they are platform-dependent in spelling: Windows expands 8.3 short names
(C:\Users\RUNNER~1\... comes back as C:\Users\runneradmin\...) and macOS
rewrites /var to /private/var. Two spellings of the same file therefore
compare unequal as strings. Compare basenames, or normalise both sides through
the same function, rather than comparing raw path strings.
- The quarantine directory is derived from
directory, not hardcoded, and both it and every listed file pass the samevalidateStrictPathgate as any other input. Aquarantine_diroutside your allowed directories is refused. - Every listed file must live inside
directory. Paths are all validated before the first move, so a batch containing one bad path moves nothing. - A same-basename collision becomes
name_1.ext, so no file overwrites another. - The manifest is what makes this reversible: pass its id to
file_organizer_restore_quarantine, or callfile_organizer_undo_last_operation.
file_organizer_quarantine_files({
directory: "/home/user/Downloads",
files: ["/home/user/Downloads/invoice.exe"],
reason: "flags as executable content",
dry_run: false,
});Description: Puts quarantined files back at the exact paths they were taken from, using the manifest quarantine_files wrote. The restore records its own manifest, so file_organizer_undo_last_operation can undo the restore and put the files back into quarantine. Defaults to dry_run=true, which lists what would be restored and changes nothing.
| Parameter | Type | Description | Default |
|---|---|---|---|
quarantine_id |
string | Manifest id returned by quarantine_files. Omit to restore the newest |
- |
dry_run |
boolean | List what would be restored without moving anything | true |
response_format |
string | markdown for human-readable, json for programmatic use |
'markdown' |
| Field | Type | Description |
|---|---|---|
dry_run |
boolean | True when nothing was moved |
quarantine_id |
string | Quarantine manifest this restore read |
requested |
number | Moves recorded in that manifest |
planned |
number | How many restores have a destination |
restored |
number | Files actually put back. Always 0 on a dry run |
items |
object[] | { file, from, to } per file: the plan on a dry run, the moves that landed |
errors |
string[] | Per-file failures, manifest-write failures, and refused paths |
manifest_id |
string | Rollback manifest covering the restore, so the restore is undoable |
items[].from and items[].to are absolute canonical paths, the same
contract file_organizer_organize_files uses. They are platform-dependent in
spelling (Windows expands 8.3 short names, macOS rewrites /var to
/private/var), so compare basenames or normalise both sides rather than
comparing raw path strings.
- The manifest is verified for integrity before any recorded path is trusted, and each recorded path is re-checked against your allowed directories.
- Omitting
quarantine_idrestores the most recent quarantine manifest. When there is none, the call fails rather than guessing at some unrelated batch. - On success the quarantine manifest is retired, the same way undo retires a spent manifest. The restore manifest carries the reverse mapping.
file_organizer_restore_quarantine({
quarantine_id: "5f2c1a90-8b3e-4d7a-9c11-2e6f0a4b7d38",
dry_run: false,
});Description: Read file contents with security checks. Supports text, binary, and base64 encoding.
| Parameter | Type | Description | Default |
|---|---|---|---|
path |
string | Absolute path to the file to read (e.g., /home/user/documents/report.txt) | - |
encoding |
string | Text encoding for the file content | "utf-8" |
maxBytes |
number | Maximum bytes to read (default: 10MB, max: 100MB) | 10MB |
offset |
number | Byte offset to start reading from | 0 |
limit |
number | Maximum bytes to read (alternative to maxBytes) | - |
response_format |
string | Format of the response | "markdown" |
calculateChecksum |
boolean | Include SHA-256 checksum in response | true |
file_organizer_read_file({
path: "value",
encoding: "value",
maxBytes: 123,
offset: 123,
limit: 123,
response_format: "value",
calculateChecksum: true,
});Description: Scan directory and get detailed file information including size, dates, and extensions. Supports recursive scanning.
| Parameter | Type | Description | Default |
|---|---|---|---|
directory |
string | Full path to the directory to scan | - |
include_subdirs |
boolean | Include subdirectories in the scan | false |
max_depth |
number | Maximum depth to scan | -1 |
limit |
number | Max items to return | 100 |
offset |
number | Items to skip | 0 |
response_format |
string | - | 'markdown' |
file_organizer_scan_directory({
directory: "value",
include_subdirs: true,
max_depth: 123,
limit: 123,
offset: 123,
response_format: "value",
});Description: Customize how files are categorized. Persists custom rules to user configuration, replacing any rules saved earlier. Invalid rules are skipped; if the rules cannot be written to disk the call reports an error instead of a success.
| Parameter | Type | Description | Default |
|---|---|---|---|
rules |
array | - | - |
items |
object | - | - |
properties |
string | - | - |
category |
string | - | - |
extensions |
array | - | - |
filename_pattern |
string | - | - |
priority |
number | - | - |
response_format |
string | 'json' or 'markdown' | 'markdown' |
file_organizer_set_custom_rules({
rules: [],
items: value,
properties: "value",
category: "value",
extensions: [],
filename_pattern: "value",
priority: 123,
response_format: "markdown",
});Description: Screen a directory for files carrying personal metadata and score each one 0-100 for the risk of sharing it. Detects EXIF GPS coordinates and altitude, GPS fix timestamps, owner/artist names, camera and lens serial numbers, camera or computer make and model, copyright lines, capture/editing software, and free-text notes. Read-only. Closes #34.
Heuristic detection, not redaction. The tool reports; it never modifies or strips anything. A risk score of 0 means no recognized EXIF tag was found — it does NOT mean the file is safe to share. Metadata outside EXIF (PDF annotations, XMP, IPTC, embedded thumbnails), file names, and the visible image content are not analyzed, and only the first 256 KB of each file is read. Every response carries this caveat in its
limitsfield, in both response formats. Treat the scan output itself as sensitive: it echoes the values it found.
| Parameter | Type | Description | Default |
|---|---|---|---|
directory |
string | Full path to the directory to screen | - |
include_subdirs |
boolean | Descend into real subdirectories. Symbolic links are never followed. | false |
response_format |
string | json or markdown |
'markdown' |
Only the head of each file is read, so a large photo is never loaded whole.
| Field | Type | Description |
|---|---|---|
directory |
string | The scanned directory |
scanned_count |
number | Files whose metadata was actually parsed |
skipped_count |
number | Files present but outside what this scan can analyze |
flagged_count |
number | Scanned files carrying at least one finding |
highest_risk_score |
number | Highest score seen, or 0 |
truncated |
boolean | A subdirectory past the max scan depth was not walked |
files[].name |
string | File name |
files[].path |
string | Full path |
files[].format |
string | Detected format, jpeg or tiff. Trusted over the extension. |
files[].risk_score |
number | 0-100, the sum of the reason weights, capped at 100 |
files[].risk_level |
string | none, low (1-24), medium (25-59), or high (60+) |
files[].reasons[].kind |
string | What kind of personal data the tag carries |
files[].reasons[].weight |
number | Points this reason added. The weights sum to risk_score. |
files[].reasons[].detail |
string | Plain-English statement naming the tag |
files[].reasons[].exif_tags |
string[] | EXIF tag names behind the finding |
files[].reasons[].value |
string | Detected value, capped in length. Absent for tags that are not a readable string or number. |
skipped[].reason |
string | format_not_analyzed or unreadable |
skipped[].detail |
string | Why this file was skipped |
limits |
string[] | The coverage caveat. Required reading, not decoration. |
files is sorted by risk score descending, then by path, so the worst file is first. A
symbolic link is reported under skipped rather than followed.
Reason weights: gps_coordinates 40, owner_name 30, serial_number 25, camera_device 15,
notes_or_comment 10, gps_altitude 5, gps_timestamp 5, copyright 5, software 5.
file_organizer_sensitive_scan({
directory: "value",
include_subdirs: true,
response_format: "value",
});Description: Analyze directory health and get actionable suggestions for organization.
| Parameter | Type | Description | Default |
|---|---|---|---|
directory |
string | Directory to analyze | - |
include_subdirs |
boolean | Include subdirectories | true |
include_duplicates |
boolean | Check for duplicates (slower) | true |
max_files |
number | Maximum files to scan | 10000 |
timeout_seconds |
number | Timeout in seconds | 60 |
sample_rate |
number | Sample rate for large dirs | 1 |
use_cache |
boolean | Use cached results | true |
response_format |
string | 'json' or 'markdown' | 'markdown' |
file_organizer_smart_suggest({
directory: "~/Downloads",
});Description: Organize files into OS-standard system directories (Music, Documents, Pictures, Videos). Source must be Downloads, Desktop, or Temp.
| Parameter | Type | Description | Default |
|---|---|---|---|
source_dir |
string | Source directory (Downloads, Desktop, or Temp) | - |
use_system_dirs |
boolean | Use OS system directories | true |
create_subfolders |
boolean | Create organized subfolders | true |
fallback_to_local |
boolean | Fallback to local folder if system dir not writable | true |
local_fallback_prefix |
string | Single folder name (no separators) for local fallback | 'Organized' |
conflict_strategy |
string | 'skip', 'rename', or 'overwrite' | 'rename' |
dry_run |
boolean | Preview without moving | true |
copy_instead_of_move |
boolean | Copy instead of move | false |
response_format |
string | 'json' or 'markdown' | 'markdown' |
file_organizer_system_organize({
source_dir: "~/Downloads",
dry_run: true,
});Description: Reverses file moves and renames from a previous organization task. Pass manifest_id to undo one specific operation by the id that organize_files returned and file_organizer_view_history lists; omit it to undo the most recent operation. Undoing an operation that is not the newest is refused outright when a newer operation already moved any of the same paths, because undoing out of order would collide with it partway through. Undo the newer one first, then retry. The manifest is deleted only when the whole undo succeeds, so a refused or failed undo can be tried again.
| Parameter | Type | Description | Default |
|---|---|---|---|
manifest_id |
string | ID of the operation to undo, a UUID in 8-4-4-4-12 form. Omit to undo the most recent operation. | - |
response_format |
string | 'json' or 'markdown' | 'markdown' |
A malformed manifest_id is a validation error rather than a lookup miss, so a typo is reported as a typo instead of arriving back as "manifest not found".
A refusal returns success: 0 and failed: 1 with an error naming the conflicting manifest ids. No file is moved when that happens. The read-apply-delete cycle runs under a lock on the rollback directory, so two concurrent undoes of the same id cannot both succeed: one wins, and the other is told the manifest is already spent.
file_organizer_undo_last_operation({
manifest_id: "3f1c8b2e-9a4d-4e77-b0c5-1d2e3f4a5b6c",
response_format: "json",
});Description: Checks the organization plan organize_files would execute and returns an ok / not-ok verdict. Read-only: nothing is moved, renamed, or deleted. Flags two or more sources landing on one destination name, destinations that already exist, moves that cross a device boundary (compared with fs.stat device ids), and sources the sensitive-file gate would refuse. Reports checked and not_checked so a clean result is not read as a guarantee. Builds the plan with the same conflict_strategy organize_files would use; pass include_subdirs=true to check a plan over subdirectories, which organize_files itself does not scan. Every path it reports (directory, sources, destinations) is an absolute filesystem path in the platform's native form, byte-identical to what preview_organization and organize_files report for the same plan; no separator normalization is applied, so match them with path-aware logic rather than string equality.
| Parameter | Type | Description | Default |
|---|---|---|---|
directory |
string | Full path to the directory | - |
include_subdirs |
boolean | Validate a plan built over subdirectories. Default false matches the depth organize_files scans | false |
response_format |
string | - | 'markdown' |
conflict_strategy |
string | How to handle file conflicts for the validated plan. Uses config default if not specified | - |
{
// Absolute filesystem path, platform-native form (see description).
directory: string;
// False when any finding has severity "error".
ok: boolean;
moves_checked: number;
counts: { error: number; warning: number };
findings: Array<{
kind:
| "destination_name_collision"
| "destination_exists"
| "cross_device_move"
| "sensitive_source"
| "incomplete_plan";
severity: "error" | "warning";
// Absolute filesystem paths, platform-native form.
sources: string[];
destinations: string[];
detail: string;
}>;
// What this check looked at, and what it did not.
checked: string[];
not_checked: string[];
}file_organizer_validate_organization_plan({
directory: "value",
include_subdirs: true,
response_format: "value",
conflict_strategy: "value",
});Description: Rehashes the files a rollback manifest names and reports which have drifted since that operation ran: unchanged, modified, or missing. Read-only; it never moves, restores, or deletes anything. A manifest only stores a content digest for the files it could hash inside a 32 MB read budget, and manifests written before content hashing existed store none at all, so those files are reported as unverifiable and never as unchanged. verified is only true when every file the manifest names was rehashed and matched. Use it after organizing to confirm the files are still where they were put.
| Parameter | Type | Description | Default |
|---|---|---|---|
manifest_id |
string | ID of the operation to verify, a UUID in 8-4-4-4-12 form. If omitted, verifies the last operation. | - |
response_format |
string | 'json' or 'markdown' | 'markdown' |
| Field | Type | Description |
|---|---|---|
manifest_id |
string | Manifest that was checked |
description |
string | Operation the manifest recorded |
recorded_at |
number | When the operation ran |
total_files |
number | Files the manifest names |
checked |
number | Files actually rehashed and compared |
unchanged |
number | Rechecked and byte-identical |
modified |
number | Rechecked and changed |
missing |
number | Gone since the operation |
unverifiable |
number | Could not be checked: no recorded digest, outside the allowed roots, or unreadable |
drift_detected |
boolean | Any file modified or missing |
verified |
boolean | True only when every file was rechecked and matched |
files |
array | One entry per action: path, status, and reason / expected_hash / actual_hash |
A status is unchanged, modified, missing, or unverifiable. unverifiable
is never counted as unchanged, and it keeps verified false.
file_organizer_verify_integrity({
manifest_id: "value",
response_format: "value",
});Description: View the history of file organization operations. Supports filtering by date range, operation type, status, and source. Use privacy_mode to control output detail level.
| Parameter | Type | Description | Default |
|---|---|---|---|
limit |
number | Maximum number of entries to return (1-1000) | 20 |
since |
string | ISO date string - return entries after this time | - |
until |
string | ISO date string - return entries before this time | - |
operation |
string | Filter by operation name | - |
status |
string | 'success', 'error', or 'partial' | - |
source |
string | 'manual' or 'scheduled' | - |
privacy_mode |
string | 'full', 'redacted', or 'none' | - |
response_format |
string | 'json' or 'markdown' | 'markdown' |
Entries may carry a paths array — the paths that operation touched, recorded
when the tool call named a directory. privacy_mode treats it like the other
path-bearing fields: redacted in redacted, absent in none.
file_organizer_search_history filters on it.
Entries that wrote a rollback manifest also carry manifestId, shown in the
Undo Manifest column. Pass it to
file_organizer_undo_last_operation to
undo that specific operation rather than whatever ran last. It is an id, not a
path, so privacy_mode leaves it as it is. Entries with no manifest report
- in that column: undoing them is only possible as "the newest operation".
file_organizer_view_history({
limit: 20,
});Description: Search the file organization history. Filter entries by path glob, date range (from / to), operation type, status, or source — every filter is optional and the ones you pass combine. Reads the same history as file_organizer_view_history; use view_history for the plain newest-first list and this tool when the list has grown past that.
Read-only. No path on disk is read or written by the filters — the glob is matched against the paths already recorded in each history entry, not against the filesystem.
| Parameter | Type | Description | Default |
|---|---|---|---|
path_glob |
string | Glob matched against the paths each entry recorded | - |
from |
string | ISO date string - return entries at or after this time | - |
to |
string | ISO date string - return entries at or before this time | - |
operation |
string | Filter by operation name | - |
status |
string | 'success', 'error', or 'partial' | - |
source |
string | 'manual' or 'scheduled' | - |
limit |
number | Maximum number of entries to return (1-1000) | 20 |
privacy_mode |
string | 'full', 'redacted', or 'none' | - |
response_format |
string | 'json' or 'markdown' | 'markdown' |
One pattern is tried three ways against each recorded path, so whichever form you write works:
| Pattern | Matches |
|---|---|
**/Downloads |
the full recorded path, e.g. /home/you/Downloads |
**/Downloads/** |
anything under that directory |
*.pdf |
the bare filename, so the pattern does not need the full path |
Recorded Windows paths are matched with \ folded to /, so **/Downloads still matches C:\Users\you\Downloads. path_glob is bounded like any other path input (non-empty, no null byte, no ..), and a pattern minimatch cannot compile is rejected as a ValidationError instead of quietly matching nothing.
An entry only matches a path_glob if it recorded a path. Operations that never touch a directory (file_organizer_get_categories, a failed call with no directory argument) do not match any glob.
file_organizer_search_history({
path_glob: "**/Downloads/**",
from: "2026-01-01T00:00:00.000Z",
operation: "file_organizer_organize_files",
limit: 20,
});Description: Report the effective configuration after defaults, config.json and env are layered, and flag every configured allowed directory that is missing, blocked by security policy, or rejected by the home-directory gate. Use this first when a call fails unexpectedly.
Read-only. Safe to call at any time; changes nothing on disk.
| Parameter | Type | Description | Default |
|---|---|---|---|
response_format |
string | 'json' or 'markdown' | 'markdown' |
| Field | Description |
|---|---|
version |
Server version |
platform |
process.platform the report was built on |
config_file_present |
Whether a config.json was found (false means defaults only) |
security |
Effective security settings after config.json is layered over defaults |
conflict_strategy |
Effective conflict strategy |
allow_external_volumes |
Whether external volumes are allowed |
custom_rule_count |
Number of custom categorization rules |
default_allowed |
Platform default allowed roots that exist |
configured_allowed_dirs |
One entry per customAllowedDirectories entry |
effective_allowed_dirs |
The configured entries the security gate kept, with ~ expanded |
unknown_config_keys |
config.json keys the loader does not understand |
problems |
Human-readable list of what is wrong |
healthy |
True when problems is empty |
Each configured_allowed_dirs entry carries configured (as written in
config.json), resolved (~ expanded), exists, is_directory, symlink,
accepted, optional rejection (missing, not_a_directory, symlink,
path_traversal, null_byte, outside_home,
external_volume_not_allowed), and blocked_by_policy.
blocked_by_policy is independent of accepted. The always-blocked pattern
list is enforced per request, so a directory can be accepted by the config gate
and still be rejected when a tool touches it. A missing directory is reported
as a likely typo, a blocked_by_policy directory is reported as unusable, and
the two problems read differently so they can be told apart.
file_organizer_doctor({
response_format: "json",
});Description: Bundle the user config — allowed directories, categorization
rules, conflict strategy, watch entries, auto-organize and history settings —
into one JSON document for another machine. The directory paths are absolute and
machine-specific, so the reply always states which fields must be edited on the
target; pass rebase_root to emit ~/-relative paths instead.
Reads the config file and never writes it. With output_path it writes the
bundle there, through the same path validation as every other tool, and the
write refuses to overwrite an existing file.
Not read-only (it can write the bundle file), not destructive (it never
deletes or replaces anything), not idempotent (a second run against the same
output_path fails on the existing file).
| Parameter | Type | Description | Default |
|---|---|---|---|
output_path |
string | Where to write the bundle JSON. Omit to receive the bundle in the reply, no write. | - |
rebase_root |
string | Directory on this machine that the target's home occupies. Paths under it export as ~/relative. |
- |
response_format |
string | 'json' or 'markdown' | 'markdown' |
| Field | Description |
|---|---|
format_version |
Bundle format version (1) |
mode |
absolute (paths as configured) or rebased (~/-relative) |
rebase_root |
The root used for rebasing, or null in absolute mode |
output_path |
Where the bundle was written, or null when nothing was written |
written |
True when a bundle file was written |
bytes_written |
Size of the written bundle |
config_file_present |
Whether a config.json was found behind this export |
counts |
Entry counts for allowed directories, custom rules, rules, watches |
requires_editing |
Fields a user must edit by hand on the target machine |
non_portable_paths |
Paths that could not be rebased, with the field each came from |
notes |
Human-readable statements about portability |
config |
The exported config subset — a superset-free copy of what the loader understands |
bundle.config holds every config key the loader understands, so merging it
into a target machine's config.json reproduces the source config's shape. The
directory half does not travel on its own:
absolutemode exportscustomAllowedDirectoriesandwatchList[].directoryverbatim and lists both inrequires_editing, because they are absolute paths of the exporting machine.rebasedmode rewrites each of those paths underrebase_rootas~/relative(forward slashes on every platform). A value already written as~/…is left alone. Anything outside the root — an external volume, a system path — has no portable spelling, so it is exported unchanged and named innon_portable_pathswithreason: outside_rebase_root.
The bundle document on disk is { format_version, exported_by, exported_at, config, portability }.
file_organizer_export_config({
output_path: "~/fom-config-bundle.json",
rebase_root: "~",
response_format: "json",
});Description: Write a health report for a directory — total files and bytes, the space each category holds, duplicate groups with the space they waste, and the largest files. One directory walk feeds all four sections, so they cannot describe different moments in time. Nothing is moved, renamed or deleted.
There is no dry_run flag, and that is deliberate. output_path is optional, and
omitting it is the only path that writes nothing: the tool reads, assembles, and
returns. So "preview" cannot be forgotten, and a caller cannot reach the
filesystem by accident.
Not read-only (it can write the report file), not destructive (the write
uses the exclusive wx flag and never replaces an existing file), not
idempotent (a second run at the same output_path fails on the existing file,
and generated_at moves even when the directory has not).
| Parameter | Type | Description | Default |
|---|---|---|---|
directory |
string | Full path to the directory to report on. Required. | - |
include_subdirs |
boolean | Recurse into subdirectories | true |
top_n |
number | How many of the largest files to report (1-100) | 10 |
duplicate_limit |
number | How many duplicate groups to list (0-1000). Totals always cover every group. | 10 |
output_path |
string | Where to write the report. Omit to receive it in the reply, no write. | - |
response_format |
string | 'json' or 'markdown' | 'markdown' |
include_subdirs defaults to true here, unlike scan_directory and
organize_files, because the space a category holds usually sits below the
directory you point at and a report that skipped it would understate both the
totals and the duplicates.
| Field | Description |
|---|---|
directory |
The validated directory the report covers |
include_subdirs |
Whether the walk recursed, echoed so the totals can be read correctly |
generated_at |
ISO timestamp of the run |
output_path |
Where the report was written, or null when nothing was written |
written |
True when a report file was written |
bytes_written |
Size of the written report |
scan |
total_files, total_size, total_size_readable over the whole walk |
categories |
Per category: file count, bytes, readable size, percent of total. Same numbers as disk_usage_by_category. |
duplicates |
total_groups, total_files, wasted_space (+ readable), groups_listed, the listed groups, skipped_count, skipped_bytes |
top_files |
The top_n largest files, each with name, path, bytes, readable size |
limits |
Prose statements about everything the numbers above under-report |
A health report gets filed and believed, so a number that means less than it
looks has to say so in the same document. limits states, in prose:
- how many duplicate groups were found but not listed (
duplicate_limit); - how many files were not compared for duplicates, because empty files and files over the hashing size cap cannot be compared. The duplicate totals are a lower bound whenever this is non-empty;
- how many files were left out of
top_files(top_n); - that
include_subdirswas false, so everything below the directory went uncounted.
The totals themselves are never truncated. duplicates.total_groups and
wasted_space always cover every group found; duplicate_limit only trims the
list. A report that claimed twelve groups and then showed two would be
misleading in the direction that matters.
Every path here is a real filesystem path the scanner returned, so it carries
whatever separator the platform uses. A report is mostly file paths, which makes
it a live surface for the portability leak classes in AGENTS.md rule 3: build a
contract string such as a folder label with a template literal, never with
path.join.
// Write the artifact.
file_organizer_export_report({
directory: "~/Documents",
output_path: "~/Documents/health-report.json",
response_format: "json",
});
// Or just look, and write nothing.
file_organizer_export_report({ directory: "~/Documents" });Description: Organize music files into structured folders based on metadata (Artist/Album/Title). Supports MP3, FLAC, OGG, WAV, M4A, AAC formats.
| Parameter | Type | Description | Default |
|---|---|---|---|
source_dir |
string | Full path to directory containing music files | - |
target_dir |
string | Full path where organized music will be placed | - |
structure |
string | Folder structure: 'artist/album', 'album', 'genre/artist', 'flat' | 'artist/album' |
filename_pattern |
string | Rename pattern: '{track} - {title}', '{artist} - {title}', '{title}' | '{track} - {title}' |
dry_run |
boolean | Preview changes without moving files | true |
copy_instead_of_move |
boolean | Copy files instead of moving them | false |
skip_if_missing_metadata |
boolean | Skip files missing artist/album metadata | false |
response_format |
string | Output format | 'markdown' |
file_organizer_organize_music({
source_dir: "/Users/Music/Downloads",
target_dir: "/Users/Music/Organized",
structure: "artist/album",
dry_run: true,
});Description: Organize photos into date-based folders using EXIF metadata. Supports JPEG, PNG, TIFF, HEIC, and RAW formats. Can strip GPS data for privacy.
| Parameter | Type | Description | Default |
|---|---|---|---|
source_dir |
string | Full path to directory containing photos | - |
target_dir |
string | Full path where organized photos will be placed | - |
date_format |
string | Date folder structure: 'YYYY/MM/DD', 'YYYY-MM-DD', 'YYYY/MM', 'YYYY' | 'YYYY/MM' |
group_by_camera |
boolean | Group photos by camera model within date folders | false |
strip_gps |
boolean | Strip GPS location data from photos | false |
unknown_date_folder |
string | Single folder name (no separators) for photos without date metadata | 'Unknown Date' |
dry_run |
boolean | Preview changes without moving files | true |
copy_instead_of_move |
boolean | Copy files instead of moving them | false |
response_format |
string | Output format | 'markdown' |
file_organizer_organize_photos({
source_dir: "/Users/Photos/Import",
target_dir: "/Users/Photos/Organized",
date_format: "YYYY/MM",
strip_gps: true,
dry_run: true,
});Description: Reads contents of all files in a specified folder for LLM context. For text files (documents, code, notes), reads the actual content. For media files (audio, video, images), reads metadata instead of binary content. Provides a comprehensive summary of folder contents.
| Parameter | Type | Description | Default |
|---|---|---|---|
directory |
string | Full path to the directory containing files to read | - |
include_subdirs |
boolean | Include subdirectories in the batch read | false |
max_files |
number | Maximum number of files to process (safety limit) | 50 |
max_file_size_mb |
number | Maximum file size in MB to read content (larger files get metadata only) | 10 |
include_content |
boolean | Include file content for text files | true |
include_metadata |
boolean | Include metadata for all files | true |
file_types |
array | Filter by specific file extensions (e.g., [".txt", ".pdf"]) |
- |
response_format |
string | Output format: 'markdown' or 'json' |
'markdown' |
file_organizer_batch_read_files({
directory: "/path/to/folder",
include_subdirs: false,
max_files: 50,
file_types: [".txt", ".md", ".json"],
});Description: Sort any file into YYYY/MM folders. Photos use EXIF DateTimeOriginal (CreateDate when that is absent); everything else uses the file's modification time. Every file reports which source chose its folder, so an EXIF→mtime fallback is never silent. Files with no usable date stay where they are and are listed under "Left In Place" — there is no Unknown Date bucket.
| Parameter | Type | Description | Default |
|---|---|---|---|
source_dir |
string | Directory containing files to sort | - |
target_dir |
string | Directory where the date folders are created. Cannot be inside source_dir, and source_dir cannot be inside it |
- |
date_format |
string | Folder structure: 'YYYY/MM', 'YYYY/MM/DD', 'YYYY' |
'YYYY/MM' |
date_source |
string | 'auto' (EXIF, else mtime), 'exif' (EXIF only — files without one are left in place), or 'mtime' |
'auto' |
recursive |
boolean | Scan subdirectories of source_dir |
false |
dry_run |
boolean | Preview the folders without moving files | true |
response_format |
string | Output format: 'markdown' or 'json' |
'markdown' |
| Field | Description |
|---|---|
organizedFiles |
Files moved (or that a dry run would move) |
skippedFiles |
Files left alone: no usable date, unsafe name, or a failed move |
moves[] |
{ file, from, to, folder, date, calendarDate, dateSource } per file; dateSource = exif/mtime |
noDateFiles[] |
Files left in place because no usable date was found |
structure |
Date folder label -> file names (same strings as moves[].folder) |
manifestId |
Rollback manifest for undo_last_operation; absent after a dry run |
undoAvailable |
false unless this run's moves are recorded in a manifest |
errors[] |
Per-file failures, sanitized |
Folder labels vs. paths. moves[].folder and the structure keys are
logical labels in the documented YYYY/MM form, always separated by / on
every platform — 2024/05 means two levels on Windows exactly as it does on
Linux, so agents and scripts can match on them. moves[].from and moves[].to
are real filesystem paths, absolute and platform-native, and use the
platform separator.
Which calendar a folder uses. EXIF is camera wall-clock data that
exif-parser anchors to UTC, so an EXIF folder is read in UTC — a photo
stamped 00:30 on 1 January files under 2024/01 in every timezone. mtime is
a true instant, so its folder is the user's local day.
moves[].calendarDate (YYYY-MM-DD) is the date the label was cut from, so
folder is always calendarDate truncated to the requested date_format and a
caller never has to re-derive the timezone to predict the folder. moves[].date
stays the exact instant.
A destination whose parent resolves outside target_dir — a directory symlink
inside the target pointing out of it — is refused before anything is written, and
reported in errors[]. Nothing is moved and nothing is recorded as undoable.
A destination name that is already taken is never overwritten: the file lands as name (1).ext. Both dry_run defaults to true, and every performed move is recorded in a rollback manifest.
file_organizer_organize_by_date({
source_dir: "/path/to/import",
target_dir: "/path/to/library",
date_format: "YYYY/MM",
date_source: "auto",
dry_run: true,
});Description: Group files across all types (documents, code, images) into detected project folders. Detection is deterministic and local-only: rarity-weighted shared name tokens (primary anchor), IDF-filtered shared content terms from text-like files (.txt, .md, code, .json, etc.), and explicit identifier markers (e.g. ABC123). Content-blind files (binary, image) join only via a shared name token or marker, never on time alone.
| Parameter | Type | Description | Default |
|---|---|---|---|
source_dir |
string | Directory containing files to organize | - |
target_dir |
string | Directory where detected projects will be placed | - |
dry_run |
boolean | Preview the grouping without moving files | true |
recursive |
boolean | Scan subdirectories recursively | true |
response_format |
string | Output format | 'markdown' |
file_organizer_organize_by_project({
source_dir: "/path/to/source",
target_dir: "/path/to/target",
dry_run: true,
});