notepad-cleanup 0.2.1__tar.gz → 0.2.4__tar.gz

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 (27) hide show
  1. notepad_cleanup-0.2.4/PKG-INFO +208 -0
  2. notepad_cleanup-0.2.4/README.md +165 -0
  3. {notepad_cleanup-0.2.1 → notepad_cleanup-0.2.4}/notepad_cleanup/_version.py +2 -2
  4. {notepad_cleanup-0.2.1 → notepad_cleanup-0.2.4}/notepad_cleanup/cli.py +99 -6
  5. {notepad_cleanup-0.2.1 → notepad_cleanup-0.2.4}/notepad_cleanup/dedup.py +111 -20
  6. notepad_cleanup-0.2.4/notepad_cleanup/organizer.py +571 -0
  7. {notepad_cleanup-0.2.1 → notepad_cleanup-0.2.4}/notepad_cleanup/prompts/organize.md +2 -1
  8. notepad_cleanup-0.2.4/notepad_cleanup.egg-info/PKG-INFO +208 -0
  9. notepad_cleanup-0.2.1/PKG-INFO +0 -299
  10. notepad_cleanup-0.2.1/README.md +0 -256
  11. notepad_cleanup-0.2.1/notepad_cleanup/organizer.py +0 -281
  12. notepad_cleanup-0.2.1/notepad_cleanup.egg-info/PKG-INFO +0 -299
  13. {notepad_cleanup-0.2.1 → notepad_cleanup-0.2.4}/LICENSE +0 -0
  14. {notepad_cleanup-0.2.1 → notepad_cleanup-0.2.4}/notepad_cleanup/__init__.py +0 -0
  15. {notepad_cleanup-0.2.1 → notepad_cleanup-0.2.4}/notepad_cleanup/__main__.py +0 -0
  16. {notepad_cleanup-0.2.1 → notepad_cleanup-0.2.4}/notepad_cleanup/config.py +0 -0
  17. {notepad_cleanup-0.2.1 → notepad_cleanup-0.2.4}/notepad_cleanup/discovery.py +0 -0
  18. {notepad_cleanup-0.2.1 → notepad_cleanup-0.2.4}/notepad_cleanup/extractor.py +0 -0
  19. {notepad_cleanup-0.2.1 → notepad_cleanup-0.2.4}/notepad_cleanup/saver.py +0 -0
  20. {notepad_cleanup-0.2.1 → notepad_cleanup-0.2.4}/notepad_cleanup.egg-info/SOURCES.txt +0 -0
  21. {notepad_cleanup-0.2.1 → notepad_cleanup-0.2.4}/notepad_cleanup.egg-info/dependency_links.txt +0 -0
  22. {notepad_cleanup-0.2.1 → notepad_cleanup-0.2.4}/notepad_cleanup.egg-info/entry_points.txt +0 -0
  23. {notepad_cleanup-0.2.1 → notepad_cleanup-0.2.4}/notepad_cleanup.egg-info/requires.txt +0 -0
  24. {notepad_cleanup-0.2.1 → notepad_cleanup-0.2.4}/notepad_cleanup.egg-info/top_level.txt +0 -0
  25. {notepad_cleanup-0.2.1 → notepad_cleanup-0.2.4}/pyproject.toml +0 -0
  26. {notepad_cleanup-0.2.1 → notepad_cleanup-0.2.4}/setup.cfg +0 -0
  27. {notepad_cleanup-0.2.1 → notepad_cleanup-0.2.4}/setup.py +0 -0
