tidyf 1.0.3 → 1.1.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.
- package/dist/cli.js +18000 -15880
- package/dist/index.js +18450 -16419
- package/package.json +1 -1
- package/src/cli.ts +42 -14
- package/src/commands/config.ts +70 -1
- package/src/commands/organize.ts +679 -44
- package/src/commands/profile.ts +943 -0
- package/src/commands/undo.ts +139 -0
- package/src/commands/watch.ts +24 -2
- package/src/lib/config.ts +69 -0
- package/src/lib/history.ts +139 -0
- package/src/lib/opencode.ts +11 -5
- package/src/lib/presets.ts +257 -0
- package/src/lib/profiles.ts +367 -0
- package/src/types/organizer.ts +24 -0
- package/src/types/profile.ts +70 -0
- package/src/utils/files.ts +15 -1
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -9,6 +9,8 @@ import updateNotifier from "simple-update-notifier";
|
|
|
9
9
|
import { createRequire } from "module";
|
|
10
10
|
import { configCommand } from "./commands/config.ts";
|
|
11
11
|
import { organizeCommand } from "./commands/organize.ts";
|
|
12
|
+
import { profileCommand } from "./commands/profile.ts";
|
|
13
|
+
import { undoCommand } from "./commands/undo.ts";
|
|
12
14
|
import { watchCommand } from "./commands/watch.ts";
|
|
13
15
|
|
|
14
16
|
const require = createRequire(import.meta.url);
|
|
@@ -24,20 +26,6 @@ program
|
|
|
24
26
|
.description("AI-powered file organizer using opencode.ai")
|
|
25
27
|
.version(pkg.version);
|
|
26
28
|
|
|
27
|
-
// Default command - organize files
|
|
28
|
-
program
|
|
29
|
-
.argument("[path]", "Directory to organize (default: ~/Downloads)")
|
|
30
|
-
.option("-d, --dry-run", "Preview changes without moving files")
|
|
31
|
-
.option("-y, --yes", "Skip confirmation prompts and apply all")
|
|
32
|
-
.option("-r, --recursive", "Scan subdirectories")
|
|
33
|
-
.option("--depth <n>", "Max subdirectory depth to scan", "1")
|
|
34
|
-
.option("-s, --source <path>", "Source directory to organize")
|
|
35
|
-
.option("-t, --target <path>", "Target directory for organized files")
|
|
36
|
-
.option("-m, --model <id>", "Override model (provider/model)")
|
|
37
|
-
.action(async (path, options) => {
|
|
38
|
-
await organizeCommand({ path: path || options.source, ...options });
|
|
39
|
-
});
|
|
40
|
-
|
|
41
29
|
// Watch command - monitor folders for new files
|
|
42
30
|
program
|
|
43
31
|
.command("watch")
|
|
@@ -48,10 +36,24 @@ program
|
|
|
48
36
|
.option("-a, --auto", "Auto-apply without confirmation")
|
|
49
37
|
.option("-q, --queue", "Queue files for review instead of auto-apply")
|
|
50
38
|
.option("-m, --model <id>", "Override model (provider/model)")
|
|
39
|
+
.option("-p, --profile <name>", "Use named profile")
|
|
51
40
|
.action(async (paths, options) => {
|
|
52
41
|
await watchCommand({ paths, ...options });
|
|
53
42
|
});
|
|
54
43
|
|
|
44
|
+
// Profile command - manage organization profiles
|
|
45
|
+
program
|
|
46
|
+
.command("profile [action]")
|
|
47
|
+
.alias("pr")
|
|
48
|
+
.description("Manage organization profiles")
|
|
49
|
+
.argument("[name]", "Profile name for action")
|
|
50
|
+
.argument("[extra]", "Extra argument (e.g., destination for copy)")
|
|
51
|
+
.option("-c, --from-current", "Create from current effective config")
|
|
52
|
+
.option("-f, --force", "Skip confirmation prompts")
|
|
53
|
+
.action(async (action, name, extra, options) => {
|
|
54
|
+
await profileCommand({ action, name, args: extra ? [extra] : [], ...options });
|
|
55
|
+
});
|
|
56
|
+
|
|
55
57
|
// Config command - configure settings
|
|
56
58
|
program
|
|
57
59
|
.command("config")
|
|
@@ -62,6 +64,32 @@ program
|
|
|
62
64
|
await configCommand(options);
|
|
63
65
|
});
|
|
64
66
|
|
|
67
|
+
// Undo command - revert file organization
|
|
68
|
+
program
|
|
69
|
+
.command("undo")
|
|
70
|
+
.alias("u")
|
|
71
|
+
.description("Undo the last file organization operation")
|
|
72
|
+
.action(async () => {
|
|
73
|
+
await undoCommand();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Default command - organize files (must be defined last to not intercept subcommands)
|
|
77
|
+
program
|
|
78
|
+
.argument("[path]", "Directory to organize (default: ~/Downloads)")
|
|
79
|
+
.option("-d, --dry-run", "Preview changes without moving files")
|
|
80
|
+
.option("-y, --yes", "Skip confirmation prompts and apply all")
|
|
81
|
+
.option("-r, --recursive", "Scan subdirectories")
|
|
82
|
+
.option("--depth <n>", "Max subdirectory depth to scan", "1")
|
|
83
|
+
.option("-s, --source <path>", "Source directory to organize")
|
|
84
|
+
.option("-t, --target <path>", "Target directory for organized files")
|
|
85
|
+
.option("-m, --model <id>", "Override model (provider/model)")
|
|
86
|
+
.option("-p, --profile <name>", "Use named profile")
|
|
87
|
+
.option("--json", "Output JSON instead of interactive UI")
|
|
88
|
+
.option("--detect-duplicates", "Detect duplicate files by content hash")
|
|
89
|
+
.action(async (path, options) => {
|
|
90
|
+
await organizeCommand({ path: path || options.source, ...options });
|
|
91
|
+
});
|
|
92
|
+
|
|
65
93
|
// Handle errors gracefully
|
|
66
94
|
process.on("unhandledRejection", (error: Error) => {
|
|
67
95
|
console.error("Error:", error.message);
|
package/src/commands/config.ts
CHANGED
|
@@ -21,17 +21,36 @@ import {
|
|
|
21
21
|
writeRules,
|
|
22
22
|
} from "../lib/config.ts";
|
|
23
23
|
import { cleanup, getAvailableModels } from "../lib/opencode.ts";
|
|
24
|
+
import {
|
|
25
|
+
listProfiles,
|
|
26
|
+
profileExists,
|
|
27
|
+
validateProfileName,
|
|
28
|
+
writeProfile,
|
|
29
|
+
} from "../lib/profiles.ts";
|
|
24
30
|
import type {
|
|
25
31
|
ConfigOptions,
|
|
26
32
|
ModelSelection,
|
|
27
33
|
TidyConfig,
|
|
28
34
|
} from "../types/config.ts";
|
|
35
|
+
import type { Profile } from "../types/profile.ts";
|
|
29
36
|
|
|
30
37
|
/**
|
|
31
38
|
* Main config command
|
|
32
39
|
*/
|
|
33
40
|
export async function configCommand(options: ConfigOptions): Promise<void> {
|
|
34
|
-
|
|
41
|
+
try {
|
|
42
|
+
p.intro(color.bgCyan(color.black(" tidyf config ")));
|
|
43
|
+
} catch (error: any) {
|
|
44
|
+
console.log(color.cyan(" tidyf config "));
|
|
45
|
+
console.log();
|
|
46
|
+
console.log("Unable to display interactive interface.");
|
|
47
|
+
console.log("Your terminal may not support interactive prompts.");
|
|
48
|
+
console.log();
|
|
49
|
+
console.log(`Edit your config directly at: ${getGlobalConfigPath()}`);
|
|
50
|
+
console.log(`Edit your rules at: ${getGlobalRulesPath()}`);
|
|
51
|
+
console.log();
|
|
52
|
+
process.exit(0);
|
|
53
|
+
}
|
|
35
54
|
|
|
36
55
|
// Initialize global config if needed
|
|
37
56
|
initGlobalConfig();
|
|
@@ -95,6 +114,11 @@ export async function configCommand(options: ConfigOptions): Promise<void> {
|
|
|
95
114
|
value: "view",
|
|
96
115
|
label: "View Current Configuration",
|
|
97
116
|
},
|
|
117
|
+
{
|
|
118
|
+
value: "save_as_profile",
|
|
119
|
+
label: "Save as Profile",
|
|
120
|
+
hint: "Create a new profile from current settings",
|
|
121
|
+
},
|
|
98
122
|
{
|
|
99
123
|
value: "reset",
|
|
100
124
|
label: "Reset to Defaults",
|
|
@@ -137,6 +161,9 @@ export async function configCommand(options: ConfigOptions): Promise<void> {
|
|
|
137
161
|
case "view":
|
|
138
162
|
viewConfig(effectiveConfig, scope);
|
|
139
163
|
break;
|
|
164
|
+
case "save_as_profile":
|
|
165
|
+
await saveAsProfile(effectiveConfig);
|
|
166
|
+
break;
|
|
140
167
|
case "reset":
|
|
141
168
|
await resetConfig(configPath, rulesPath, scope);
|
|
142
169
|
break;
|
|
@@ -628,3 +655,45 @@ async function resetConfig(
|
|
|
628
655
|
|
|
629
656
|
p.log.success("Configuration reset to defaults");
|
|
630
657
|
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Save current configuration as a new profile
|
|
661
|
+
*/
|
|
662
|
+
async function saveAsProfile(config: TidyConfig): Promise<void> {
|
|
663
|
+
// Get profile name
|
|
664
|
+
const name = await p.text({
|
|
665
|
+
message: "Profile name:",
|
|
666
|
+
placeholder: "work",
|
|
667
|
+
validate: (value) => {
|
|
668
|
+
const validation = validateProfileName(value);
|
|
669
|
+
if (!validation.valid) return validation.error;
|
|
670
|
+
if (profileExists(value)) return `Profile "${value}" already exists`;
|
|
671
|
+
},
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
if (p.isCancel(name)) return;
|
|
675
|
+
|
|
676
|
+
// Get description
|
|
677
|
+
const description = await p.text({
|
|
678
|
+
message: "Description (optional):",
|
|
679
|
+
placeholder: "e.g., Work documents and projects",
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
// Create profile from current config
|
|
683
|
+
const profile: Profile = {
|
|
684
|
+
name,
|
|
685
|
+
description: p.isCancel(description) ? undefined : description || undefined,
|
|
686
|
+
...config,
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
writeProfile(name, profile);
|
|
690
|
+
|
|
691
|
+
p.log.success(`Profile "${name}" created!`);
|
|
692
|
+
p.log.message(color.dim(`Use with: tidyf -p ${name}`));
|
|
693
|
+
|
|
694
|
+
// Show existing profiles
|
|
695
|
+
const profiles = listProfiles();
|
|
696
|
+
if (profiles.length > 1) {
|
|
697
|
+
p.log.info(`You now have ${profiles.length} profiles: ${profiles.map((pr) => pr.name).join(", ")}`);
|
|
698
|
+
}
|
|
699
|
+
}
|