@amirdaraee/namewise 0.6.1 → 0.7.0

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.
Files changed (135) hide show
  1. package/CHANGELOG.md +43 -1
  2. package/README.md +258 -9
  3. package/dist/cli/apply.d.ts +4 -0
  4. package/dist/cli/apply.d.ts.map +1 -0
  5. package/dist/cli/apply.js +66 -0
  6. package/dist/cli/apply.js.map +1 -0
  7. package/dist/cli/clean-empty.d.ts +4 -0
  8. package/dist/cli/clean-empty.d.ts.map +1 -0
  9. package/dist/cli/clean-empty.js +48 -0
  10. package/dist/cli/clean-empty.js.map +1 -0
  11. package/dist/cli/commands.d.ts.map +1 -1
  12. package/dist/cli/commands.js +299 -14
  13. package/dist/cli/commands.js.map +1 -1
  14. package/dist/cli/config-cmd.d.ts +2 -0
  15. package/dist/cli/config-cmd.d.ts.map +1 -0
  16. package/dist/cli/config-cmd.js +71 -0
  17. package/dist/cli/config-cmd.js.map +1 -0
  18. package/dist/cli/dedup.d.ts +5 -0
  19. package/dist/cli/dedup.d.ts.map +1 -0
  20. package/dist/cli/dedup.js +58 -0
  21. package/dist/cli/dedup.js.map +1 -0
  22. package/dist/cli/diff.d.ts +6 -0
  23. package/dist/cli/diff.d.ts.map +1 -0
  24. package/dist/cli/diff.js +75 -0
  25. package/dist/cli/diff.js.map +1 -0
  26. package/dist/cli/find.d.ts +11 -0
  27. package/dist/cli/find.d.ts.map +1 -0
  28. package/dist/cli/find.js +63 -0
  29. package/dist/cli/find.js.map +1 -0
  30. package/dist/cli/flatten.d.ts +4 -0
  31. package/dist/cli/flatten.d.ts.map +1 -0
  32. package/dist/cli/flatten.js +79 -0
  33. package/dist/cli/flatten.js.map +1 -0
  34. package/dist/cli/info.d.ts +2 -0
  35. package/dist/cli/info.d.ts.map +1 -0
  36. package/dist/cli/info.js +65 -0
  37. package/dist/cli/info.js.map +1 -0
  38. package/dist/cli/init.d.ts +2 -0
  39. package/dist/cli/init.d.ts.map +1 -0
  40. package/dist/cli/init.js +139 -0
  41. package/dist/cli/init.js.map +1 -0
  42. package/dist/cli/organize.d.ts +7 -0
  43. package/dist/cli/organize.d.ts.map +1 -0
  44. package/dist/cli/organize.js +39 -0
  45. package/dist/cli/organize.js.map +1 -0
  46. package/dist/cli/rename.d.ts +12 -1
  47. package/dist/cli/rename.d.ts.map +1 -1
  48. package/dist/cli/rename.js +143 -14
  49. package/dist/cli/rename.js.map +1 -1
  50. package/dist/cli/sanitize.d.ts +7 -0
  51. package/dist/cli/sanitize.d.ts.map +1 -0
  52. package/dist/cli/sanitize.js +49 -0
  53. package/dist/cli/sanitize.js.map +1 -0
  54. package/dist/cli/stats.d.ts +4 -0
  55. package/dist/cli/stats.d.ts.map +1 -0
  56. package/dist/cli/stats.js +35 -0
  57. package/dist/cli/stats.js.map +1 -0
  58. package/dist/cli/tree.d.ts +4 -0
  59. package/dist/cli/tree.d.ts.map +1 -0
  60. package/dist/cli/tree.js +65 -0
  61. package/dist/cli/tree.js.map +1 -0
  62. package/dist/cli/undo.d.ts +1 -0
  63. package/dist/cli/undo.d.ts.map +1 -1
  64. package/dist/cli/undo.js +40 -2
  65. package/dist/cli/undo.js.map +1 -1
  66. package/dist/cli/watch.d.ts +2 -0
  67. package/dist/cli/watch.d.ts.map +1 -0
  68. package/dist/cli/watch.js +128 -0
  69. package/dist/cli/watch.js.map +1 -0
  70. package/dist/index.js +14 -10
  71. package/dist/index.js.map +1 -1
  72. package/dist/parsers/pdf-parser.js +2 -2
  73. package/dist/parsers/pdf-parser.js.map +1 -1
  74. package/dist/services/claude-service.d.ts +1 -1
  75. package/dist/services/claude-service.d.ts.map +1 -1
  76. package/dist/services/claude-service.js +9 -5
  77. package/dist/services/claude-service.js.map +1 -1
  78. package/dist/services/file-renamer.d.ts +1 -0
  79. package/dist/services/file-renamer.d.ts.map +1 -1
  80. package/dist/services/file-renamer.js +17 -5
  81. package/dist/services/file-renamer.js.map +1 -1
  82. package/dist/services/lmstudio-service.d.ts +1 -1
  83. package/dist/services/lmstudio-service.d.ts.map +1 -1
  84. package/dist/services/lmstudio-service.js +6 -5
  85. package/dist/services/lmstudio-service.js.map +1 -1
  86. package/dist/services/ollama-service.d.ts +1 -1
  87. package/dist/services/ollama-service.d.ts.map +1 -1
  88. package/dist/services/ollama-service.js +6 -5
  89. package/dist/services/ollama-service.js.map +1 -1
  90. package/dist/services/openai-service.d.ts +1 -1
  91. package/dist/services/openai-service.d.ts.map +1 -1
  92. package/dist/services/openai-service.js +9 -5
  93. package/dist/services/openai-service.js.map +1 -1
  94. package/dist/types/index.d.ts +4 -1
  95. package/dist/types/index.d.ts.map +1 -1
  96. package/dist/utils/ai-prompts.d.ts +1 -0
  97. package/dist/utils/ai-prompts.d.ts.map +1 -1
  98. package/dist/utils/ai-prompts.js +3 -2
  99. package/dist/utils/ai-prompts.js.map +1 -1
  100. package/dist/utils/batch-rename.d.ts +8 -0
  101. package/dist/utils/batch-rename.d.ts.map +1 -0
  102. package/dist/utils/batch-rename.js +36 -0
  103. package/dist/utils/batch-rename.js.map +1 -0
  104. package/dist/utils/config-loader.d.ts +3 -0
  105. package/dist/utils/config-loader.d.ts.map +1 -1
  106. package/dist/utils/config-loader.js.map +1 -1
  107. package/dist/utils/dedup.d.ts +3 -0
  108. package/dist/utils/dedup.d.ts.map +1 -0
  109. package/dist/utils/dedup.js +29 -0
  110. package/dist/utils/dedup.js.map +1 -0
  111. package/dist/utils/fs-collect.d.ts +7 -0
  112. package/dist/utils/fs-collect.d.ts.map +1 -0
  113. package/dist/utils/fs-collect.js +28 -0
  114. package/dist/utils/fs-collect.js.map +1 -0
  115. package/dist/utils/naming-conventions.d.ts +7 -0
  116. package/dist/utils/naming-conventions.d.ts.map +1 -1
  117. package/dist/utils/naming-conventions.js +9 -0
  118. package/dist/utils/naming-conventions.js.map +1 -1
  119. package/dist/utils/organize.d.ts +8 -0
  120. package/dist/utils/organize.d.ts.map +1 -0
  121. package/dist/utils/organize.js +41 -0
  122. package/dist/utils/organize.js.map +1 -0
  123. package/dist/utils/pattern-rename.d.ts +6 -0
  124. package/dist/utils/pattern-rename.d.ts.map +1 -0
  125. package/dist/utils/pattern-rename.js +23 -0
  126. package/dist/utils/pattern-rename.js.map +1 -0
  127. package/dist/utils/sanitizer.d.ts +3 -0
  128. package/dist/utils/sanitizer.d.ts.map +1 -0
  129. package/dist/utils/sanitizer.js +11 -0
  130. package/dist/utils/sanitizer.js.map +1 -0
  131. package/dist/utils/stats.d.ts +16 -0
  132. package/dist/utils/stats.d.ts.map +1 -0
  133. package/dist/utils/stats.js +26 -0
  134. package/dist/utils/stats.js.map +1 -0
  135. package/package.json +2 -1