@@ -0,0 +1,208 @@
1
+ Metadata-Version: 2.4
2
+ Name: notepad-cleanup
3
+ Version: 0.2.4
4
+ Summary: Extract and organize text from Windows 11 Notepad tabs using AI
5
+ Home-page: https://github.com/DazzleTools/notepad-cleanup
6
+ Author: djdarcy
7
+ Author-email: djdarcy <6962246+djdarcy@users.noreply.github.com>
8
+ License: GPL-3.0-or-later
9
+ Project-URL: Homepage, https://github.com/DazzleTools/notepad-cleanup
10
+ Project-URL: Repository, https://github.com/DazzleTools/notepad-cleanup
11
+ Project-URL: Issues, https://github.com/DazzleTools/notepad-cleanup/issues
12
+ Project-URL: Discussions, https://github.com/DazzleTools/notepad-cleanup/discussions
13
+ Project-URL: Changelog, https://github.com/DazzleTools/notepad-cleanup/blob/main/CHANGELOG.md
14
+ Keywords: windows,notepad,extraction,organization,ai,dedup,tabs,clipboard
15
+ Classifier: Development Status :: 3 - Alpha
16
+ Classifier: Environment :: Console
17
+ Classifier: Intended Audience :: End Users/Desktop
18
+ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
19
+ Classifier: Operating System :: Microsoft :: Windows
20
+ Classifier: Operating System :: Microsoft :: Windows :: Windows 11
21
+ Classifier: Programming Language :: Python :: 3
22
+ Classifier: Programming Language :: Python :: 3.10
23
+ Classifier: Programming Language :: Python :: 3.11
24
+ Classifier: Programming Language :: Python :: 3.12
25
+ Classifier: Programming Language :: Python :: 3.13
26
+ Classifier: Topic :: Utilities
27
+ Classifier: Topic :: Text Processing
28
+ Requires-Python: >=3.10
29
+ Description-Content-Type: text/markdown
30
+ License-File: LICENSE
31
+ Requires-Dist: pywinauto>=0.6.9
32
+ Requires-Dist: pywin32>=306
33
+ Requires-Dist: psutil>=5.9.0
34
+ Requires-Dist: click>=8.1.0
35
+ Requires-Dist: rich>=13.0.0
36
+ Provides-Extra: dev
37
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
38
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
39
+ Dynamic: author
40
+ Dynamic: home-page
41
+ Dynamic: license-file
42
+ Dynamic: requires-python
43
+
44
+ # notepad-cleanup
45
+
46
+ [![PyPI](https://img.shields.io/pypi/v/notepad-cleanup?color=green)](https://pypi.org/project/notepad-cleanup/)
47
+ [![Release Date](https://img.shields.io/github/release-date/DazzleTools/notepad-cleanup?color=green)](https://github.com/DazzleTools/notepad-cleanup/releases)
48
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
49
+ [![License: GPL v3](https://img.shields.io/badge/license-GPL%20v3-green.svg)](https://www.gnu.org/licenses/gpl-3.0.html)
50
+ [![Installs](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/djdarcy/a165031b15ecf9b6fdac066d0222a591/raw/installs.json)](https://dazzletools.github.io/notepad-cleanup/stats/#installs)
51
+ [![GitHub Discussions](https://img.shields.io/github/discussions/DazzleTools/notepad-cleanup)](https://github.com/DazzleTools/notepad-cleanup/discussions)
52
+ [![Platform](https://img.shields.io/badge/platform-Windows%2011-lightgrey.svg)](https://github.com/DazzleTools/notepad-cleanup)
53
+
54
+ Extract and organize text from all open Windows 11 Notepad tabs using AI-powered categorization.
55
+
56
+ ## What It Does
57
+
58
+ Windows 11 Notepad supports multiple tabs, making it easy to accumulate dozens of text snippets, code fragments, notes, and temporary data across multiple windows. **notepad-cleanup** extracts all that text in one command, deduplicates it against previous sessions, and organizes it into categorized folders using AI.
59
+
60
+ ## Installation
61
+
62
+ ```bash
63
+ pip install notepad-cleanup
64
+ ```
65
+
66
+ For virtual environments, source installs, and Claude Code CLI setup, see [docs/install.md](docs/install.md).
67
+
68
+ ```bash
69
+ notepad-cleanup extract # Extract all Notepad tabs
70
+ notepad-cleanup compare --last --link auto # Find and link duplicates
71
+ notepad-cleanup organize --last # AI categorization (duplicates become symlinks)
72
+ notepad-cleanup links separate --last # Optional: split out linked files to see only new content
73
+ ```
74
+
75
+ ## Features
76
+
77
+ - **Two-phase extraction** -- Silent `WM_GETTEXT` for loaded tabs, UI Automation for unloaded tabs
78
+ - **Cross-session deduplication** -- Compare against historical sessions with exact and [fuzzy matching](docs/fuzzy-matching.md)
79
+ - **Filesystem linking** -- Replace duplicates with hardlinks, symlinks, or DazzleLink descriptors
80
+ - **Link-aware organization** -- AI categorizes all files, but duplicates become symlinks in `organized/` instead of copies. Preserves the connection network back to canonical sources
81
+ - **Separate/join links** -- Split `organized/` into new files vs linked duplicates, or rejoin them
82
+ - **Configuration system** -- Unified folder registry with [`...` notation](docs/config.md), MRU history, persistent settings
83
+ - **Diff integration** -- Auto-generated scripts for Beyond Compare, WinMerge, VS Code, etc.
84
+
85
+ ## Usage
86
+
87
+ ### First-time setup
88
+
89
+ ```bash
90
+ notepad-cleanup config add "C:\Users\YourName\Desktop\Notepad Organize"
91
+ notepad-cleanup config set search "...1"
92
+ notepad-cleanup config set diff_tool bcomp
93
+ ```
94
+
95
+ ### Daily workflow
96
+
97
+ ```bash
98
+ notepad-cleanup extract # 1. Extract all tabs
99
+ notepad-cleanup compare --last # 2. Find duplicates
100
+ notepad-cleanup diff --last # 3. Spot-check in diff tool
101
+ notepad-cleanup compare --last --link auto # 4. Link duplicates
102
+ notepad-cleanup organize --last # 5. AI categorization (dupes become symlinks)
103
+ notepad-cleanup links separate --last # 6. Optional: see only new files
104
+ notepad-cleanup links join --last # 7. Optional: rejoin everything
105
+ ```
106
+
107
+ After setup, `--last` auto-uses the most recent extraction. No path copy-pasting.
108
+
109
+ ### Commands
110
+
111
+ | Command | Purpose |
112
+ |---------|---------|
113
+ | `extract` | Extract text from all open Notepad windows/tabs |
114
+ | `compare` | Find duplicates across historical sessions |
115
+ | `organize` | AI-powered categorization (symlinks for duplicates, copies for new) |
116
+ | `links` | Separate linked files from organized/, or rejoin them |
117
+ | `diff` | Launch diff script to spot-check matched pairs |
118
+ | `config` | Manage folders, search dirs, diff tool, settings |
119
+ | `run` | Extract + organize in one step |
120
+
121
+ For full parameter documentation, see [docs/parameters.md](docs/parameters.md).
122
+
123
+ ## Documentation
124
+
125
+ | Doc | Contents |
126
+ |-----|----------|
127
+ | [Parameters](docs/parameters.md) | Full command reference with all options |
128
+ | [Configuration](docs/config.md) | Folder registry, `...` notation, MRU, search dirs |
129
+ | [Fuzzy Matching](docs/fuzzy-matching.md) | Threshold formula, derivation, customization |
130
+
131
+ ## Output Structure
132
+
133
+ ```
134
+ notepad-cleanup/nc-2026-03-16__08-15-30/
135
+ ├── manifest.json # Extraction metadata
136
+ ├── window01/
137
+ │ ├── tab01.txt # Raw extracted files
138
+ │ ├── tab02.txt
139
+ │ └── tab03.txt
140
+ ├── window02/
141
+ │ └── tab01.txt
142
+ ├── organized/ # AI-organized output (after organize step)
143
+ │ ├── code-snippets/
144
+ │ │ ├── process-data.py
145
+ │ │ └── batch-rename.bat
146
+ │ ├── personal-notes/
147
+ │ │ └── grocery-list.txt
148
+ │ └── _summary.md # Organization summary
149
+ ├── _compare_results.json # Dedup comparison cache
150
+ ├── _compare_diffs.cmd # Diff script for spot-checking
151
+ ├── _dedup_links.json # Link manifest (if --link used)
152
+ ├── _organize_prompt.md # AI prompt used
153
+ └── _organize_log.txt # Claude CLI output
154
+ ```
155
+
156
+ ## How It Works
157
+
158
+ ### Phase 1: Silent Extraction
159
+
160
+ Uses `WM_GETTEXT` message to read text from `RichEditD2DPT` child windows. This is completely silent and invisible -- no focus changes, no window activation, no disruption to your workflow.
161
+
162
+ **Limitation:** Only works for tabs that have been loaded (visited) at least once in the current Notepad session. Unloaded tabs have no `RichEditD2DPT` control yet, so they cannot be read silently.
163
+
164
+ ### Phase 2: Tab Switching (Announced)
165
+
166
+ For unloaded tabs, uses UI Automation (`TabItem.Select()`) to activate each tab, which forces Windows to load the `RichEditD2DPT` control. Once loaded, the same `WM_GETTEXT` method reads the content.
167
+
168
+ **Warning:** This steals focus and activates Notepad windows. The tool warns you before Phase 2 starts and waits for confirmation. Do not type or click during Phase 2.
169
+
170
+ ### Organization with AI
171
+
172
+ After extraction, Claude Code CLI:
173
+ 1. Reads `manifest.json` to understand the collection
174
+ 2. Reads each extracted file to determine content type
175
+ 3. Returns a JSON plan with categories and renamed filenames
176
+ 4. The tool executes the plan locally (copy files to organized folders)
177
+
178
+ ## Requirements
179
+
180
+ - **Windows 11** (uses Windows 11 Notepad tab features)
181
+ - **Python 3.10+**
182
+ - **Claude Code CLI** (optional, for organize step)
183
+
184
+ For detailed installation instructions, see [docs/install.md](docs/install.md).
185
+
186
+ ## Development
187
+
188
+ ```bash
189
+ git clone https://github.com/DazzleTools/notepad-cleanup.git
190
+ cd notepad-cleanup
191
+ python -m venv venv
192
+ venv\Scripts\activate
193
+ pip install -e .
194
+ ```
195
+
196
+ ## Contributing
197
+
198
+ Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
199
+
200
+ Like the project?
201
+
202
+ [!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/djdarcy)
203
+
204
+ ## License
205
+
206
+ notepad-cleanup, Copyright (C) 2026 Dustin Darcy.
207
+
208
+ This project is licensed under the GNU General Public License v3.0 — see [LICENSE](LICENSE) for full details.
@@ -0,0 +1,165 @@
1
+ # notepad-cleanup
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/notepad-cleanup?color=green)](https://pypi.org/project/notepad-cleanup/)
4
+ [![Release Date](https://img.shields.io/github/release-date/DazzleTools/notepad-cleanup?color=green)](https://github.com/DazzleTools/notepad-cleanup/releases)
5
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
6
+ [![License: GPL v3](https://img.shields.io/badge/license-GPL%20v3-green.svg)](https://www.gnu.org/licenses/gpl-3.0.html)
7
+ [![Installs](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/djdarcy/a165031b15ecf9b6fdac066d0222a591/raw/installs.json)](https://dazzletools.github.io/notepad-cleanup/stats/#installs)
8
+ [![GitHub Discussions](https://img.shields.io/github/discussions/DazzleTools/notepad-cleanup)](https://github.com/DazzleTools/notepad-cleanup/discussions)
9
+ [![Platform](https://img.shields.io/badge/platform-Windows%2011-lightgrey.svg)](https://github.com/DazzleTools/notepad-cleanup)
10
+
11
+ Extract and organize text from all open Windows 11 Notepad tabs using AI-powered categorization.
12
+
13
+ ## What It Does
14
+
15
+ Windows 11 Notepad supports multiple tabs, making it easy to accumulate dozens of text snippets, code fragments, notes, and temporary data across multiple windows. **notepad-cleanup** extracts all that text in one command, deduplicates it against previous sessions, and organizes it into categorized folders using AI.
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pip install notepad-cleanup
21
+ ```
22
+
23
+ For virtual environments, source installs, and Claude Code CLI setup, see [docs/install.md](docs/install.md).
24
+
25
+ ```bash
26
+ notepad-cleanup extract # Extract all Notepad tabs
27
+ notepad-cleanup compare --last --link auto # Find and link duplicates
28
+ notepad-cleanup organize --last # AI categorization (duplicates become symlinks)
29
+ notepad-cleanup links separate --last # Optional: split out linked files to see only new content
30
+ ```
31
+
32
+ ## Features
33
+
34
+ - **Two-phase extraction** -- Silent `WM_GETTEXT` for loaded tabs, UI Automation for unloaded tabs
35
+ - **Cross-session deduplication** -- Compare against historical sessions with exact and [fuzzy matching](docs/fuzzy-matching.md)
36
+ - **Filesystem linking** -- Replace duplicates with hardlinks, symlinks, or DazzleLink descriptors
37
+ - **Link-aware organization** -- AI categorizes all files, but duplicates become symlinks in `organized/` instead of copies. Preserves the connection network back to canonical sources
38
+ - **Separate/join links** -- Split `organized/` into new files vs linked duplicates, or rejoin them
39
+ - **Configuration system** -- Unified folder registry with [`...` notation](docs/config.md), MRU history, persistent settings
40
+ - **Diff integration** -- Auto-generated scripts for Beyond Compare, WinMerge, VS Code, etc.
41
+
42
+ ## Usage
43
+
44
+ ### First-time setup
45
+
46
+ ```bash
47
+ notepad-cleanup config add "C:\Users\YourName\Desktop\Notepad Organize"
48
+ notepad-cleanup config set search "...1"
49
+ notepad-cleanup config set diff_tool bcomp
50
+ ```
51
+
52
+ ### Daily workflow
53
+
54
+ ```bash
55
+ notepad-cleanup extract # 1. Extract all tabs
56
+ notepad-cleanup compare --last # 2. Find duplicates
57
+ notepad-cleanup diff --last # 3. Spot-check in diff tool
58
+ notepad-cleanup compare --last --link auto # 4. Link duplicates
59
+ notepad-cleanup organize --last # 5. AI categorization (dupes become symlinks)
60
+ notepad-cleanup links separate --last # 6. Optional: see only new files
61
+ notepad-cleanup links join --last # 7. Optional: rejoin everything
62
+ ```
63
+
64
+ After setup, `--last` auto-uses the most recent extraction. No path copy-pasting.
65
+
66
+ ### Commands
67
+
68
+ | Command | Purpose |
69
+ |---------|---------|
70
+ | `extract` | Extract text from all open Notepad windows/tabs |
71
+ | `compare` | Find duplicates across historical sessions |
72
+ | `organize` | AI-powered categorization (symlinks for duplicates, copies for new) |
73
+ | `links` | Separate linked files from organized/, or rejoin them |
74
+ | `diff` | Launch diff script to spot-check matched pairs |
75
+ | `config` | Manage folders, search dirs, diff tool, settings |
76
+ | `run` | Extract + organize in one step |
77
+
78
+ For full parameter documentation, see [docs/parameters.md](docs/parameters.md).
79
+
80
+ ## Documentation
81
+
82
+ | Doc | Contents |
83
+ |-----|----------|
84
+ | [Parameters](docs/parameters.md) | Full command reference with all options |
85
+ | [Configuration](docs/config.md) | Folder registry, `...` notation, MRU, search dirs |
86
+ | [Fuzzy Matching](docs/fuzzy-matching.md) | Threshold formula, derivation, customization |
87
+
88
+ ## Output Structure
89
+
90
+ ```
91
+ notepad-cleanup/nc-2026-03-16__08-15-30/
92
+ ├── manifest.json # Extraction metadata
93
+ ├── window01/
94
+ │ ├── tab01.txt # Raw extracted files
95
+ │ ├── tab02.txt
96
+ │ └── tab03.txt
97
+ ├── window02/
98
+ │ └── tab01.txt
99
+ ├── organized/ # AI-organized output (after organize step)
100
+ │ ├── code-snippets/
101
+ │ │ ├── process-data.py
102
+ │ │ └── batch-rename.bat
103
+ │ ├── personal-notes/
104
+ │ │ └── grocery-list.txt
105
+ │ └── _summary.md # Organization summary
106
+ ├── _compare_results.json # Dedup comparison cache
107
+ ├── _compare_diffs.cmd # Diff script for spot-checking
108
+ ├── _dedup_links.json # Link manifest (if --link used)
109
+ ├── _organize_prompt.md # AI prompt used
110
+ └── _organize_log.txt # Claude CLI output
111
+ ```
112
+
113
+ ## How It Works
114
+
115
+ ### Phase 1: Silent Extraction
116
+
117
+ Uses `WM_GETTEXT` message to read text from `RichEditD2DPT` child windows. This is completely silent and invisible -- no focus changes, no window activation, no disruption to your workflow.
118
+
119
+ **Limitation:** Only works for tabs that have been loaded (visited) at least once in the current Notepad session. Unloaded tabs have no `RichEditD2DPT` control yet, so they cannot be read silently.
120
+
121
+ ### Phase 2: Tab Switching (Announced)
122
+
123
+ For unloaded tabs, uses UI Automation (`TabItem.Select()`) to activate each tab, which forces Windows to load the `RichEditD2DPT` control. Once loaded, the same `WM_GETTEXT` method reads the content.
124
+
125
+ **Warning:** This steals focus and activates Notepad windows. The tool warns you before Phase 2 starts and waits for confirmation. Do not type or click during Phase 2.
126
+
127
+ ### Organization with AI
128
+
129
+ After extraction, Claude Code CLI:
130
+ 1. Reads `manifest.json` to understand the collection
131
+ 2. Reads each extracted file to determine content type
132
+ 3. Returns a JSON plan with categories and renamed filenames
133
+ 4. The tool executes the plan locally (copy files to organized folders)
134
+
135
+ ## Requirements
136
+
137
+ - **Windows 11** (uses Windows 11 Notepad tab features)
138
+ - **Python 3.10+**
139
+ - **Claude Code CLI** (optional, for organize step)
140
+
141
+ For detailed installation instructions, see [docs/install.md](docs/install.md).
142
+
143
+ ## Development
144
+
145
+ ```bash
146
+ git clone https://github.com/DazzleTools/notepad-cleanup.git
147
+ cd notepad-cleanup
148
+ python -m venv venv
149
+ venv\Scripts\activate
150
+ pip install -e .
151
+ ```
152
+
153
+ ## Contributing
154
+
155
+ Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
156
+
157
+ Like the project?
158
+
159
+ [!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/djdarcy)
160
+
161
+ ## License
162
+
163
+ notepad-cleanup, Copyright (C) 2026 Dustin Darcy.
164
+
165
+ This project is licensed under the GNU General Public License v3.0 — see [LICENSE](LICENSE) for full details.
@@ -21,13 +21,13 @@ Version levels:
21
21
  # Version components - edit these for version bumps
22
22
  MAJOR = 0
23
23
  MINOR = 2
24
- PATCH = 1
24
+ PATCH = 4
25
25
  PHASE = None # Per-MINOR feature set: None, "alpha", "beta", "rc1", etc.
26
26
  PRE_RELEASE_NUM = 0 # PEP 440 pre-release number (e.g., a1, b2)
27
27
  PROJECT_PHASE = "prealpha" # Project-wide: "prealpha", "alpha", "beta", "stable"
28
28
 
29
29
  # Auto-updated by git hooks - do not edit manually
30
- __version__ = "0.2.1_main_14-20260316-16a4e3a7"
30
+ __version__ = "0.2.4_main_16-20260410-34ed8c9d"
31
31
  __app_name__ = "notepad-cleanup"
32
32
 
33
33
 
@@ -13,13 +13,15 @@ from .discovery import find_notepad_windows, get_richedit_children, get_tab_coun
13
13
  from .extractor import extract_phase1, extract_phase2, merge_results
14
14
  from .saver import save_extraction
15
15
  from .organizer import (generate_prompt, invoke_claude_cli, save_prompt_to_file,
16
- find_claude_cli, parse_plan, execute_plan)
16
+ find_claude_cli, parse_plan, execute_plan,
17
+ separate_links, join_links)
17
18
  from .dedup import (find_session_dirs, build_hash_index, find_duplicates,
18
19
  find_content_files,
19
20
  generate_unified_diff, near_match_threshold,
20
21
  resolve_diff_tool, launch_diff_tool, create_links,
21
22
  write_link_manifest, generate_diff_script,
22
23
  save_compare_results, load_compare_results,
24
+ get_linked_paths,
23
25
  LINK_STRATEGIES, CACHE_FILENAME)
24
26
  from .config import (
25
27
  load_config, save_config, config_get, config_set, config_unset,
@@ -546,8 +548,14 @@ def organize(folder, last, backend, dry_run, verbose):
546
548
 
547
549
  console.print("\n[bold]Notepad Cleanup -- Organize[/bold]\n")
548
550
 
549
- # Generate prompt
550
- prompt = generate_prompt(manifest_path)
551
+ # Load dedup context if available (for link-aware organize)
552
+ linked_paths = get_linked_paths(folder)
553
+ if linked_paths:
554
+ console.print(f" [dim]Found {len(linked_paths)} dedup-linked files "
555
+ f"(will symlink, not copy)[/dim]")
556
+
557
+ # Generate prompt (linked files excluded from AI task)
558
+ prompt = generate_prompt(manifest_path, linked_paths=linked_paths)
551
559
 
552
560
  if backend == "prompt-only" or dry_run:
553
561
  prompt_file = save_prompt_to_file(prompt, folder)
@@ -606,12 +614,14 @@ def organize(folder, last, backend, dry_run, verbose):
606
614
 
607
615
  console.print(f" Claude proposed a plan for [bold]{len(plan)}[/bold] files\n")
608
616
 
609
- # Step 3: Execute the plan locally
617
+ # Step 3: Execute the plan locally (link-aware)
610
618
  with console.status("Organizing files..."):
611
- summary, stats = execute_plan(plan, folder)
619
+ summary, stats = execute_plan(plan, folder, linked_paths=linked_paths)
612
620
 
613
621
  console.print("[bold green]Organization complete![/bold green]\n")
614
- console.print(f" Files organized: {stats['copied']}")
622
+ console.print(f" Files copied: {stats['copied']}")
623
+ if stats.get('linked', 0):
624
+ console.print(f" Files linked: {stats['linked']} (symlink/hardlink to canonical)")
615
625
  if stats['errors']:
616
626
  console.print(f" [red]Errors: {stats['errors']}[/red]")
617
627
 
@@ -1011,6 +1021,89 @@ def diff_cmd(folder, last):
1011
1021
  console.print(f" You can run it manually: {script_path}\n")
1012
1022
 
1013
1023
 
1024
+ @main.command("links")
1025
+ @click.argument("action", type=click.Choice(["separate", "join"]))
1026
+ @click.argument("folder", type=click.Path(), required=False, default=None)
1027
+ @click.option("--last", is_flag=True, help="Use the most recent extraction")
1028
+ @click.option("--dir-name", default="organized-links",
1029
+ help="Name for the links directory (default: organized-links)")
1030
+ @click.option("--dry-run", is_flag=True, help="Preview without moving files")
1031
+ def links_cmd(action, folder, last, dir_name, dry_run):
1032
+ """Separate or rejoin linked files in organized/.
1033
+
1034
+ \b
1035
+ separate: Move linked files from organized/ into a parallel tree,
1036
+ preserving category structure. Shows only new files in organized/.
1037
+ join: Move linked files back from the parallel tree into organized/,
1038
+ restoring the full collection.
1039
+
1040
+ \b
1041
+ Examples:
1042
+ notepad-cleanup links separate --last Split links out
1043
+ notepad-cleanup links separate --last --dry-run Preview the split
1044
+ notepad-cleanup links join --last Rejoin links
1045
+ notepad-cleanup links join --last --dry-run Preview the rejoin
1046
+ """
1047
+
1048
+ folder = resolve_folder(folder, use_last=last)
1049
+ if folder is None:
1050
+ console.print("[red]No extraction folder specified.[/red]")
1051
+ console.print(" Provide a FOLDER argument or use --last.")
1052
+ return
1053
+ folder = Path(folder)
1054
+ if not folder.exists():
1055
+ console.print(f"[red]Folder does not exist: {folder}[/red]")
1056
+ return
1057
+
1058
+ if last:
1059
+ console.print(f"\n [dim]Using last extraction: {folder}[/dim]")
1060
+
1061
+ organized_dir = folder / "organized"
1062
+ links_dir = folder / dir_name
1063
+
1064
+ if action == "separate":
1065
+ if not organized_dir.exists():
1066
+ console.print(f"\n [yellow]No organized/ folder found in {folder}[/yellow]")
1067
+ console.print(" Run 'notepad-cleanup organize' first.\n")
1068
+ return
1069
+
1070
+ label = "Separate Links -- Dry Run" if dry_run else "Separate Links"
1071
+ console.print(f"\n[bold]{label}[/bold]\n")
1072
+ console.print(f" From: {organized_dir}")
1073
+ console.print(f" To: {links_dir}\n")
1074
+
1075
+ stats, details = separate_links(organized_dir, links_dir_name=dir_name, dry_run=dry_run)
1076
+
1077
+ console.print(f" Links moved: {stats['moved']}")
1078
+ console.print(f" Real files: {stats['real_kept']}")
1079
+ if stats['errors']:
1080
+ console.print(f" [red]Errors: {stats['errors']}[/red]")
1081
+
1082
+ elif action == "join":
1083
+ if not links_dir.exists():
1084
+ console.print(f"\n [yellow]No {dir_name}/ folder found in {folder}[/yellow]")
1085
+ console.print(" Run 'notepad-cleanup links separate' first.\n")
1086
+ return
1087
+
1088
+ label = "Join Links -- Dry Run" if dry_run else "Join Links"
1089
+ console.print(f"\n[bold]{label}[/bold]\n")
1090
+ console.print(f" From: {links_dir}")
1091
+ console.print(f" To: {organized_dir}\n")
1092
+
1093
+ stats, details = join_links(organized_dir, links_dir_name=dir_name, dry_run=dry_run)
1094
+
1095
+ console.print(f" Links restored: {stats['moved']}")
1096
+ if stats['errors']:
1097
+ console.print(f" [red]Errors: {stats['errors']}[/red]")
1098
+
1099
+ if details:
1100
+ console.print()
1101
+ for d in details:
1102
+ console.print(d)
1103
+
1104
+ console.print()
1105
+
1106
+
1014
1107
  @main.command()
1015
1108
  @click.option("--output-dir", "-o", type=click.Path(), default=None,
1016
1109
  help="Output directory")