repo-cloak-cli 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.
@@ -0,0 +1,36 @@
1
+ name: Publish to npm
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ - master
8
+ paths-ignore:
9
+ - '*.md'
10
+ - '.gitignore'
11
+
12
+ jobs:
13
+ publish:
14
+ runs-on: ubuntu-latest
15
+
16
+ steps:
17
+ - name: ๐ŸŽญ Checkout repository
18
+ uses: actions/checkout@v4
19
+
20
+ - name: ๐Ÿ“ฆ Setup Node.js
21
+ uses: actions/setup-node@v4
22
+ with:
23
+ node-version: '20'
24
+ registry-url: 'https://registry.npmjs.org'
25
+
26
+ - name: ๐Ÿ“ฅ Install dependencies
27
+ run: npm ci
28
+
29
+ - name: ๐Ÿงช Run tests (if any)
30
+ run: npm test --if-present
31
+ continue-on-error: true
32
+
33
+ - name: ๐Ÿš€ Publish to npm
34
+ run: npm publish --access public
35
+ env:
36
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -0,0 +1,49 @@
1
+ name: Version Bump & Publish
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ version_type:
7
+ description: 'Version bump type'
8
+ required: true
9
+ default: 'patch'
10
+ type: choice
11
+ options:
12
+ - patch
13
+ - minor
14
+ - major
15
+
16
+ jobs:
17
+ version-and-publish:
18
+ runs-on: ubuntu-latest
19
+
20
+ steps:
21
+ - name: ๐ŸŽญ Checkout repository
22
+ uses: actions/checkout@v4
23
+ with:
24
+ token: ${{ secrets.GITHUB_TOKEN }}
25
+
26
+ - name: ๐Ÿ“ฆ Setup Node.js
27
+ uses: actions/setup-node@v4
28
+ with:
29
+ node-version: '20'
30
+ registry-url: 'https://registry.npmjs.org'
31
+
32
+ - name: ๐Ÿ”ง Configure Git
33
+ run: |
34
+ git config user.name "github-actions[bot]"
35
+ git config user.email "github-actions[bot]@users.noreply.github.com"
36
+
37
+ - name: ๐Ÿ“ฅ Install dependencies
38
+ run: npm ci
39
+
40
+ - name: ๐Ÿ“ Bump version
41
+ run: npm version ${{ inputs.version_type }} -m "chore: bump version to %s"
42
+
43
+ - name: ๐Ÿ“ค Push changes
44
+ run: git push --follow-tags
45
+
46
+ - name: ๐Ÿš€ Publish to npm
47
+ run: npm publish --access public
48
+ env:
49
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024
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,118 @@
1
+ # repo-cloak ๐ŸŽญ
2
+
3
+ > **Selectively extract and anonymize files from repositories**
4
+
5
+ Perfect for sharing code with AI agents without exposing proprietary details. Extract specific files, replace sensitive information (company names, project names, etc.), and restore them later.
6
+
7
+ [![npm version](https://img.shields.io/npm/v/repo-cloak.svg)](https://www.npmjs.com/package/repo-cloak)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
+
10
+ ## โœจ Features
11
+
12
+ - ๐Ÿ” **Interactive file browser** - Navigate and select files with a beautiful TUI
13
+ - ๐ŸŽญ **Smart anonymization** - Replace sensitive keywords while preserving case
14
+ - ๐Ÿ“ **Structure preservation** - Maintains original folder hierarchy
15
+ - ๐Ÿ”„ **Push/Pull workflow** - Extract files, work on them, push back with original names
16
+ - ๐Ÿ’พ **Mapping file** - Tracks all replacements for seamless restoration
17
+ - ๐ŸŒ **Cross-platform** - Works on Windows, macOS, and Linux
18
+
19
+ ## ๐Ÿ“ฆ Installation
20
+
21
+ ```bash
22
+ npm install -g repo-cloak-cli
23
+ ```
24
+
25
+ Or use directly with npx:
26
+
27
+ ```bash
28
+ npx repo-cloak-cli
29
+ ```
30
+
31
+ ## ๐Ÿš€ Quick Start
32
+
33
+ ### Pull (Extract & Anonymize)
34
+
35
+ ```bash
36
+ # Interactive mode
37
+ repo-cloak pull
38
+
39
+ # With options
40
+ repo-cloak pull --source ./my-project --dest ./extracted
41
+ ```
42
+
43
+ 1. Select files/folders to extract
44
+ 2. Enter destination path
45
+ 3. Add keyword replacements (e.g., "Microsoft Corp" โ†’ "ACME Inc")
46
+ 4. Confirm and extract!
47
+
48
+ ### Push (Restore)
49
+
50
+ ```bash
51
+ # Interactive mode
52
+ repo-cloak push
53
+
54
+ # With options
55
+ repo-cloak push --source ./extracted --dest ./my-project
56
+ ```
57
+
58
+ Restores all files with original keywords replaced back.
59
+
60
+ ## ๐ŸŽฏ Use Cases
61
+
62
+ - **AI Code Review** - Share proprietary code with AI tools by anonymizing company/project names
63
+ - **Open Source Templates** - Extract project templates while removing internal references
64
+ - **Code Samples** - Create sanitized examples from production code
65
+ - **Compliance** - Remove sensitive identifiers before sharing code externally
66
+
67
+ ## ๐Ÿ“‹ Commands
68
+
69
+ | Command | Description |
70
+ |---------|-------------|
71
+ | `repo-cloak` | Interactive menu to choose pull or push |
72
+ | `repo-cloak pull` | Extract and anonymize files |
73
+ | `repo-cloak push` | Restore files with original names |
74
+ | `repo-cloak --help` | Show help |
75
+ | `repo-cloak --version` | Show version |
76
+
77
+ ## ๐Ÿ”ง Options
78
+
79
+ ### Pull Options
80
+ - `-s, --source <path>` - Source directory (default: current directory)
81
+ - `-d, --dest <path>` - Destination directory
82
+ - `-q, --quiet` - Minimal output
83
+
84
+ ### Push Options
85
+ - `-s, --source <path>` - Cloaked backup directory
86
+ - `-d, --dest <path>` - Destination directory
87
+ - `-q, --quiet` - Minimal output
88
+
89
+ ## ๐Ÿ“ How It Works
90
+
91
+ 1. **Pull** creates a `.repo-cloak-map.json` file in the destination that stores:
92
+ - Original source path
93
+ - All keyword replacements
94
+ - File list with mappings
95
+ - Timestamp
96
+
97
+ 2. **Push** reads this mapping file to:
98
+ - Reverse all keyword replacements
99
+ - Restore files to original or new location
100
+
101
+ ## ๐Ÿ”’ Privacy by Design
102
+
103
+ - No data is sent to any external servers
104
+ - All processing happens locally
105
+ - Binary files are copied without modification
106
+ - Hidden files and common ignored directories (node_modules, .git) are skipped
107
+
108
+ ## ๐Ÿค Contributing
109
+
110
+ Contributions are welcome! Please feel free to submit a Pull Request.
111
+
112
+ ## ๐Ÿ“„ License
113
+
114
+ MIT ยฉ Shazni Shiraz
115
+
116
+ ---
117
+
118
+ Made with โค๏ธ for developers who need to share code safely
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * repo-cloak CLI Entry Point
5
+ * ๐ŸŽญ Selectively extract and anonymize files from repositories
6
+ */
7
+
8
+ import { run } from '../src/cli.js';
9
+
10
+ run();
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "repo-cloak-cli",
3
+ "version": "1.0.0",
4
+ "description": "๐ŸŽญ Selectively extract and anonymize files from repositories. Perfect for sharing code with AI agents without exposing proprietary details.",
5
+ "main": "src/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "repo-cloak": "./bin/repo-cloak.js",
9
+ "cloak": "./bin/repo-cloak.js"
10
+ },
11
+ "scripts": {
12
+ "start": "node bin/repo-cloak.js",
13
+ "test": "node --test",
14
+ "lint": "eslint src/"
15
+ },
16
+ "keywords": [
17
+ "cli",
18
+ "anonymize",
19
+ "extract",
20
+ "repository",
21
+ "code",
22
+ "privacy",
23
+ "ai",
24
+ "llm",
25
+ "obfuscate",
26
+ "cloak",
27
+ "mask",
28
+ "sanitize"
29
+ ],
30
+ "author": "",
31
+ "license": "MIT",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": ""
35
+ },
36
+ "engines": {
37
+ "node": ">=18.0.0"
38
+ },
39
+ "dependencies": {
40
+ "chalk": "^5.3.0",
41
+ "commander": "^12.1.0",
42
+ "figlet": "^1.7.0",
43
+ "glob": "^10.3.10",
44
+ "inquirer": "^9.2.12",
45
+ "ora": "^8.0.1"
46
+ },
47
+ "devDependencies": {}
48
+ }
package/src/cli.js ADDED
@@ -0,0 +1,84 @@
1
+ /**
2
+ * CLI Command Router
3
+ * Handles command-line arguments and routes to appropriate handlers
4
+ */
5
+
6
+ import { Command } from 'commander';
7
+ import { showBanner } from './ui/banner.js';
8
+ import { pull } from './commands/pull.js';
9
+ import { push } from './commands/push.js';
10
+ import { readFileSync } from 'fs';
11
+ import { fileURLToPath } from 'url';
12
+ import { dirname, join } from 'path';
13
+
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = dirname(__filename);
16
+
17
+ // Read version from package.json
18
+ const packageJson = JSON.parse(
19
+ readFileSync(join(__dirname, '..', 'package.json'), 'utf-8')
20
+ );
21
+
22
+ const program = new Command();
23
+
24
+ program
25
+ .name('repo-cloak')
26
+ .description('๐ŸŽญ Selectively extract and anonymize files from repositories')
27
+ .version(packageJson.version);
28
+
29
+ program
30
+ .command('pull')
31
+ .description('Extract files and anonymize sensitive information')
32
+ .option('-s, --source <path>', 'Source directory (default: current directory)')
33
+ .option('-d, --dest <path>', 'Destination directory')
34
+ .option('-q, --quiet', 'Minimal output')
35
+ .action(async (options) => {
36
+ await showBanner();
37
+ await pull(options);
38
+ });
39
+
40
+ program
41
+ .command('push')
42
+ .description('Restore files with original names from a cloaked backup')
43
+ .option('-s, --source <path>', 'Source cloaked directory')
44
+ .option('-d, --dest <path>', 'Destination directory (original location)')
45
+ .option('-q, --quiet', 'Minimal output')
46
+ .action(async (options) => {
47
+ await showBanner();
48
+ await push(options);
49
+ });
50
+
51
+ // Default command (no subcommand) - show interactive menu
52
+ program
53
+ .action(async () => {
54
+ await showBanner();
55
+ const { default: inquirer } = await import('inquirer');
56
+
57
+ const { action } = await inquirer.prompt([
58
+ {
59
+ type: 'list',
60
+ name: 'action',
61
+ message: 'What would you like to do?',
62
+ choices: [
63
+ { name: '๐Ÿ“ค Pull - Extract & anonymize files', value: 'pull' },
64
+ { name: '๐Ÿ“ฅ Push - Restore files with original names', value: 'push' },
65
+ { name: 'โŒ Exit', value: 'exit' }
66
+ ]
67
+ }
68
+ ]);
69
+
70
+ if (action === 'pull') {
71
+ await pull({});
72
+ } else if (action === 'push') {
73
+ await push({});
74
+ }
75
+ });
76
+
77
+ export async function run() {
78
+ try {
79
+ await program.parseAsync(process.argv);
80
+ } catch (error) {
81
+ console.error('Error:', error.message);
82
+ process.exit(1);
83
+ }
84
+ }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Pull Command
3
+ * Extract files and anonymize sensitive information
4
+ */
5
+
6
+ import ora from 'ora';
7
+ import chalk from 'chalk';
8
+ import { existsSync, mkdirSync } from 'fs';
9
+ import { resolve, relative } from 'path';
10
+
11
+ import { selectFiles } from '../ui/fileSelector.js';
12
+ import {
13
+ promptSourceDirectory,
14
+ promptDestinationDirectory,
15
+ promptKeywordReplacements,
16
+ showSummaryAndConfirm
17
+ } from '../ui/prompts.js';
18
+ import { showSuccess, showError, showInfo } from '../ui/banner.js';
19
+ import { getAllFiles } from '../core/scanner.js';
20
+ import { copyFiles } from '../core/copier.js';
21
+ import { createAnonymizer } from '../core/anonymizer.js';
22
+ import { createMapping, saveMapping } from '../core/mapper.js';
23
+
24
+ export async function pull(options = {}) {
25
+ try {
26
+ // Step 1: Get source directory
27
+ const sourceDir = options.source
28
+ ? resolve(options.source)
29
+ : await promptSourceDirectory();
30
+
31
+ if (!existsSync(sourceDir)) {
32
+ showError(`Source directory does not exist: ${sourceDir}`);
33
+ return;
34
+ }
35
+
36
+ console.log(chalk.dim(`\n Source: ${sourceDir}\n`));
37
+
38
+ // Step 2: Select files
39
+ const selectedFiles = await selectFiles(sourceDir);
40
+
41
+ if (selectedFiles.length === 0) {
42
+ showError('No files selected. Aborting.');
43
+ return;
44
+ }
45
+
46
+ console.log(chalk.green(`\nโœ“ Selected ${selectedFiles.length} files\n`));
47
+
48
+ // Step 3: Get destination directory
49
+ const destDir = options.dest
50
+ ? resolve(options.dest)
51
+ : await promptDestinationDirectory();
52
+
53
+ // Step 4: Get keyword replacements
54
+ const replacements = await promptKeywordReplacements();
55
+
56
+ // Step 5: Confirm
57
+ const confirmed = await showSummaryAndConfirm(
58
+ selectedFiles.length,
59
+ destDir,
60
+ replacements
61
+ );
62
+
63
+ if (!confirmed) {
64
+ showInfo('Operation cancelled.');
65
+ return;
66
+ }
67
+
68
+ // Step 6: Create destination directory
69
+ if (!existsSync(destDir)) {
70
+ mkdirSync(destDir, { recursive: true });
71
+ console.log(chalk.dim(` Created directory: ${destDir}`));
72
+ }
73
+
74
+ // Step 7: Copy and anonymize files
75
+ const spinner = ora('Copying and anonymizing files...').start();
76
+
77
+ const anonymizer = createAnonymizer(replacements);
78
+ let lastFile = '';
79
+
80
+ const results = await copyFiles(
81
+ selectedFiles,
82
+ sourceDir,
83
+ destDir,
84
+ anonymizer,
85
+ (current, total, file) => {
86
+ lastFile = file;
87
+ spinner.text = `Copying files... ${current}/${total} - ${file}`;
88
+ }
89
+ );
90
+
91
+ spinner.succeed(`Copied ${results.copied} files`);
92
+
93
+ if (results.transformed > 0) {
94
+ console.log(chalk.cyan(` ๐Ÿ“ ${results.transformed} files had content replaced`));
95
+ }
96
+
97
+ if (results.errors.length > 0) {
98
+ console.log(chalk.yellow(` โš ๏ธ ${results.errors.length} files had errors`));
99
+ results.errors.forEach(e => {
100
+ console.log(chalk.dim(` - ${e.file}: ${e.error}`));
101
+ });
102
+ }
103
+
104
+ // Step 8: Save mapping file
105
+ const mapping = createMapping({
106
+ sourceDir,
107
+ destDir,
108
+ replacements,
109
+ files: selectedFiles.map(f => ({
110
+ relativePath: relative(sourceDir, f)
111
+ }))
112
+ });
113
+
114
+ const mapPath = saveMapping(destDir, mapping);
115
+ console.log(chalk.dim(` ๐Ÿ“‹ Mapping saved: ${mapPath}`));
116
+
117
+ // Done!
118
+ showSuccess('Extraction complete!');
119
+ console.log(chalk.white(` ๐Ÿ“‚ Files extracted to: ${chalk.cyan.bold(destDir)}`));
120
+ console.log(chalk.dim(`\n To restore later, run: ${chalk.white('repo-cloak push')}\n`));
121
+
122
+ } catch (error) {
123
+ showError(`Pull failed: ${error.message}`);
124
+ if (process.env.DEBUG) {
125
+ console.error(error);
126
+ }
127
+ }
128
+ }
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Push Command
3
+ * Restore files with original names from a cloaked backup
4
+ */
5
+
6
+ import ora from 'ora';
7
+ import chalk from 'chalk';
8
+ import { existsSync, mkdirSync, readdirSync } from 'fs';
9
+ import { resolve, join, relative } from 'path';
10
+
11
+ import {
12
+ promptBackupFolder,
13
+ promptDestinationDirectory,
14
+ confirmAction
15
+ } from '../ui/prompts.js';
16
+ import { showSuccess, showError, showInfo, showWarning } from '../ui/banner.js';
17
+ import { getAllFiles } from '../core/scanner.js';
18
+ import { copyFiles } from '../core/copier.js';
19
+ import { createDeanonymizer } from '../core/anonymizer.js';
20
+ import { loadMapping, hasMapping, getReplacements, getOriginalSource } from '../core/mapper.js';
21
+
22
+ export async function push(options = {}) {
23
+ try {
24
+ // Step 1: Get source (cloaked) directory
25
+ const cloakedDir = options.source
26
+ ? resolve(options.source)
27
+ : await promptBackupFolder();
28
+
29
+ if (!existsSync(cloakedDir)) {
30
+ showError(`Directory does not exist: ${cloakedDir}`);
31
+ return;
32
+ }
33
+
34
+ // Step 2: Check for mapping file
35
+ if (!hasMapping(cloakedDir)) {
36
+ showError('No repo-cloak mapping file found in this directory.');
37
+ console.log(chalk.dim(' Make sure you selected a directory created by "repo-cloak pull"'));
38
+ return;
39
+ }
40
+
41
+ // Step 3: Load mapping
42
+ const spinner = ora('Loading mapping file...').start();
43
+ const mapping = loadMapping(cloakedDir);
44
+ spinner.succeed('Mapping file loaded');
45
+
46
+ console.log(chalk.dim(`\n Original source: ${mapping.source?.path || 'Unknown'}`));
47
+ console.log(chalk.dim(` Extracted on: ${mapping.timestamp}`));
48
+ console.log(chalk.dim(` Replacements: ${mapping.replacements?.length || 0}`));
49
+ console.log(chalk.dim(` Files: ${mapping.files?.length || 0}\n`));
50
+
51
+ // Show replacements that will be reversed
52
+ if (mapping.replacements && mapping.replacements.length > 0) {
53
+ console.log(chalk.cyan(' Replacements to reverse:'));
54
+ mapping.replacements.forEach(r => {
55
+ console.log(chalk.dim(` "${r.replacement}" โ†’ "${r.original}"`));
56
+ });
57
+ console.log('');
58
+ }
59
+
60
+ // Step 4: Get destination directory
61
+ const originalPath = getOriginalSource(mapping);
62
+ let destDir;
63
+
64
+ if (options.dest) {
65
+ destDir = resolve(options.dest);
66
+ } else if (originalPath && existsSync(originalPath)) {
67
+ const useOriginal = await confirmAction(
68
+ `Restore to original location? (${originalPath})`
69
+ );
70
+
71
+ if (useOriginal) {
72
+ destDir = originalPath;
73
+ } else {
74
+ destDir = await promptDestinationDirectory();
75
+ }
76
+ } else {
77
+ if (originalPath) {
78
+ console.log(chalk.yellow(` Original path no longer exists: ${originalPath}`));
79
+ }
80
+ destDir = await promptDestinationDirectory();
81
+ }
82
+
83
+ // Step 5: Confirm
84
+ const confirmed = await confirmAction(
85
+ `Restore ${mapping.files?.length || 0} files to ${destDir}?`
86
+ );
87
+
88
+ if (!confirmed) {
89
+ showInfo('Operation cancelled.');
90
+ return;
91
+ }
92
+
93
+ // Step 6: Get all files from cloaked directory
94
+ const files = getAllFiles(cloakedDir);
95
+
96
+ if (files.length === 0) {
97
+ showWarning('No files found in the cloaked directory.');
98
+ return;
99
+ }
100
+
101
+ // Step 7: Create destination if needed
102
+ if (!existsSync(destDir)) {
103
+ mkdirSync(destDir, { recursive: true });
104
+ console.log(chalk.dim(` Created directory: ${destDir}`));
105
+ }
106
+
107
+ // Step 8: Copy and de-anonymize files
108
+ const restoreSpinner = ora('Restoring files...').start();
109
+
110
+ const deanonymizer = createDeanonymizer(getReplacements(mapping));
111
+
112
+ const results = await copyFiles(
113
+ files,
114
+ cloakedDir,
115
+ destDir,
116
+ deanonymizer,
117
+ (current, total, file) => {
118
+ restoreSpinner.text = `Restoring files... ${current}/${total} - ${file}`;
119
+ }
120
+ );
121
+
122
+ restoreSpinner.succeed(`Restored ${results.copied} files`);
123
+
124
+ if (results.transformed > 0) {
125
+ console.log(chalk.cyan(` ๐Ÿ“ ${results.transformed} files had content restored`));
126
+ }
127
+
128
+ if (results.errors.length > 0) {
129
+ console.log(chalk.yellow(` โš ๏ธ ${results.errors.length} files had errors`));
130
+ results.errors.forEach(e => {
131
+ console.log(chalk.dim(` - ${e.file}: ${e.error}`));
132
+ });
133
+ }
134
+
135
+ // Done!
136
+ showSuccess('Restoration complete!');
137
+ console.log(chalk.white(` ๐Ÿ“‚ Files restored to: ${chalk.cyan.bold(destDir)}\n`));
138
+
139
+ } catch (error) {
140
+ showError(`Push failed: ${error.message}`);
141
+ if (process.env.DEBUG) {
142
+ console.error(error);
143
+ }
144
+ }
145
+ }