package/CHANGELOG.md CHANGED
@@ -5,7 +5,49 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [Unreleased]
8
+ ## [0.7.0] - 2026-04-08
9
+
10
+ ### Added
11
+ - `namewise sanitize [dir]` — clean filenames without AI: strips unsafe characters, normalises unicode (NFC), and applies any naming convention; supports `--dry-run` and `--recursive`
12
+ - `namewise dedup [dir]` — find duplicate files by SHA-256 content hash; prints file paths and sizes per group, keeps the lexicographically earliest path; `--delete` prompts for confirmation before removing duplicates; supports `--recursive`
13
+ - `namewise watch [dir]` — monitor a directory for new files and automatically rename them through the full rename pipeline; accepts all `rename` flags (`--provider`, `--no-ai`, `--pattern`, etc.); shuts down gracefully on SIGINT/SIGTERM; requires `chokidar`
14
+ - `namewise apply <plan.json>` — execute a saved rename plan produced by `--output`; validates that source files exist and targets are conflict-free before applying; supports `--dry-run`
15
+ - `namewise config <list|get|set>` — manage `~/.namewise.json` from the CLI without editing JSON manually; validates keys against the known config schema
16
+ - `rename --pattern <pattern>` — regex find-and-replace on filename stems, chainable; supports sed-style `s/find/replace/flags` and plain `find:replace` format; skips AI entirely when used
17
+ - `rename --no-ai` — rename files using extracted metadata (title, author, creation date) with no API call; parsers still run; all templates and naming conventions still apply
18
+ - `undo --all` — undo all non-dry-run history sessions at once; prompts for confirmation when more than one session is affected
19
+ - Enhanced rename stats: elapsed time, total MB processed, and per-extension file count breakdown printed at the end of every `rename` run
20
+ - `namewise stats [dir]` — show total file count and storage breakdown by file type, with largest-files list; supports `--recursive`
21
+ - `namewise tree [dir]` — print visual directory tree with per-file sizes and per-folder file counts; `--depth <n>` limits recursion depth
22
+ - `namewise info <path>` — display detailed metadata for a file (size, extension, SHA-256 hash, created/modified dates) or directory (total file count, subdirectory count, total size)
23
+ - `namewise organize [dir]` — move files into subfolders organised by extension (`--by ext`), modification date (`--by date`), or file size (`--by size`); supports `--dry-run` and `--recursive`; tracks moves in history for undo
24
+ - `namewise flatten [dir]` — move all files from nested subdirectories up to the root directory; auto-resolves name conflicts with `-1`, `-2` suffixes; supports `--dry-run`; tracks moves in history
25
+ - `namewise clean-empty [dir]` — recursively find and remove empty directories; supports `--dry-run`
26
+ - `namewise find [dir]` — search files by extension (`--ext`), name glob (`--name`), size range (`--larger-than` / `--smaller-than`), or date range (`--newer-than` / `--older-than`); reports match count
27
+ - `namewise diff <dir1> <dir2>` — compare two directories by filename (`--by name`) or content hash (`--by hash`); hash mode detects moved/renamed files; reports difference count
28
+ - Batch rename flags on `rename` (no AI required): `--sequence` (sequential numbering), `--sequence-prefix <p>`, `--prefix <text>`, `--suffix <text>`, `--date-stamp created|modified`, `--strip <pattern>`, `--truncate <n>`
29
+ - `namewise init` — interactive first-time setup wizard; prompts for scope (global or project), provider, API key, base URL for local LLMs, model, naming convention, dry-run default, and personal name; writes to `~/.namewise.json` or `./.namewise.json`; detects and offers to overwrite existing config
30
+ - `apiKey` and `dryRun` fields added to the config file schema (`~/.namewise.json`); `namewise config get/set` now supports both keys; `rename` reads `apiKey` and `dryRun` from config so no flags are needed after `namewise init`
31
+ - `language` setting for AI-generated filenames: set a default output language (e.g. `English`, `French`) so filenames are always produced in that language regardless of the document's original language; configurable via `--language <lang>` flag, `namewise init` wizard, or `namewise config set language English`; stored in `~/.namewise.json` or `.namewise.json`
32
+ - Shared `collectFiles` utility (`src/utils/fs-collect.ts`) extracted from duplicated code in `dedup` and `sanitize`
33
+
34
+ ### Changed
35
+ - `undo` now accepts `--all` flag in addition to an optional session ID
36
+ - `rename` now reads `apiKey` and `dryRun` from the config file (set by `namewise init`), so cloud API keys no longer need to be passed on every run
37
+
38
+ ## [0.6.2] - 2026-04-02
39
+
40
+ ### Fixed
41
+ - `ClaudeService` and `OpenAIService` now explicitly strip Windows-illegal filename characters (`< > : " / \ | ? *`) before applying the naming convention, making all four AI providers consistent; previously these characters were silently removed by `applyNamingConvention` but the sanitisation intent was invisible
42
+ - Added `stripWindowsIllegalChars` shared utility in `naming-conventions.ts` used by Claude and OpenAI services (Ollama and LMStudio already handled these inline)
43
+
44
+ ### Changed
45
+ - CI test matrix now runs on `ubuntu-latest`, `windows-latest`, and `macos-latest` across Node.js 20, 22, and 24 (9 jobs total); coverage upload is gated to the ubuntu/Node 24 job only
46
+
47
+ ### Tests
48
+ - Added unit tests for `stripWindowsIllegalChars` covering all 9 illegal characters and real-world AI response patterns
49
+ - Added Windows-illegal character sanitisation tests for all four AI services (`ClaudeService`, `OpenAIService`, `OllamaService`, `LMStudioService`)
50
+ - Added cross-platform `birthtime` test documenting that `FileInfo.createdAt` is correctly populated when `stat.birthtime === stat.mtime` (Linux filesystem behaviour where creation time is not stored)
9
51
 
