@utarid/cryo 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 cryo contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,151 @@
1
+ # cryo
2
+
3
+ **cryo** is an intelligent CLI tool designed for polyglot developers. It scans your workspaces, identifies forgotten projects, cleans up heavy build artifacts (like `node_modules`), and archives your source code securely.
4
+
5
+ ## Key Features
6
+
7
+ * **Smart Scanning:** Automatically detects **Node.js, Flutter, Java, Python, Rust, .NET, PHP, and Swift** projects.
8
+ * **Deep Cleaning:** Frees up disk space by deleting build artifacts with surgical precision.
9
+ * **Backup:** Archives your source code into `.tar.gz` files. Capture full snapshots or lightweight source-only backups.
10
+ * **Rebuild Capability:** Triggers build commands directly from the dashboard.
11
+ * **Insightful Dashboard:** Tracks modification dates vs. build status.
12
+ * **Multi-Profile System:** Manage separate configurations for Work, Personal, or Clients.
13
+
14
+ ## Supported Frameworks & Tools
15
+
16
+ cryo automatically detects the following project types and handles their specific build/clean operations:
17
+
18
+ | Framework | Detection File | Clean Target | Build Command |
19
+ | :--- | :--- | :--- | :--- |
20
+ | **Node.js** | `package.json` | `node_modules`, `dist`, `.next` | `npm/yarn/pnpm install && build` |
21
+ | **Flutter** | `pubspec.yaml` | `build`, `.dart_tool` | `flutter pub get` |
22
+ | **Java (Maven)** | `pom.xml` | `target` | `mvn clean install` |
23
+ | **Java (Gradle)**| `build.gradle` | `build`, `.gradle` | `./gradlew build` |
24
+ | **Python** | `requirements.txt` | `venv`, `__pycache__`, `dist` | `pip/pipenv install` |
25
+ | **Rust** | `Cargo.toml` | `target` | `cargo build` |
26
+ | **.NET** | `*.csproj`, `*.sln` | `bin`, `obj` | `dotnet build` |
27
+ | **PHP** | `composer.json` | `vendor` | `composer install` |
28
+ | **Swift/iOS** | `Podfile` | `Pods`, `build`, `DerivedData` | `pod install` / `swift build` |
29
+
30
+ ## ๐Ÿ“ฆ Installation
31
+
32
+ ### Option 1: Run from Source
33
+
34
+ 1. Clone the repository:
35
+ ```bash
36
+ git clone https://github.com/utariddev/cryo.git
37
+ cd cryo
38
+ ```
39
+ 2. Install dependencies:
40
+ ```bash
41
+ npm install
42
+ ```
43
+ 3. Run the CLI:
44
+ ```bash
45
+ npx ts-node src/index.ts
46
+ ```
47
+
48
+ ### Option 2: Install Globally
49
+
50
+ ```bash
51
+ npm install -g @utarid/cryo
52
+ cryo
53
+ ```
54
+
55
+ ## ๐Ÿ“– Usage Guide
56
+
57
+ Just type `cryo` (or the start command) to launch the interactive menu.
58
+
59
+ ### ๐Ÿš€ CLI Commands
60
+
61
+ You can use cryo directly from your terminal without entering the interactive menu.
62
+
63
+ | Command | Description | Example |
64
+ | :--- | :--- | :--- |
65
+ | `cryo profiles list` | List all saved profiles. | `cryo profiles list` |
66
+ | `cryo profiles show <name>` | Show specific profile details. | `cryo profiles show Work` |
67
+ | `cryo profiles create <name> -p <paths>` | Create a new profile. | `cryo profiles create Work -p ./src,./apps` |
68
+ | `cryo profiles delete <name>` | Delete a profile. | `cryo profiles delete Tmp` |
69
+ | `cryo scan <target>` | Scan and list projects in the target path or profile. | `cryo scan ./projects` |
70
+ | `cryo clean <target> [-f] [-d]` | Clean build folders. `-f`: Force, `-d`: Dry-run. | `cryo clean Work -d` |
71
+ | `cryo build <target>` | Rebuild all projects in the target. | `cryo build ./my-app` |
72
+ | `cryo backup <target> [-o <path>]` | Backup projects to a zip archive. | `cryo backup Work -o ./backups` |
73
+
74
+ * **target**: Can be a valid system path (e.g., `./src`) OR a saved profile name (e.g., `Work`).
75
+
76
+ ### ๐Ÿ› ๏ธ Options & Flags
77
+
78
+ | Flag | Long Name | Description |
79
+ | :--- | :--- | :--- |
80
+ | `-f` | `--force` | Skips the confirmation prompt. Dangerous but useful for scripts. |
81
+ | `-d` | `--dry-run` | Simulation mode. Shows what would happen without deleting anything. |
82
+ | `-p` | `--paths` | Comma-separated list of paths (used in `profiles create`). |
83
+ | `-i` | `--ignore` | Comma-separated list of ignore patterns (used in `profiles create`). |
84
+ | `-o` | `--out` | Custom output directory for backups or profiles. |
85
+
86
+ ### 1. Main Menu
87
+ The central hub where you can manage projects or configure profiles.
88
+
89
+ * **๐Ÿ“‚ List Projects:** View all projects in the active profile, sortable by date.
90
+ * **๐Ÿงน Clean Projects:** Select multiple projects to wipe their build folders.
91
+ * **๐Ÿ”จ Build Projects:** Re-compile selected projects (streams output to terminal).
92
+ * **๐Ÿ“ฆ Backup Projects:** Archive source code to your configured Backup Path.
93
+
94
+ > [!IMPORTANT]
95
+ > **Raw Snapshot Philosophy:** By default, cryo archives **everything** (including `node_modules`, `build`, etc.) to ensure a perfect point-in-time snapshot.
96
+ >
97
+ > If you want a **lightweight source-only backup**, simply run the **Clean** command first, then perform the **Backup**.
98
+
99
+ ### 2. Project Explorer
100
+ When you select **List**, cryo shows a clean, aligned table using breadcrumbs for context:
101
+
102
+ ```text
103
+ ๎‚ฑ List Projects > Work
104
+ -----------------------
105
+
106
+ Date Build Status Type Project Name
107
+ ------------------------------------------------------------
108
+ 18.01.2026 19.01.2026 (dist) nodejs cryo (/Users/ben/works/cryo)
109
+ 15.01.2026 None flutter mobile_app (/Users/ben/works/mobile_app)
110
+ ```
111
+ * **Date:** The last time you modified the source code (`src`, `lib`, etc.).
112
+ * **Build Status:** Shows the last build date and the artifact folder. Red "None" means no build found.
113
+
114
+ ### 3. Profiles
115
+ You don't need to scan your whole disk every time. Create profiles for specific workflows:
116
+
117
+ * **Default:** Scans your current working directory.
118
+ * **Work:** Scans `~/Work` and `~/Clients`.
119
+ * **Playground:** Scans `~/Tmp` and `~/Tests`.
120
+
121
+ ## โš™๏ธ Configuration
122
+
123
+ cryo stores its configuration in `~/.cryo/profiles/`. You can edit them via the CLI or manually.
124
+
125
+ **Example Profile (`default.json`):**
126
+ ```json
127
+ {
128
+ "profileName": "Work",
129
+ "scanPaths": ["/Users/me/projects"],
130
+ "ignoredPaths": ["**/*.log", "**/tmp/**"],
131
+ "archivePath": "/Users/me/cryo_Archives"
132
+ }
133
+ ```
134
+
135
+ ## ๐Ÿค Contributing
136
+
137
+ Contributions are welcome!
138
+ 1. Fork the project.
139
+ 2. Create your feature branch (`git checkout -b feature/AmazingFeature`).
140
+ 3. Commit your changes (`git commit -m 'Add some AmazingFeature'`).
141
+ 4. Push to the branch (`git push origin feature/AmazingFeature`).
142
+ 5. Open a Pull Request.
143
+
144
+ ## ๐Ÿ“ License
145
+
146
+ Distributed under the MIT License. See `LICENSE` for more information.
147
+
148
+ ---
149
+
150
+ ## Made with โค๏ธ and [<img src="https://img.shields.io/badge/-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white" height="30">](https://www.typescriptlang.org/) in [<img src="https://upload.wikimedia.org/wikipedia/commons/b/b1/Flag_of_Lower_Austria_%28state%29.svg" height="30">](https://en.wikipedia.org/wiki/Lower_Austria)
151
+
package/bin/cryo.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require('../dist/index.js');
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ // src/core/Archiver.ts
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.Archiver = void 0;
8
+ const fs_extra_1 = __importDefault(require("fs-extra"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const archiver_1 = __importDefault(require("archiver"));
11
+ class Archiver {
12
+ constructor() {
13
+ // Fixed Ignore List
14
+ this.defaultIgnores = [
15
+ '**/.DS_Store'
16
+ ];
17
+ }
18
+ /**
19
+ * Archives the project in .tar.gz format
20
+ */
21
+ async freeze(project, archiveDir, userIgnores = []) {
22
+ // 1. Prepare ignore list
23
+ const finalIgnoreList = [...this.defaultIgnores, ...userIgnores];
24
+ // 2. Create directory
25
+ await fs_extra_1.default.ensureDir(archiveDir);
26
+ const date = new Date().toISOString().split('T')[0];
27
+ const fileName = `${project.name}_${date}.tar.gz`;
28
+ const outputPath = path_1.default.join(archiveDir, fileName);
29
+ return new Promise((resolve, reject) => {
30
+ // Output file (Stream)
31
+ const output = fs_extra_1.default.createWriteStream(outputPath);
32
+ // Archive object
33
+ const archive = (0, archiver_1.default)('tar', {
34
+ gzip: true,
35
+ zlib: { level: 9 }
36
+ });
37
+ // --- EVENT LISTENERS (ERROR HANDLING) ---
38
+ // When process finishes successfully
39
+ output.on('close', () => {
40
+ resolve(outputPath);
41
+ });
42
+ output.on('error', (err) => {
43
+ reject(err);
44
+ });
45
+ // If critical error occurs
46
+ archive.on('error', (err) => {
47
+ reject(err);
48
+ });
49
+ // If warning occurs (e.g. No permission to read file but can continue)
50
+ archive.on('warning', (err) => {
51
+ if (err.code === 'ENOENT') {
52
+ // File not found warning, not critical, log and continue
53
+ console.warn('Archive warning:', err.message);
54
+ }
55
+ else {
56
+ reject(err);
57
+ }
58
+ });
59
+ // Pipe stream
60
+ archive.pipe(output);
61
+ // --- ADD FILES ---
62
+ try {
63
+ archive.glob('**/*', {
64
+ cwd: project.path,
65
+ ignore: finalIgnoreList,
66
+ dot: true // Include files like .env
67
+ });
68
+ // Finalize process
69
+ archive.finalize();
70
+ }
71
+ catch (err) {
72
+ // If a synchronous error occurs during glob, reject promise
73
+ reject(err);
74
+ }
75
+ });
76
+ }
77
+ }
78
+ exports.Archiver = Archiver;
79
+ exports.default = new Archiver();
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ // src/core/Builder.ts
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.Builder = void 0;
8
+ const child_process_1 = require("child_process");
9
+ const fs_extra_1 = __importDefault(require("fs-extra"));
10
+ const path_1 = __importDefault(require("path"));
11
+ class Builder {
12
+ async build(project) {
13
+ // Java/Gradle Permission Fix
14
+ // On Linux/Mac, ensure gradlew is executable
15
+ if (process.platform !== 'win32' && project.type === 'java-gradle') {
16
+ const gradlewPath = path_1.default.join(project.path, 'gradlew');
17
+ try {
18
+ if (await fs_extra_1.default.pathExists(gradlewPath))
19
+ await fs_extra_1.default.chmod(gradlewPath, 0o755);
20
+ }
21
+ catch (e) { }
22
+ }
23
+ const steps = this.getBuildSteps(project);
24
+ if (steps.length === 0)
25
+ return;
26
+ for (const step of steps) {
27
+ await this.runStep(project.path, step.cmd, step.args);
28
+ }
29
+ }
30
+ runStep(cwd, cmd, args) {
31
+ return new Promise((resolve, reject) => {
32
+ const child = (0, child_process_1.spawn)(cmd, args, {
33
+ cwd,
34
+ shell: false, // Security improvement
35
+ stdio: 'inherit'
36
+ });
37
+ child.on('close', (code) => {
38
+ if (code === 0)
39
+ resolve();
40
+ else
41
+ reject(new Error(`Command "${cmd}" exited with code ${code}`));
42
+ });
43
+ child.on('error', (err) => reject(err));
44
+ });
45
+ }
46
+ getBuildSteps(project) {
47
+ const isWin = process.platform === 'win32';
48
+ const fail = [];
49
+ // Helper for script based tools on Windows
50
+ const cmd = (name) => isWin ? `${name}.cmd` : name;
51
+ const bat = (name) => isWin ? `${name}.bat` : name;
52
+ switch (project.type) {
53
+ // Node.js
54
+ case 'nodejs': return this.getNodeSteps(project.path, isWin);
55
+ // Flutter
56
+ case 'flutter':
57
+ // flutter is usually a batch file on windows or shell script on linux/mac
58
+ // but spawn('flutter') might need '.bat' on windows if shell=false
59
+ return [{ cmd: isWin ? 'flutter.bat' : 'flutter', args: ['pub', 'get'] }];
60
+ // Java
61
+ case 'java-maven':
62
+ return [{ cmd: cmd('mvn'), args: ['clean', 'install', '-DskipTests'] }];
63
+ case 'java-gradle':
64
+ const gradlew = isWin ? 'gradlew.bat' : './gradlew';
65
+ return [{ cmd: gradlew, args: ['build', '-x', 'test'] }];
66
+ // Python
67
+ case 'python':
68
+ if (fs_extra_1.default.existsSync(path_1.default.join(project.path, 'requirements.txt'))) {
69
+ // pip is usually exe on windows
70
+ return [{ cmd: 'pip', args: ['install', '-r', 'requirements.txt'] }];
71
+ }
72
+ if (fs_extra_1.default.existsSync(path_1.default.join(project.path, 'Pipfile'))) {
73
+ return [{ cmd: 'pipenv', args: ['install'] }];
74
+ }
75
+ return fail;
76
+ // Rust
77
+ case 'rust': return [{ cmd: 'cargo', args: ['build'] }];
78
+ // .NET
79
+ case 'dotnet': return [{ cmd: 'dotnet', args: ['build'] }];
80
+ // PHP
81
+ case 'php': return [{ cmd: cmd('composer'), args: ['install'] }];
82
+ // Swift
83
+ case 'swift':
84
+ if (fs_extra_1.default.existsSync(path_1.default.join(project.path, 'Podfile'))) {
85
+ return [{ cmd: 'pod', args: ['install'] }];
86
+ }
87
+ return [{ cmd: 'swift', args: ['build'] }];
88
+ default: return fail;
89
+ }
90
+ }
91
+ getNodeSteps(projectPath, isWin) {
92
+ const cmd = (name) => isWin ? `${name}.cmd` : name;
93
+ if (fs_extra_1.default.existsSync(path_1.default.join(projectPath, 'pnpm-lock.yaml'))) {
94
+ return [
95
+ { cmd: cmd('pnpm'), args: ['install'] },
96
+ { cmd: cmd('pnpm'), args: ['build'] }
97
+ ];
98
+ }
99
+ if (fs_extra_1.default.existsSync(path_1.default.join(projectPath, 'yarn.lock'))) {
100
+ return [
101
+ { cmd: cmd('yarn'), args: ['install'] },
102
+ { cmd: cmd('yarn'), args: ['build'] }
103
+ ];
104
+ }
105
+ return [
106
+ { cmd: cmd('npm'), args: ['install'] },
107
+ { cmd: cmd('npm'), args: ['run', 'build'] }
108
+ ];
109
+ }
110
+ }
111
+ exports.Builder = Builder;
112
+ exports.default = new Builder();
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ // src/core/Cleaner.ts
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.Cleaner = void 0;
8
+ const fs_extra_1 = __importDefault(require("fs-extra"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ const child_process_1 = require("child_process");
12
+ const util_1 = __importDefault(require("util"));
13
+ const execAsync = util_1.default.promisify(child_process_1.exec);
14
+ class Cleaner {
15
+ constructor() {
16
+ // Define what to delete
17
+ this.targetFolders = {
18
+ // Old
19
+ 'nodejs': ['node_modules', 'dist', '.next', '.nuxt', 'coverage', '.turbo'],
20
+ 'flutter': ['build', '.dart_tool', 'android/.gradle', 'ios/Pods'],
21
+ 'java-maven': ['target'],
22
+ 'java-gradle': ['build', '.gradle'],
23
+ // Newly Added
24
+ 'python': ['__pycache__', 'venv', '.venv', 'env', 'build', 'dist', '.pytest_cache', '*.egg-info'],
25
+ 'rust': ['target'],
26
+ 'dotnet': ['bin', 'obj'],
27
+ 'php': ['vendor'],
28
+ 'swift': ['Pods', 'build', 'DerivedData', '.build']
29
+ };
30
+ }
31
+ async calculateSize(project) {
32
+ const targets = this.targetFolders[project.type] || [];
33
+ let totalSize = 0;
34
+ for (const folder of targets) {
35
+ const folderPath = path_1.default.join(project.path, folder);
36
+ if (fs_extra_1.default.existsSync(folderPath)) {
37
+ totalSize += await this.getFolderSize(folderPath);
38
+ }
39
+ }
40
+ return parseFloat((totalSize / 1024 / 1024).toFixed(2));
41
+ }
42
+ async getFolderSize(dirPath) {
43
+ // Optimization: Use 'du' on Linux/macOS
44
+ if (process.platform !== 'win32') {
45
+ try {
46
+ // -s: summary, -k: kilobytes (1024 bytes blocks)
47
+ const { stdout } = await execAsync(`du -sk "${dirPath}"`);
48
+ const match = stdout.match(/^(\d+)/);
49
+ if (match && match[1]) {
50
+ // Convert KB to Bytes
51
+ return parseInt(match[1], 10) * 1024;
52
+ }
53
+ }
54
+ catch (e) {
55
+ // Fallback to recursive method if 'du' fails
56
+ }
57
+ }
58
+ return this.getFolderSizeRecursive(dirPath);
59
+ }
60
+ async getFolderSizeRecursive(dirPath) {
61
+ let size = 0;
62
+ try {
63
+ const files = await fs_extra_1.default.readdir(dirPath);
64
+ for (const file of files) {
65
+ const filePath = path_1.default.join(dirPath, file);
66
+ const stats = await fs_extra_1.default.stat(filePath);
67
+ if (stats.isDirectory()) {
68
+ size += await this.getFolderSizeRecursive(filePath);
69
+ }
70
+ else {
71
+ size += stats.size;
72
+ }
73
+ }
74
+ }
75
+ catch (e) {
76
+ console.warn(chalk_1.default.yellow(`\n[WARNING] Clean warning (Size Calc): ${dirPath} - ${e.message}`));
77
+ return 0;
78
+ }
79
+ return size;
80
+ }
81
+ async clean(project, options = {}) {
82
+ const targets = this.targetFolders[project.type];
83
+ if (!targets)
84
+ return false;
85
+ const isDryRun = options.dryRun === true;
86
+ if (isDryRun) {
87
+ console.log(chalk_1.default.blue(`\n[DRY RUN] Simulating clean for ${project.name}:`));
88
+ }
89
+ try {
90
+ for (const folder of targets) {
91
+ const targetPath = path_1.default.join(project.path, folder);
92
+ if (fs_extra_1.default.existsSync(targetPath)) {
93
+ if (isDryRun) {
94
+ const size = await this.getFolderSize(targetPath);
95
+ const sizeStr = (size / 1024 / 1024).toFixed(2);
96
+ console.log(chalk_1.default.gray(` - Would delete: ${folder} (~${sizeStr} MB)`));
97
+ }
98
+ else {
99
+ await fs_extra_1.default.remove(targetPath);
100
+ }
101
+ }
102
+ }
103
+ return true;
104
+ }
105
+ catch (error) {
106
+ console.error(chalk_1.default.red(`Clean error (${project.name}):`), error);
107
+ return false;
108
+ }
109
+ }
110
+ }
111
+ exports.Cleaner = Cleaner;
112
+ exports.default = new Cleaner();
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ // src/core/ConfigManager.ts
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.ConfigManager = void 0;
8
+ const fs_extra_1 = __importDefault(require("fs-extra"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const os_1 = __importDefault(require("os"));
11
+ const chalk_1 = __importDefault(require("chalk"));
12
+ const child_process_1 = require("child_process");
13
+ const CONFIG_DIR = path_1.default.join(os_1.default.homedir(), '.cryo');
14
+ const PROFILES_DIR = path_1.default.join(CONFIG_DIR, 'profiles');
15
+ class ConfigManager {
16
+ constructor() {
17
+ // Ensure directories exist
18
+ fs_extra_1.default.ensureDirSync(PROFILES_DIR);
19
+ }
20
+ getSafeName(name) {
21
+ return name.replace(/[^a-z0-9]/gi, '-').toLowerCase();
22
+ }
23
+ getProfile(profileName = 'default') {
24
+ const safeName = this.getSafeName(profileName);
25
+ const profilePath = path_1.default.join(PROFILES_DIR, `${safeName}.json`);
26
+ if (!fs_extra_1.default.existsSync(profilePath)) {
27
+ if (profileName === 'default') {
28
+ return this.createDefaultProfile();
29
+ }
30
+ throw new Error(`Profile not found: ${profileName}`);
31
+ }
32
+ try {
33
+ return fs_extra_1.default.readJsonSync(profilePath);
34
+ }
35
+ catch (error) {
36
+ console.error(chalk_1.default.red('Config file unreachable or corrupted!'));
37
+ process.exit(1);
38
+ }
39
+ }
40
+ createDefaultProfile() {
41
+ const defaultConfig = {
42
+ profileName: "Default",
43
+ scanPaths: [process.cwd()],
44
+ ignoredPaths: [],
45
+ archivePath: path_1.default.join(os_1.default.homedir(), 'Cryo_Archives')
46
+ };
47
+ const filePath = path_1.default.join(PROFILES_DIR, 'default.json');
48
+ fs_extra_1.default.writeJsonSync(filePath, defaultConfig, { spaces: 2 });
49
+ console.log(chalk_1.default.green(' Default profile (~/.cryo/profiles/default.json) created.'));
50
+ return defaultConfig;
51
+ }
52
+ listProfiles() {
53
+ if (!fs_extra_1.default.existsSync(PROFILES_DIR))
54
+ return [];
55
+ return fs_extra_1.default.readdirSync(PROFILES_DIR)
56
+ .filter(file => file.endsWith('.json'))
57
+ .map(file => file.replace('.json', ''));
58
+ }
59
+ /**
60
+ * Saves a new profile to disk.
61
+ */
62
+ saveProfile(profileName, config) {
63
+ // Clean up the file name (replace spaces with hyphens, remove invalid characters)
64
+ const safeName = this.getSafeName(profileName);
65
+ const filePath = path_1.default.join(PROFILES_DIR, `${safeName}.json`);
66
+ try {
67
+ fs_extra_1.default.writeJsonSync(filePath, config, { spaces: 2 });
68
+ console.log(chalk_1.default.green(` Profile created successfully: ${chalk_1.default.bold(filePath)}`));
69
+ }
70
+ catch (error) {
71
+ console.error(chalk_1.default.red(`Error saving profile: ${error.message}`));
72
+ }
73
+ }
74
+ editProfile(profileName) {
75
+ const safeName = this.getSafeName(profileName);
76
+ const filePath = path_1.default.join(PROFILES_DIR, `${safeName}.json`);
77
+ if (!fs_extra_1.default.existsSync(filePath)) {
78
+ console.log(chalk_1.default.red(`Error: Profile ${profileName} not found.`));
79
+ return;
80
+ }
81
+ console.log(chalk_1.default.yellow(` Opening config file: ${filePath}`));
82
+ // Command based on OS
83
+ let command;
84
+ let args = [filePath];
85
+ switch (process.platform) {
86
+ case 'win32': // Windows
87
+ command = 'notepad';
88
+ break;
89
+ case 'darwin': // Mac
90
+ command = 'open'; // Default editor
91
+ args = ['-t', filePath]; // -t: force text editor
92
+ break;
93
+ default: // Linux
94
+ command = 'xdg-open'; // Default opener
95
+ break;
96
+ }
97
+ // Run command in background
98
+ const child = (0, child_process_1.spawn)(command, args, {
99
+ detached: true,
100
+ stdio: 'ignore'
101
+ });
102
+ child.unref();
103
+ }
104
+ deleteProfile(profileName) {
105
+ const safeName = this.getSafeName(profileName);
106
+ const filePath = path_1.default.join(PROFILES_DIR, `${safeName}.json`);
107
+ if (fs_extra_1.default.existsSync(filePath)) {
108
+ fs_extra_1.default.removeSync(filePath);
109
+ return true;
110
+ }
111
+ return false;
112
+ }
113
+ }
114
+ exports.ConfigManager = ConfigManager;
115
+ exports.default = new ConfigManager();
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.EscapableConfirm = exports.EscapableCheckbox = void 0;
7
+ // We need to import the internal ListPrompt class.
8
+ // Since no types are available for internal files, we ignore TS errors.
9
+ // @ts-ignore
10
+ const list_js_1 = __importDefault(require("inquirer/lib/prompts/list.js"));
11
+ class EscapableList extends list_js_1.default {
12
+ _run(cb) {
13
+ // Listen for raw data to get instant ESC detection (bypassing readline's 500ms timeout)
14
+ // @ts-ignore
15
+ const onData = (chunk) => {
16
+ if (chunk[0] === 0x1b && chunk.length === 1) {
17
+ // Remove listener to prevent memory leaks/double triggers
18
+ // @ts-ignore
19
+ this.rl.input.removeListener('data', onData);
20
+ // When ESC is pressed, submit 'back' immediately.
21
+ // @ts-ignore
22
+ this.onSubmit('back');
23
+ }
24
+ };
25
+ // @ts-ignore
26
+ this.rl.input.on('data', onData);
27
+ // Also cleanup on finish
28
+ // @ts-ignore
29
+ this.rl.on('close', () => this.rl.input.removeListener('data', onData));
30
+ return super._run(cb);
31
+ }
32
+ }
33
+ exports.default = EscapableList;
34
+ // @ts-ignore
35
+ const checkbox_js_1 = __importDefault(require("inquirer/lib/prompts/checkbox.js"));
36
+ class EscapableCheckbox extends checkbox_js_1.default {
37
+ _run(cb) {
38
+ // @ts-ignore
39
+ const onData = (chunk) => {
40
+ if (chunk[0] === 0x1b && chunk.length === 1) {
41
+ // @ts-ignore
42
+ this.rl.input.removeListener('data', onData);
43
+ // @ts-ignore
44
+ this.onSubmit([]);
45
+ }
46
+ };
47
+ // @ts-ignore
48
+ this.rl.input.on('data', onData);
49
+ // @ts-ignore
50
+ this.rl.on('close', () => this.rl.input.removeListener('data', onData));
51
+ // @ts-ignore
52
+ return super._run(cb);
53
+ }
54
+ }
55
+ exports.EscapableCheckbox = EscapableCheckbox;
56
+ // @ts-ignore
57
+ const confirm_js_1 = __importDefault(require("inquirer/lib/prompts/confirm.js"));
58
+ class EscapableConfirm extends confirm_js_1.default {
59
+ _run(cb) {
60
+ // @ts-ignore
61
+ const onData = (chunk) => {
62
+ if (chunk[0] === 0x1b && chunk.length === 1) {
63
+ // @ts-ignore
64
+ this.rl.input.removeListener('data', onData);
65
+ // @ts-ignore
66
+ this.onSubmit(false);
67
+ }
68
+ };
69
+ // @ts-ignore
70
+ this.rl.input.on('data', onData);
71
+ // @ts-ignore
72
+ this.rl.on('close', () => this.rl.input.removeListener('data', onData));
73
+ // @ts-ignore
74
+ return super._run(cb);
75
+ }
76
+ }
77
+ exports.EscapableConfirm = EscapableConfirm;