tidyf 1.0.0 → 1.0.1

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.
package/README.md CHANGED
@@ -2,24 +2,52 @@
2
2
 
3
3
  AI-powered file organizer CLI using [opencode.ai](https://opencode.ai)
4
4
 
5
- ```
6
- ┌ tidyf
5
+ ```text
6
+ ┌ tidyf
7
7
 
8
- Scanning ~/Downloads...
9
- │ Found 12 files
8
+ Source: ~/Downloads
10
9
 
11
- Analyzing files with AI...
10
+ Target: ~/Downloads/Organized
12
11
 
13
- Organization proposal:
14
- 📄 report-2024.pdf → Documents/Work
15
- │ 🖼️ screenshot.png Images/Screenshots
16
- 📦 project.zip → Archives
12
+ Found 15 files
13
+
14
+ Total size: 79 MB
15
+
16
+ ◇ Analysis complete
17
+
18
+ ● Proposed organization for 15 files:
19
+
20
+ │ Strategy: Primary categorization by file type and MIME type, secondary categorization
21
+ │ by filename context and naming patterns. Documents go to Work, books
22
+ │ to Education, and camera images to Photos.
23
+
24
+ ● 📄 Documents (9 files)
25
+
26
+ │ [1] 📄 financial-report.pdf (596 KB)
27
+ │ → ~/Downloads/Organized/Documents/Work/financial-report.pdf
28
+ │ 📄 Documents/Work 90%
29
+ │ Document with financial context and report keywords
30
+
31
+ │ [2] 📄 research-paper.pdf (448.6 KB)
32
+ │ → ~/Downloads/Organized/Documents/Education/research-paper.pdf
33
+ │ 📄 Documents/Education 85%
34
+ │ Technical document, appears to be educational content
17
35
 
18
- What would you like to do?
19
- ● Apply all
36
+ 🖼️ Images (4 files)
37
+
38
+ │ [12] 🖼️ vacation-photo.jpg (361.1 KB)
39
+ │ → ~/Downloads/Organized/Images/Photos/vacation-photo.jpg
40
+ │ 🖼️ Images/Photos 95%
41
+ │ Image with metadata indicating it was taken with a camera
42
+
43
+ ■ What would you like to do?
44
+ │ ● Apply all 15 moves
20
45
  │ ○ Select individually
46
+ │ ○ Regenerate analysis
47
+ │ ○ Regenerate analysis (different model)
21
48
  │ ○ Cancel
22
-
49
+
50
+ └ Organization complete!
23
51
  ```
24
52
 
25
53
  ## Features
@@ -191,6 +219,11 @@ Edit this file to customize AI behavior for your workflow.
191
219
  2. **Analyzes with AI** - Sends file info to AI with your configured rules
192
220
  3. **Proposes organization** - Shows categorization with confidence levels
193
221
  4. **Confirms with you** - Presents interactive UI for approval
222
+ - Apply all moves
223
+ - View file details
224
+ - Regenerate analysis (optional: with different instructions)
225
+ - Regenerate analysis (different model) — choose another provider/model
226
+ - Cancel
194
227
  5. **Moves files** - Organizes files into target directory structure
195
228
 
196
229
  ## Examples
@@ -198,24 +231,68 @@ Edit this file to customize AI behavior for your workflow.
198
231
  ### Basic Organization
199
232
 
200
233
  ```bash
201
- $ tidyf ~/Downloads
202
- ┌ tidyf
234
+ $ tidyf
235
+ ┌ tidyf
236
+
237
+ ● Source: ~/Downloads
238
+
239
+ ● Target: ~/Downloads/Organized
240
+
241
+ ◇ Found 12 files
242
+
243
+ ● Total size: 45 MB
244
+
245
+ ◇ Analysis complete
246
+
247
+ ● Proposed organization for 12 files:
248
+
249
+ │ Strategy: Primary categorization by file type and MIME type, secondary categorization
250
+ │ by filename context and naming patterns...
251
+
252
+ ● 📄 Documents (5 files)
253
+
254
+ │ [1] 📄 project-proposal.pdf (245 KB)
255
+ │ → ~/Downloads/Organized/Documents/Work/project-proposal.pdf
256
+ │ 📄 Documents/Work 95%
257
+ │ Business document with project keywords
258
+
259
+ ■ What would you like to do?
260
+ │ ● Apply all 12 moves
261
+ │ ○ Select individually
262
+ │ ○ Cancel
263
+
264
+ └ Organization complete!
265
+ ```
266
+
267
+ ### Interactive Configuration
268
+
269
+ ```bash
270
+ $ tidyf config
271
+ ┌ tidyf config
272
+
273
+ ● Configuring global settings
274
+
275
+ │ Config: ~/.tidy/settings.json
276
+
277
+ ◇ What would you like to configure?
278
+ │ AI Model
279
+
280
+ ◇ Fetched 2 providers
281
+
282
+ ● Current Model: opencode/big-pickle
283
+
284
+ ◇ Select AI provider:
285
+ │ OpenCode Zen
203
286
 
204
- Scanning ~/Downloads...
205
- Found 5 files
287
+ Select model:
288
+ glm-4.7-free
206
289
 
207
- Analyzing files with AI...
290
+ Model set to opencode/glm-4.7-free
208
291
 
209
- Organization proposal:
210
- 📄 invoice-2024.pdf → Documents/Receipts
211
- │ 🖼️ vacation-photo.jpg → Images/Photos
212
- │ 📦 backup.zip → Archives
213
- │ 🎵 podcast.mp3 → Audio/Podcasts
214
- │ 💻 installer.dmg → Applications/Installers
292
+ What would you like to configure?
293
+ Done
215
294
 
216
- Apply these changes?
217
- │ ● Yes
218
-
295
+ Configuration saved!
219
296
  ```
220
297
 
221
298
  ### Watch Mode
package/dist/cli.js CHANGED
@@ -18813,6 +18813,143 @@ function getStatusIndicator(status) {
18813
18813
  }
18814
18814
  }
18815
18815
 
18816
+ // src/commands/modelPicker.ts
18817
+ async function pickModel() {
18818
+ const s = Y2();
18819
+ s.start("Fetching available models from OpenCode...");
18820
+ let providers = [];
18821
+ try {
18822
+ const response = await getAvailableModels();
18823
+ if (response.error) {
18824
+ throw new Error("Failed to fetch models");
18825
+ }
18826
+ providers = response.data?.providers || [];
18827
+ s.stop(`Fetched ${providers.length} providers`);
18828
+ } catch (error) {
18829
+ s.stop("Failed to fetch models");
18830
+ M2.error(error.message);
18831
+ M2.warn("Falling back to manual entry.");
18832
+ }
18833
+ let providerId;
18834
+ let modelName;
18835
+ if (providers.length > 0) {
18836
+ const providerOptions = providers.map((prov) => ({
18837
+ value: prov.id,
18838
+ label: prov.name || prov.id
18839
+ }));
18840
+ providerOptions.push({
18841
+ value: "custom",
18842
+ label: "Enter custom provider..."
18843
+ });
18844
+ const selectedProvider = await ve({
18845
+ message: "Select AI provider:",
18846
+ options: providerOptions
18847
+ });
18848
+ if (pD(selectedProvider)) {
18849
+ return;
18850
+ }
18851
+ if (selectedProvider === "custom") {
18852
+ const customProv = await he({
18853
+ message: "Enter provider ID:",
18854
+ placeholder: "opencode",
18855
+ validate: (value) => {
18856
+ if (!value)
18857
+ return "Provider ID is required";
18858
+ }
18859
+ });
18860
+ if (pD(customProv))
18861
+ return;
18862
+ providerId = customProv;
18863
+ const customModel = await he({
18864
+ message: "Enter model ID:",
18865
+ placeholder: "gpt-4o",
18866
+ validate: (value) => {
18867
+ if (!value)
18868
+ return "Model ID is required";
18869
+ }
18870
+ });
18871
+ if (pD(customModel))
18872
+ return;
18873
+ modelName = customModel;
18874
+ } else {
18875
+ providerId = selectedProvider;
18876
+ const providerData = providers.find((p2) => p2.id === providerId);
18877
+ if (!providerData || !providerData.models) {
18878
+ M2.warn(`No models found for provider ${providerId}, please enter manually.`);
18879
+ const customModel = await he({
18880
+ message: "Enter model ID:",
18881
+ placeholder: "gpt-4o",
18882
+ validate: (value) => {
18883
+ if (!value)
18884
+ return "Model ID is required";
18885
+ }
18886
+ });
18887
+ if (pD(customModel))
18888
+ return;
18889
+ modelName = customModel;
18890
+ } else {
18891
+ let modelIds = [];
18892
+ if (Array.isArray(providerData.models)) {
18893
+ modelIds = providerData.models;
18894
+ } else if (typeof providerData.models === "object") {
18895
+ modelIds = Object.keys(providerData.models);
18896
+ }
18897
+ if (modelIds.length === 0) {
18898
+ M2.warn(`No models found for provider ${providerId}`);
18899
+ const customModel = await he({
18900
+ message: "Enter model ID:"
18901
+ });
18902
+ if (pD(customModel))
18903
+ return;
18904
+ modelName = customModel;
18905
+ } else {
18906
+ const modelOptions = modelIds.map((model) => ({
18907
+ value: model,
18908
+ label: model
18909
+ }));
18910
+ modelOptions.push({
18911
+ value: "custom",
18912
+ label: "Enter custom model..."
18913
+ });
18914
+ const selectedModel = await ve({
18915
+ message: "Select model:",
18916
+ options: modelOptions
18917
+ });
18918
+ if (pD(selectedModel))
18919
+ return;
18920
+ if (selectedModel === "custom") {
18921
+ const customModel = await he({
18922
+ message: "Enter model ID:"
18923
+ });
18924
+ if (pD(customModel))
18925
+ return;
18926
+ modelName = customModel;
18927
+ } else {
18928
+ modelName = selectedModel;
18929
+ }
18930
+ }
18931
+ }
18932
+ }
18933
+ } else {
18934
+ const manualEntry = await he({
18935
+ message: "Enter model (format: provider/model):",
18936
+ placeholder: "opencode/gpt-4o",
18937
+ validate: (value) => {
18938
+ if (!value.includes("/")) {
18939
+ return "Model must be in format: provider/model";
18940
+ }
18941
+ }
18942
+ });
18943
+ if (pD(manualEntry)) {
18944
+ return;
18945
+ }
18946
+ const parts = manualEntry.split("/");
18947
+ providerId = parts[0];
18948
+ modelName = parts.slice(1).join("/");
18949
+ }
18950
+ return { provider: providerId, model: modelName };
18951
+ }
18952
+
18816
18953
  // src/commands/organize.ts
18817
18954
  function displayProposal(proposal, index) {
18818
18955
  const icon = getFileIcon(proposal.file.name);
@@ -19000,6 +19137,11 @@ async function organizeCommand(options) {
19000
19137
  label: "Regenerate analysis",
19001
19138
  hint: "Ask AI to re-analyze with different instructions"
19002
19139
  },
19140
+ {
19141
+ value: "regenerate_with_model",
19142
+ label: "Regenerate analysis (different model)",
19143
+ hint: "Re-analyze using another provider/model"
19144
+ },
19003
19145
  {
19004
19146
  value: "cancel",
19005
19147
  label: "Cancel"
@@ -19043,6 +19185,34 @@ async function organizeCommand(options) {
19043
19185
  }
19044
19186
  break;
19045
19187
  }
19188
+ case "regenerate_with_model": {
19189
+ const newInstructions = await he({
19190
+ message: "Enter additional instructions for AI (or press Enter to skip):",
19191
+ placeholder: "e.g., Keep all PDFs together, sort images by date"
19192
+ });
19193
+ if (pD(newInstructions)) {
19194
+ break;
19195
+ }
19196
+ const pickedModel = await pickModel();
19197
+ if (!pickedModel) {
19198
+ break;
19199
+ }
19200
+ spinner.start(`Re-analyzing with ${pickedModel.provider}/${pickedModel.model}...`);
19201
+ try {
19202
+ proposal = await analyzeFiles({
19203
+ files,
19204
+ targetDir: targetPath,
19205
+ instructions: newInstructions || undefined,
19206
+ model: pickedModel
19207
+ });
19208
+ spinner.stop("Analysis complete");
19209
+ displayAllProposals(proposal);
19210
+ } catch (error) {
19211
+ spinner.stop("Analysis failed");
19212
+ M2.error(error.message);
19213
+ }
19214
+ break;
19215
+ }
19046
19216
  }
19047
19217
  }
19048
19218
  }
package/dist/index.js CHANGED
@@ -16714,6 +16714,145 @@ function createWatcher(options) {
16714
16714
  // src/commands/organize.ts
16715
16715
  var import_picocolors4 = __toESM(require_picocolors(), 1);
16716
16716
  import { isAbsolute, resolve } from "path";
16717
+
16718
+ // src/commands/modelPicker.ts
16719
+ async function pickModel() {
16720
+ const s = Y2();
16721
+ s.start("Fetching available models from OpenCode...");
16722
+ let providers = [];
16723
+ try {
16724
+ const response = await getAvailableModels();
16725
+ if (response.error) {
16726
+ throw new Error("Failed to fetch models");
16727
+ }
16728
+ providers = response.data?.providers || [];
16729
+ s.stop(`Fetched ${providers.length} providers`);
16730
+ } catch (error) {
16731
+ s.stop("Failed to fetch models");
16732
+ M2.error(error.message);
16733
+ M2.warn("Falling back to manual entry.");
16734
+ }
16735
+ let providerId;
16736
+ let modelName;
16737
+ if (providers.length > 0) {
16738
+ const providerOptions = providers.map((prov) => ({
16739
+ value: prov.id,
16740
+ label: prov.name || prov.id
16741
+ }));
16742
+ providerOptions.push({
16743
+ value: "custom",
16744
+ label: "Enter custom provider..."
16745
+ });
16746
+ const selectedProvider = await ve({
16747
+ message: "Select AI provider:",
16748
+ options: providerOptions
16749
+ });
16750
+ if (pD(selectedProvider)) {
16751
+ return;
16752
+ }
16753
+ if (selectedProvider === "custom") {
16754
+ const customProv = await he({
16755
+ message: "Enter provider ID:",
16756
+ placeholder: "opencode",
16757
+ validate: (value) => {
16758
+ if (!value)
16759
+ return "Provider ID is required";
16760
+ }
16761
+ });
16762
+ if (pD(customProv))
16763
+ return;
16764
+ providerId = customProv;
16765
+ const customModel = await he({
16766
+ message: "Enter model ID:",
16767
+ placeholder: "gpt-4o",
16768
+ validate: (value) => {
16769
+ if (!value)
16770
+ return "Model ID is required";
16771
+ }
16772
+ });
16773
+ if (pD(customModel))
16774
+ return;
16775
+ modelName = customModel;
16776
+ } else {
16777
+ providerId = selectedProvider;
16778
+ const providerData = providers.find((p2) => p2.id === providerId);
16779
+ if (!providerData || !providerData.models) {
16780
+ M2.warn(`No models found for provider ${providerId}, please enter manually.`);
16781
+ const customModel = await he({
16782
+ message: "Enter model ID:",
16783
+ placeholder: "gpt-4o",
16784
+ validate: (value) => {
16785
+ if (!value)
16786
+ return "Model ID is required";
16787
+ }
16788
+ });
16789
+ if (pD(customModel))
16790
+ return;
16791
+ modelName = customModel;
16792
+ } else {
16793
+ let modelIds = [];
16794
+ if (Array.isArray(providerData.models)) {
16795
+ modelIds = providerData.models;
16796
+ } else if (typeof providerData.models === "object") {
16797
+ modelIds = Object.keys(providerData.models);
16798
+ }
16799
+ if (modelIds.length === 0) {
16800
+ M2.warn(`No models found for provider ${providerId}`);
16801
+ const customModel = await he({
16802
+ message: "Enter model ID:"
16803
+ });
16804
+ if (pD(customModel))
16805
+ return;
16806
+ modelName = customModel;
16807
+ } else {
16808
+ const modelOptions = modelIds.map((model) => ({
16809
+ value: model,
16810
+ label: model
16811
+ }));
16812
+ modelOptions.push({
16813
+ value: "custom",
16814
+ label: "Enter custom model..."
16815
+ });
16816
+ const selectedModel = await ve({
16817
+ message: "Select model:",
16818
+ options: modelOptions
16819
+ });
16820
+ if (pD(selectedModel))
16821
+ return;
16822
+ if (selectedModel === "custom") {
16823
+ const customModel = await he({
16824
+ message: "Enter model ID:"
16825
+ });
16826
+ if (pD(customModel))
16827
+ return;
16828
+ modelName = customModel;
16829
+ } else {
16830
+ modelName = selectedModel;
16831
+ }
16832
+ }
16833
+ }
16834
+ }
16835
+ } else {
16836
+ const manualEntry = await he({
16837
+ message: "Enter model (format: provider/model):",
16838
+ placeholder: "opencode/gpt-4o",
16839
+ validate: (value) => {
16840
+ if (!value.includes("/")) {
16841
+ return "Model must be in format: provider/model";
16842
+ }
16843
+ }
16844
+ });
16845
+ if (pD(manualEntry)) {
16846
+ return;
16847
+ }
16848
+ const parts = manualEntry.split("/");
16849
+ providerId = parts[0];
16850
+ modelName = parts.slice(1).join("/");
16851
+ }
16852
+ return { provider: providerId, model: modelName };
16853
+ }
16854
+
16855
+ // src/commands/organize.ts
16717
16856
  function displayProposal(proposal, index) {
16718
16857
  const icon = getFileIcon(proposal.file.name);
16719
16858
  const size = import_picocolors4.default.dim(`(${formatFileSize(proposal.file.size)})`);
@@ -16900,6 +17039,11 @@ async function organizeCommand(options) {
16900
17039
  label: "Regenerate analysis",
16901
17040
  hint: "Ask AI to re-analyze with different instructions"
16902
17041
  },
17042
+ {
17043
+ value: "regenerate_with_model",
17044
+ label: "Regenerate analysis (different model)",
17045
+ hint: "Re-analyze using another provider/model"
17046
+ },
16903
17047
  {
16904
17048
  value: "cancel",
16905
17049
  label: "Cancel"
@@ -16943,6 +17087,34 @@ async function organizeCommand(options) {
16943
17087
  }
16944
17088
  break;
16945
17089
  }
17090
+ case "regenerate_with_model": {
17091
+ const newInstructions = await he({
17092
+ message: "Enter additional instructions for AI (or press Enter to skip):",
17093
+ placeholder: "e.g., Keep all PDFs together, sort images by date"
17094
+ });
17095
+ if (pD(newInstructions)) {
17096
+ break;
17097
+ }
17098
+ const pickedModel = await pickModel();
17099
+ if (!pickedModel) {
17100
+ break;
17101
+ }
17102
+ spinner.start(`Re-analyzing with ${pickedModel.provider}/${pickedModel.model}...`);
17103
+ try {
17104
+ proposal = await analyzeFiles({
17105
+ files,
17106
+ targetDir: targetPath,
17107
+ instructions: newInstructions || undefined,
17108
+ model: pickedModel
17109
+ });
17110
+ spinner.stop("Analysis complete");
17111
+ displayAllProposals(proposal);
17112
+ } catch (error) {
17113
+ spinner.stop("Analysis failed");
17114
+ M2.error(error.message);
17115
+ }
17116
+ break;
17117
+ }
16946
17118
  }
16947
17119
  }
16948
17120
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tidyf",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "AI-powered file organizer using opencode.ai",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Interactive model picker for regenerate-with-model flow
3
+ */
4
+
5
+ import * as p from "@clack/prompts";
6
+ import { getAvailableModels } from "../lib/opencode.ts";
7
+ import type { ModelSelection } from "../types/config.ts";
8
+
9
+ /**
10
+ * Interactive model selection using OpenCode providers
11
+ */
12
+ export async function pickModel(): Promise<ModelSelection | undefined> {
13
+ const s = p.spinner();
14
+ s.start("Fetching available models from OpenCode...");
15
+
16
+ let providers: any[] = [];
17
+ try {
18
+ const response = await getAvailableModels();
19
+ if (response.error) {
20
+ throw new Error("Failed to fetch models");
21
+ }
22
+ providers = response.data?.providers || [];
23
+ s.stop(`Fetched ${providers.length} providers`);
24
+ } catch (error: any) {
25
+ s.stop("Failed to fetch models");
26
+ p.log.error(error.message);
27
+ p.log.warn("Falling back to manual entry.");
28
+ }
29
+
30
+ let providerId: string;
31
+ let modelName: string;
32
+
33
+ if (providers.length > 0) {
34
+ const providerOptions = providers.map((prov) => ({
35
+ value: prov.id,
36
+ label: prov.name || prov.id,
37
+ }));
38
+
39
+ providerOptions.push({
40
+ value: "custom",
41
+ label: "Enter custom provider...",
42
+ });
43
+
44
+ const selectedProvider = await p.select({
45
+ message: "Select AI provider:",
46
+ options: providerOptions,
47
+ });
48
+
49
+ if (p.isCancel(selectedProvider)) {
50
+ return undefined;
51
+ }
52
+
53
+ if (selectedProvider === "custom") {
54
+ const customProv = await p.text({
55
+ message: "Enter provider ID:",
56
+ placeholder: "opencode",
57
+ validate: (value) => {
58
+ if (!value) return "Provider ID is required";
59
+ },
60
+ });
61
+ if (p.isCancel(customProv)) return undefined;
62
+ providerId = customProv;
63
+
64
+ const customModel = await p.text({
65
+ message: "Enter model ID:",
66
+ placeholder: "gpt-4o",
67
+ validate: (value) => {
68
+ if (!value) return "Model ID is required";
69
+ },
70
+ });
71
+ if (p.isCancel(customModel)) return undefined;
72
+ modelName = customModel;
73
+ } else {
74
+ providerId = selectedProvider as string;
75
+ const providerData = providers.find((p) => p.id === providerId);
76
+
77
+ if (!providerData || !providerData.models) {
78
+ p.log.warn(
79
+ `No models found for provider ${providerId}, please enter manually.`,
80
+ );
81
+ const customModel = await p.text({
82
+ message: "Enter model ID:",
83
+ placeholder: "gpt-4o",
84
+ validate: (value) => {
85
+ if (!value) return "Model ID is required";
86
+ },
87
+ });
88
+ if (p.isCancel(customModel)) return undefined;
89
+ modelName = customModel;
90
+ } else {
91
+ let modelIds: string[] = [];
92
+ if (Array.isArray(providerData.models)) {
93
+ modelIds = providerData.models;
94
+ } else if (typeof providerData.models === "object") {
95
+ modelIds = Object.keys(providerData.models);
96
+ }
97
+
98
+ if (modelIds.length === 0) {
99
+ p.log.warn(`No models found for provider ${providerId}`);
100
+ const customModel = await p.text({
101
+ message: "Enter model ID:",
102
+ });
103
+ if (p.isCancel(customModel)) return undefined;
104
+ modelName = customModel;
105
+ } else {
106
+ const modelOptions = modelIds.map((model: string) => ({
107
+ value: model,
108
+ label: model,
109
+ }));
110
+ modelOptions.push({
111
+ value: "custom",
112
+ label: "Enter custom model...",
113
+ });
114
+
115
+ const selectedModel = await p.select({
116
+ message: "Select model:",
117
+ options: modelOptions,
118
+ });
119
+
120
+ if (p.isCancel(selectedModel)) return undefined;
121
+
122
+ if (selectedModel === "custom") {
123
+ const customModel = await p.text({
124
+ message: "Enter model ID:",
125
+ });
126
+ if (p.isCancel(customModel)) return undefined;
127
+ modelName = customModel;
128
+ } else {
129
+ modelName = selectedModel as string;
130
+ }
131
+ }
132
+ }
133
+ }
134
+ } else {
135
+ const manualEntry = await p.text({
136
+ message: "Enter model (format: provider/model):",
137
+ placeholder: "opencode/gpt-4o",
138
+ validate: (value) => {
139
+ if (!value.includes("/")) {
140
+ return "Model must be in format: provider/model";
141
+ }
142
+ },
143
+ });
144
+
145
+ if (p.isCancel(manualEntry)) {
146
+ return undefined;
147
+ }
148
+
149
+ const parts = manualEntry.split("/");
150
+ providerId = parts[0];
151
+ modelName = parts.slice(1).join("/");
152
+ }
153
+
154
+ return { provider: providerId, model: modelName };
155
+ }
@@ -10,7 +10,6 @@
10
10
  */
11
11
 
12
12
  import * as p from "@clack/prompts";
13
- import { homedir } from "os";
14
13
  import { isAbsolute, resolve } from "path";
15
14
  import color from "picocolors";
16
15
  import {
@@ -27,12 +26,13 @@ import type {
27
26
  OrganizationProposal,
28
27
  OrganizeOptions,
29
28
  } from "../types/organizer.ts";
30
- import { fileExists, formatFileSize, moveFile } from "../utils/files.ts";
29
+ import { formatFileSize, moveFile } from "../utils/files.ts";
31
30
  import {
32
31
  getCategoryIcon,
33
32
  getFileIcon,
34
33
  getStatusIndicator,
35
34
  } from "../utils/icons.ts";
35
+ import { pickModel } from "./modelPicker.ts";
36
36
 
37
37
  /**
38
38
  * Display a single proposal
@@ -337,6 +337,11 @@ export async function organizeCommand(options: OrganizeOptions): Promise<void> {
337
337
  label: "Regenerate analysis",
338
338
  hint: "Ask AI to re-analyze with different instructions",
339
339
  },
340
+ {
341
+ value: "regenerate_with_model",
342
+ label: "Regenerate analysis (different model)",
343
+ hint: "Re-analyze using another provider/model",
344
+ },
340
345
  {
341
346
  value: "cancel",
342
347
  label: "Cancel",
@@ -387,6 +392,39 @@ export async function organizeCommand(options: OrganizeOptions): Promise<void> {
387
392
  }
388
393
  break;
389
394
  }
395
+
396
+ case "regenerate_with_model": {
397
+ const newInstructions = await p.text({
398
+ message:
399
+ "Enter additional instructions for AI (or press Enter to skip):",
400
+ placeholder: "e.g., Keep all PDFs together, sort images by date",
401
+ });
402
+
403
+ if (p.isCancel(newInstructions)) {
404
+ break;
405
+ }
406
+
407
+ const pickedModel = await pickModel();
408
+ if (!pickedModel) {
409
+ break;
410
+ }
411
+
412
+ spinner.start(`Re-analyzing with ${pickedModel.provider}/${pickedModel.model}...`);
413
+ try {
414
+ proposal = await analyzeFiles({
415
+ files,
416
+ targetDir: targetPath,
417
+ instructions: newInstructions || undefined,
418
+ model: pickedModel,
419
+ });
420
+ spinner.stop("Analysis complete");
421
+ displayAllProposals(proposal);
422
+ } catch (error: any) {
423
+ spinner.stop("Analysis failed");
424
+ p.log.error(error.message);
425
+ }
426
+ break;
427
+ }
390
428
  }
391
429
  }
392
430
  }