10
52
  ## [0.6.1] - 2026-04-02
11
53
 
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Namewise
2
2
 
3
- [![Tests](https://img.shields.io/badge/tests-457%20passing-brightgreen.svg)](#testing--development)
3
+ [![Tests](https://img.shields.io/badge/tests-778%20passing-brightgreen.svg)](#testing--development)
4
4
  [![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen.svg)](#testing--development)
5
5
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue.svg)](https://www.typescriptlang.org/)
6
6
  [![Node.js](https://img.shields.io/badge/Node.js-20+-green.svg)](https://nodejs.org/)
@@ -16,8 +16,25 @@ Automatically rename files based on their content using AI providers (Claude, Op
16
16
 
17
17
  - **AI-Powered Renaming**: Uses cloud providers (Claude, OpenAI) or local LLMs (Ollama, LMStudio) to generate descriptive filenames
18
18
  - **Privacy First**: Local LLM support means your files never leave your machine
19
+ - **No-AI Mode**: Rename using file metadata only — no API key required (`--no-ai`)
20
+ - **Interactive Setup**: First-time wizard to configure provider, API key, naming convention and more (`namewise init`)
21
+ - **Batch Rename (no AI)**: Sequence-number, prefix/suffix, date-stamp, strip, or truncate filenames in bulk (`--sequence`, `--prefix`, `--suffix`, `--date-stamp`, `--strip`, `--truncate`)
22
+ - **Pattern Rename**: Regex/sed-style find-and-replace on filenames, chainable, no AI needed (`--pattern`)
23
+ - **Watch Mode**: Monitor a directory and auto-rename new files as they arrive (`namewise watch`)
24
+ - **Sanitize**: Clean filenames by removing unsafe characters and applying naming conventions (`namewise sanitize`)
25
+ - **Dedup**: Find and optionally delete duplicate files by content hash (`namewise dedup`)
26
+ - **Apply Plans**: Execute a saved rename plan from a previous `--output` report (`namewise apply`)
27
+ - **Config Management**: Manage `~/.namewise.json` from the CLI (`namewise config get|set|list`)
28
+ - **Storage Stats**: Show file count and size breakdown by type (`namewise stats`)
29
+ - **Tree View**: Visual directory tree with file sizes and per-folder summaries (`namewise tree`)
30
+ - **File Info**: Detailed metadata for any file or directory including SHA-256 hash (`namewise info`)
31
+ - **Organize**: Move files into subfolders by extension, date, or size (`namewise organize`)
32
+ - **Flatten**: Move all nested files up to the root directory (`namewise flatten`)
33
+ - **Clean Empty Dirs**: Find and remove empty directories recursively (`namewise clean-empty`)
34
+ - **Find**: Search files by extension, name glob, size range, or date range (`namewise find`)
35
+ - **Diff Directories**: Compare two directories by filename or content hash (`namewise diff`)
19
36
  - **Recursive Scanning**: Scan nested directories with an optional depth limit
20
- - **Undo Support**: Reverse any previous rename session via `namewise undo`
37
+ - **Undo Support**: Reverse any previous rename session via `namewise undo` (or `undo --all`)
21
38
  - **Config File**: Set persistent defaults in `~/.namewise.json` or per-project `.namewise.json`
22
39
  - **Concurrency Control**: Process multiple files in parallel with a configurable limit
23
40
  - **Conflict Auto-Numbering**: When a target name is taken, automatically appends `-2`, `-3`, etc.
@@ -38,6 +55,9 @@ cd namewise
38
55
  npm install
39
56
  npm run build
40
57
 
58
+ # Run the interactive setup wizard (recommended for first use)
59
+ namewise init
60
+
41
61
  # Preview renames (recommended first)
42
62
  npx namewise rename ./my-documents --dry-run --provider claude
43
63
 
@@ -63,11 +83,43 @@ Download the latest release from [GitHub Releases](https://github.com/amirdaraee
63
83
 
64
84
  ### Command Structure
65
85
  ```bash
66
- namewise rename [directory] [options]
67
- namewise undo [session-id] [options]
86
+ namewise init # First-time setup wizard
87
+ namewise rename [directory] [options] # AI-powered rename (or batch rename with flags)
88
+ namewise sanitize [directory] [options] # Clean filenames without AI
89
+ namewise dedup [directory] [options] # Find and remove duplicate files
90
+ namewise watch [directory] [options] # Auto-rename new files as they arrive
91
+ namewise apply <plan.json> [options] # Execute a saved rename plan
92
+ namewise config <list|get|set> [key] [val] # Manage ~/.namewise.json
93
+ namewise undo [session-id] [options] # Reverse a previous rename session
94
+ namewise stats [directory] [options] # Storage breakdown by file type
95
+ namewise tree [directory] [options] # Visual directory tree with sizes
96
+ namewise info <path> # Metadata for a file or directory
97
+ namewise organize [directory] [options] # Move files into subfolders
98
+ namewise flatten [directory] [options] # Move all nested files to root
99
+ namewise clean-empty [directory] [options] # Remove empty directories
100
+ namewise find [directory] [options] # Search files by criteria
101
+ namewise diff <dir1> <dir2> [options] # Compare two directories
68
102
  ```
69
103
 
70
- ### Options Reference
104
+ ### `init`
105
+
106
+ Run `namewise init` to launch the interactive setup wizard. It will ask:
107
+
108
+ | Step | Question | Notes |
109
+ |------|----------|-------|
110
+ | 1 | **Scope** — global (`~/.namewise.json`) or project (`./.namewise.json`) | Global applies everywhere |
111
+ | 2 | **AI provider** — claude, openai, ollama, lmstudio | |
112
+ | 3 | **API key** | Cloud providers only; stored in config file |
113
+ | 4 | **Base URL** | Local providers only; skipped if using the default |
114
+ | 5 | **Model** | Leave blank to use the provider default |
115
+ | 6 | **Naming convention** | kebab-case, snake_case, camelCase, etc. |
116
+ | 7 | **Output language** | e.g. `English`, `French` — leave blank to match document language |
117
+ | 8 | **Always dry-run by default?** | Recommended `Yes` for first-time users |
118
+ | 9 | **Your name** | Optional; used in document/photo templates |
119
+
120
+ After init, all saved settings apply automatically — no flags needed on every run.
121
+
122
+ ### `rename` Options
71
123
 
72
124
  | Option | Description | Default |
73
125
  |--------|-------------|---------|
@@ -85,16 +137,105 @@ namewise undo [session-id] [options]
85
137
  | `--depth <n>` | Maximum recursion depth (requires `--recursive`) | Unlimited |
86
138
  | `--concurrency <n>` | Files to process in parallel | `3` |
87
139
  | `--output <path>` | Save rename report as JSON to this path | - |
140
+ | `--pattern <pattern>` | Regex rename pattern (repeatable); skips AI | - |
141
+ | `--no-ai` | Use file metadata instead of AI (no API call) | `false` |
142
+ | `--language <lang>` | Output language for generated filenames (e.g. `English`, `French`) | Document language |
143
+
144
+ ### `sanitize` Options
88
145
 
89
- ### Undo Options
146
+ | Option | Description | Default |
147
+ |--------|-------------|---------|
148
+ | `--dry-run` | Preview changes without renaming | `false` |
149
+ | `-r, --recursive` | Process subdirectories | `false` |
150
+ | `--case` | Naming convention to apply | `kebab-case` |
151
+
152
+ ### `dedup` Options
153
+
154
+ | Option | Description | Default |
155
+ |--------|-------------|---------|
156
+ | `-r, --recursive` | Scan subdirectories | `false` |
157
+ | `--delete` | Delete duplicates after confirmation | `false` |
158
+
159
+ ### `undo` Options
90
160
 
91
161
  | Option | Description |
92
162
  |--------|-------------|
93
163
  | `--list` | List recent rename sessions with their IDs |
164
+ | `--all` | Undo all sessions at once |
94
165
  | `[session-id]` | Undo a specific session by ID (default: most recent) |
95
166
 
167
+ ### Batch rename flags (on `rename`, no AI required)
168
+
169
+ | Flag | Description | Example |
170
+ |------|-------------|---------|
171
+ | `--sequence` | Replace filenames with padded sequence numbers | `001.pdf`, `002.pdf` |
172
+ | `--sequence-prefix <p>` | Prefix for sequence numbers | `--sequence-prefix photo` → `photo-001.jpg` |
173
+ | `--prefix <text>` | Prepend text to every filename stem | `--prefix "2024-"` |
174
+ | `--suffix <text>` | Append text to every filename stem | `--suffix "-final"` |
175
+ | `--date-stamp <created\|modified>` | Prepend file date to stem | `--date-stamp modified` |
176
+ | `--strip <pattern>` | Remove a substring or regex from stems | `--strip "IMG_"` |
177
+ | `--truncate <n>` | Truncate stems to N characters | `--truncate 20` |
178
+
179
+ ### `stats` Options
180
+
181
+ | Option | Description | Default |
182
+ |--------|-------------|---------|
183
+ | `-r, --recursive` | Include subdirectories | `false` |
184
+
185
+ ### `tree` Options
186
+
187
+ | Option | Description | Default |
188
+ |--------|-------------|---------|
189
+ | `--depth <n>` | Maximum depth to display | Unlimited |
190
+
191
+ ### `organize` Options
192
+
193
+ | Option | Description | Default |
194
+ |--------|-------------|---------|
195
+ | `--by <mode>` | Organisation mode: `ext` \| `date` \| `size` | `ext` |
196
+ | `-r, --recursive` | Include subdirectories | `false` |
197
+ | `--dry-run` | Preview without moving files | `false` |
198
+
199
+ ### `flatten` Options
200
+
201
+ | Option | Description | Default |
202
+ |--------|-------------|---------|
203
+ | `--dry-run` | Preview without moving files | `false` |
204
+
205
+ ### `clean-empty` Options
206
+
207
+ | Option | Description | Default |
208
+ |--------|-------------|---------|
209
+ | `--dry-run` | Preview without deleting directories | `false` |
210
+
211
+ ### `find` Options
212
+
213
+ | Option | Description |
214
+ |--------|-------------|
215
+ | `--ext <ext>` | Filter by file extension (e.g. `pdf`) |
216
+ | `--name <glob>` | Filter by filename glob (e.g. `"*.report*"`) |
217
+ | `--larger-than <size>` | Minimum size (e.g. `5mb`, `100kb`, `500`) |
218
+ | `--smaller-than <size>` | Maximum size (e.g. `10mb`) |
219
+ | `--newer-than <date>` | Modified after date (`YYYY-MM-DD`) |
220
+ | `--older-than <date>` | Modified before date (`YYYY-MM-DD`) |
221
+ | `-r, --recursive` | Search subdirectories (default: `true`) |
222
+
223
+ ### `diff` Options
224
+
225
+ | Option | Description | Default |
226
+ |--------|-------------|---------|
227
+ | `--by <mode>` | Compare by `name` or `hash` (content-aware, detects renames) | `name` |
228
+ | `-r, --recursive` | Compare subdirectories | `true` |
229
+
96
230
  ### Examples
97
231
 
232
+ **First-time setup:**
233
+ ```bash
234
+ namewise init
235
+ # Walks through provider, API key, naming convention, dry-run preference, and your name
236
+ # Saves to ~/.namewise.json or ./.namewise.json
237
+ ```
238
+
98
239
  **Basic usage:**
99
240
  ```bash
100
241
  namewise rename ./documents --dry-run
@@ -111,9 +252,57 @@ namewise rename ./projects --recursive --depth 2 --dry-run
111
252
  namewise rename ./documents --output ./report.json
112
253
  ```
113
254
 
255
+ **Rename using metadata only (no API key needed):**
256
+ ```bash
257
+ namewise rename ./documents --no-ai --dry-run
258
+ ```
259
+
260
+ **Force output language (e.g. rename Farsi documents with English names):**
261
+ ```bash
262
+ namewise rename ./farsi-docs --language English --dry-run
263
+ namewise rename ./documents --language French
264
+ ```
265
+
266
+ **Regex pattern rename (no AI):**
267
+ ```bash
268
+ namewise rename ./docs --pattern "s/IMG_//i" --pattern "s/ /-/g" --dry-run
269
+ ```
270
+
271
+ **Clean filenames without AI:**
272
+ ```bash
273
+ namewise sanitize ./downloads --dry-run
274
+ namewise sanitize ./downloads --case snake_case
275
+ ```
276
+
277
+ **Find and remove duplicate files:**
278
+ ```bash
279
+ namewise dedup ./photos --recursive
280
+ namewise dedup ./photos --recursive --delete
281
+ ```
282
+
283
+ **Watch a directory and auto-rename new files:**
284
+ ```bash
285
+ namewise watch ./inbox --provider claude --template document
286
+ namewise watch ./inbox --no-ai --case snake_case
287
+ ```
288
+
289
+ **Apply a saved rename plan:**
290
+ ```bash
291
+ namewise rename ./docs --output ./plan.json --dry-run
292
+ namewise apply ./plan.json
293
+ ```
294
+
295
+ **Manage config from the CLI:**
296
+ ```bash
297
+ namewise config list
298
+ namewise config get provider
299
+ namewise config set case snake_case
300
+ ```
301
+
114
302
  **Undo the last rename session:**
115
303
  ```bash
116
304
  namewise undo
305
+ namewise undo --all
117
306
  ```
118
307
 
119
308
  **List and undo a specific session:**
@@ -122,6 +311,66 @@ namewise undo --list
122
311
  namewise undo 2026-04-02T10:30:00.000Z
123
312
  ```
124
313
 
314
+ **Batch rename without AI:**
315
+ ```bash
316
+ namewise rename ./photos --sequence --sequence-prefix holiday
317
+ # holiday-001.jpg, holiday-002.jpg, ...
318
+
319
+ namewise rename ./docs --prefix "2024-" --dry-run
320
+ namewise rename ./exports --suffix "-final" --truncate 30
321
+ namewise rename ./downloads --strip "IMG_" --date-stamp modified
322
+ ```
323
+
324
+ **Storage stats:**
325
+ ```bash
326
+ namewise stats ./documents
327
+ namewise stats ./projects --recursive
328
+ ```
329
+
330
+ **Directory tree:**
331
+ ```bash
332
+ namewise tree ./src
333
+ namewise tree ./src --depth 3
334
+ ```
335
+
336
+ **File or directory info:**
337
+ ```bash
338
+ namewise info ./report.pdf
339
+ namewise info ./downloads
340
+ ```
341
+
342
+ **Organise files into subfolders:**
343
+ ```bash
344
+ namewise organize ./downloads --by ext --dry-run
345
+ namewise organize ./photos --by date
346
+ namewise organize ./backup --by size --dry-run
347
+ ```
348
+
349
+ **Flatten nested directories:**
350
+ ```bash
351
+ namewise flatten ./inbox --dry-run
352
+ namewise flatten ./inbox
353
+ ```
354
+
355
+ **Remove empty directories:**
356
+ ```bash
357
+ namewise clean-empty ./projects --dry-run
358
+ namewise clean-empty ./projects
359
+ ```
360
+
361
+ **Find files by criteria:**
362
+ ```bash
363
+ namewise find ./downloads --ext pdf
364
+ namewise find ./photos --larger-than 5mb --newer-than 2024-01-01
365
+ namewise find . --name "*.report*" --smaller-than 1mb
366
+ ```
367
+
368
+ **Compare two directories:**
369
+ ```bash
370
+ namewise diff ./backup ./original
371
+ namewise diff ./backup ./original --by hash # detects renamed files
372
+ ```
373
+
125
374
  **Personal documents with your name and date:**
126
375
  ```bash
127
376
  namewise rename ./documents --template document --name "john" --date "YYYYMMDD" --dry-run
@@ -201,7 +450,7 @@ Set persistent defaults so you don't have to repeat flags on every run.
201
450
 
202
451
  Priority order (highest to lowest): CLI flags > project config > user config.
203
452
 
204
- Supported keys: `provider`, `case`, `template`, `name`, `date`, `maxSize`, `model`, `baseUrl`, `concurrency`, `recursive`, `depth`, `output`.
453
+ Supported keys: `provider`, `apiKey`, `case`, `template`, `name`, `date`, `maxSize`, `model`, `baseUrl`, `concurrency`, `recursive`, `depth`, `output`, `dryRun`, `language`.
205
454
 
206
455
  ## Supported File Types
207
456
 
@@ -268,11 +517,11 @@ Supported keys: `provider`, `case`, `template`, `name`, `date`, `maxSize`, `mode
268
517
  ## Safety Features
269
518
 
270
519
  - **Dry Run Mode**: Always preview changes first with `--dry-run`
271
- - **Undo**: Reverse any session with `namewise undo`
520
+ - **Undo**: Reverse any session with `namewise undo` (or `undo --all`)
272
521
  - **Conflict Auto-Numbering**: Never overwrites an existing file
273
522
  - **File Size Limits**: Skips files above `--max-size`
274
523
  - **Extension Preservation**: Original file extensions are never changed
275
- - **Comprehensive Testing**: 457 tests with 100% coverage
524
+ - **Comprehensive Testing**: 621 tests with 100% coverage
276
525
 
277
526
  ## Testing & Development
278
527
 
@@ -0,0 +1,4 @@
1
+ export declare function applyPlan(planPath: string, options?: {
2
+ dryRun?: boolean;
3
+ }): Promise<void>;
4
+ //# sourceMappingURL=apply.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../src/cli/apply.ts"],"names":[],"mappings":"AAcA,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAgEnG"}
@@ -0,0 +1,66 @@
1
+ import { promises as fs } from 'fs';
2
+ import path from 'path';
3
+ import { appendHistory } from '../utils/history.js';
4
+ export async function applyPlan(planPath, options = {}) {
5
+ let plan;
6
+ try {
7
+ const raw = await fs.readFile(planPath, 'utf-8');
8
+ plan = JSON.parse(raw);
9
+ }
10
+ catch (error) {
11
+ throw new Error(`Could not read plan file: ${error instanceof Error ? error.message : 'Unknown error'}`);
12
+ }
13
+ if (!Array.isArray(plan.results)) {
14
+ throw new Error('Invalid plan file: missing "results" array');
15
+ }
16
+ const pending = plan.results.filter(r => r.success && r.originalPath !== r.newPath);
17
+ if (pending.length === 0) {
18
+ console.log('No renames to apply.');
19
+ return;
20
+ }
21
+ for (const r of pending) {
22
+ try {
23
+ await fs.access(r.originalPath);
24
+ }
25
+ catch {
26
+ throw new Error(`Source file not found: ${r.originalPath}`);
27
+ }
28
+ }
29
+ for (const r of pending) {
30
+ let targetExists = false;
31
+ try {
32
+ await fs.access(r.newPath);
33
+ targetExists = true;
34
+ }
35
+ catch (error) {
36
+ if (error.code !== 'ENOENT')
37
+ throw error;
38
+ }
39
+ if (targetExists)
40
+ throw new Error(`Target already exists: ${r.newPath}`);
41
+ }
42
+ const renames = [];
43
+ let previewCount = 0;
44
+ for (const r of pending) {
45
+ console.log(`${options.dryRun ? '[dry-run] ' : ''}${path.basename(r.originalPath)} → ${path.basename(r.newPath)}`);
46
+ if (!options.dryRun) {
47
+ await fs.rename(r.originalPath, r.newPath);
48
+ renames.push({ originalPath: r.originalPath, newPath: r.newPath });
49
+ }
50
+ else {
51
+ previewCount++;
52
+ }
53
+ }
54
+ if (!options.dryRun && renames.length > 0) {
55
+ await appendHistory({
56
+ id: new Date().toISOString(),
57
+ timestamp: new Date().toISOString(),
58
+ directory: path.dirname(renames[0].originalPath),
59
+ dryRun: false,
60
+ renames
61
+ });
62
+ }
63
+ const count = options.dryRun ? previewCount : renames.length;
64
+ console.log(`\nApplied${options.dryRun ? ' (dry-run)' : ''}: ${count} rename(s).`);
65
+ }
66
+ //# sourceMappingURL=apply.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply.js","sourceRoot":"","sources":["../../src/cli/apply.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export declare function cleanEmptyDirs(directory: string, options?: {
2
+ dryRun?: boolean;
3
+ }): Promise<void>;
4
+ //# sourceMappingURL=clean-empty.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clean-empty.d.ts","sourceRoot":"","sources":["../../src/cli/clean-empty.ts"],"names":[],"mappings":"AAGA,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACjC,OAAO,CAAC,IAAI,CAAC,CAsBf"}
@@ -0,0 +1,48 @@
1
+ import { promises as fs } from 'fs';
2
+ import path from 'path';
3
+ export async function cleanEmptyDirs(directory, options = {}) {
4
+ const stat = await fs.stat(directory);
5
+ if (!stat.isDirectory())
6
+ throw new Error(`${directory} is not a directory`);
7
+ const dryRun = options.dryRun ?? false;
8
+ const { emptyDirs } = await scan(directory);
9
+ if (emptyDirs.length === 0) {
10
+ console.log('No empty directories found.');
11
+ return;
12
+ }
13
+ for (const dir of emptyDirs) {
14
+ const rel = path.relative(directory, dir);
15
+ console.log(`${dryRun ? '[dry-run] ' : ''}Remove: ${rel}`);
16
+ if (!dryRun) {
17
+ await fs.rmdir(dir);
18
+ }
19
+ }
20
+ const count = emptyDirs.length;
21
+ console.log(`\n${dryRun ? 'Would remove' : 'Removed'} ${count} empty director${count === 1 ? 'y' : 'ies'}.`);
22
+ }
23
+ async function scan(dir) {
24
+ const entries = await fs.readdir(dir, { withFileTypes: true });
25
+ if (entries.length === 0) {
26
+ return { emptyDirs: [], isEmpty: true };
27
+ }
28
+ const emptyDirs = [];
29
+ let allChildDirsEmpty = true;
30
+ let hasFiles = false;
31
+ for (const entry of entries) {
32
+ if (!entry.isDirectory()) {
33
+ hasFiles = true;
34
+ continue;
35
+ }
36
+ const subDir = path.join(dir, entry.name);
37
+ const subResult = await scan(subDir);
38
+ emptyDirs.push(...subResult.emptyDirs);
39
+ if (subResult.isEmpty) {
40
+ emptyDirs.push(subDir);
41
+ }
42
+ else {
43
+ allChildDirsEmpty = false;
44
+ }
45
+ }
46
+ return { emptyDirs, isEmpty: !hasFiles && allChildDirsEmpty };
47
+ }
48
+ //# sourceMappingURL=clean-empty.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clean-empty.js","sourceRoot":"","sources":["../../src/cli/clean-empty.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,SAAiB,EACjB,UAAgC,EAAE;IAElC,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACtC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,SAAS,qBAAqB,CAAC,CAAC;IAE5E,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,KAAK,CAAC;IACvC,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,CAAC;IAE5C,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;QAC3C,OAAO;IACT,CAAC;IAED,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,WAAW,GAAG,EAAE,CAAC,CAAC;QAC3D,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;IAED,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC;IAC/B,OAAO,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS,IAAI,KAAK,kBAAkB,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC;AAC/G,CAAC;AAOD,KAAK,UAAU,IAAI,CAAC,GAAW;IAC7B,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAE/D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC1C,CAAC;IAED,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,IAAI,iBAAiB,GAAG,IAAI,CAAC;IAC7B,IAAI,QAAQ,GAAG,KAAK,CAAC;IAErB,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACzB,QAAQ,GAAG,IAAI,CAAC;YAChB,SAAS;QACX,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAC1C,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,CAAC;QACrC,SAAS,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC;QACvC,IAAI,SAAS,CAAC,OAAO,EAAE,CAAC;YACtB,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACzB,CAAC;aAAM,CAAC;YACN,iBAAiB,GAAG,KAAK,CAAC;QAC5B,CAAC;IACH,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,QAAQ,IAAI,iBAAiB,EAAE,CAAC;AAChE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/cli/commands.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA8EpD"}
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/cli/commands.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkBpC,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAoUpD"}