repo-cloak-cli 1.0.2 → 1.2.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/medium.md +319 -0
- package/package.json +1 -1
- package/src/commands/pull.js +141 -22
package/medium.md
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
# Repo-Cloak: A CLI Tool for Safely Using AI Coding Assistants on Proprietary Codebases
|
|
2
|
+
|
|
3
|
+
The rise of AI-powered coding assistants like GitHub Copilot, Cursor, and Claude has fundamentally changed how developers write software. These tools can dramatically accelerate development, debug complex issues, and even architect entire systems. But for engineers working on proprietary or enterprise codebases, there is a persistent concern: *How do you leverage these powerful AI tools without exposing sensitive business logic, customer data, or confidential intellectual property?*
|
|
4
|
+
|
|
5
|
+
This article introduces **Repo-Cloak**, a command-line tool I built to address this exact challenge. It provides a secure, selective approach to sharing code with AI assistants while maintaining complete control over what information leaves your environment.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## The Problem: AI Assistance vs Enterprise Security
|
|
10
|
+
|
|
11
|
+
Modern AI coding assistants require context to be effective. The more code they can see, the better their suggestions. But enterprise environments present unique challenges:
|
|
12
|
+
|
|
13
|
+
- **Proprietary Business Logic**: Core algorithms, pricing engines, and competitive differentiators must remain confidential
|
|
14
|
+
- **Customer Data References**: Database schemas, API endpoints, and configuration files often contain sensitive identifiers
|
|
15
|
+
- **Company-Specific Naming**: Project names, internal tools, and organizational structures reveal information about your infrastructure
|
|
16
|
+
- **Compliance Requirements**: Healthcare, finance, and government sectors have strict regulations about data handling
|
|
17
|
+
|
|
18
|
+
The traditional approach forces developers to choose between two suboptimal paths: either manually copy-paste sanitized code snippets (losing context and wasting time), or share entire repositories with AI tools (accepting security risks). Repo-Cloak offers a third path.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## The Solution: Selective Extraction with Intelligent Anonymization
|
|
23
|
+
|
|
24
|
+
Repo-Cloak operates on a simple but powerful principle: **extract only what you need, anonymize what you must protect, and maintain a reversible mapping for seamless integration**.
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
28
|
+
│ ORIGINAL REPOSITORY │
|
|
29
|
+
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
|
30
|
+
│ │ XCorpAPI/ │ │ Payments/ │ │ Analytics/ │ │
|
|
31
|
+
│ │ auth.cs │ │ stripe.cs │ │ metrics.cs │ │
|
|
32
|
+
│ │ users.cs │ │ billing.cs │ │ reports.cs │ │
|
|
33
|
+
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
|
34
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
35
|
+
│
|
|
36
|
+
│ PULL (selective + anonymize)
|
|
37
|
+
▼
|
|
38
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
39
|
+
│ CLOAKED WORKSPACE │
|
|
40
|
+
│ ┌──────────────┐ │
|
|
41
|
+
│ │ AcmeAPI/ │ ← Folder names anonymized │
|
|
42
|
+
│ │ auth.cs │ ← Content: "XCorp" → "Acme" │
|
|
43
|
+
│ │ users.cs │ ← Safe to share with AI assistants │
|
|
44
|
+
│ └──────────────┘ │
|
|
45
|
+
│ │
|
|
46
|
+
│ .repo-cloak-map.json ← Encrypted mapping for restoration │
|
|
47
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
48
|
+
│
|
|
49
|
+
│ AI AGENT MAKES MODIFICATIONS
|
|
50
|
+
▼
|
|
51
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
52
|
+
│ PUSH (restore + de-anonymize) │
|
|
53
|
+
│ │
|
|
54
|
+
│ Modified files pushed back to original repository │
|
|
55
|
+
│ All anonymization reversed automatically │
|
|
56
|
+
│ "Acme" → "XCorp" in both content and file paths │
|
|
57
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## How It Works: A Technical Deep Dive
|
|
63
|
+
|
|
64
|
+
### 1. Interactive File Selection
|
|
65
|
+
|
|
66
|
+
Rather than extracting entire directories, Repo-Cloak provides an interactive file selector with hierarchical navigation. Developers can search, filter, and selectively choose exactly which files to extract.
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
? Select files to extract:
|
|
70
|
+
◉ 📁 src/Services
|
|
71
|
+
◉ 📄 BookService.cs
|
|
72
|
+
◯ 📄 PaymentService.cs ← Excluded: contains billing logic
|
|
73
|
+
◉ 📄 UserService.cs
|
|
74
|
+
◯ 📁 src/Infrastructure ← Entire folder excluded
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The selector supports pagination for large codebases, folder-level selection (selecting a folder automatically includes all children), and real-time filtering.
|
|
78
|
+
|
|
79
|
+
### 2. Intelligent Content Anonymization
|
|
80
|
+
|
|
81
|
+
The anonymization engine performs case-preserving replacements across all extracted content. This means your code remains syntactically valid and readable:
|
|
82
|
+
|
|
83
|
+
```csharp
|
|
84
|
+
// Original
|
|
85
|
+
namespace XCorp.Services {
|
|
86
|
+
public class XCorpBookManager : IXCorpService {
|
|
87
|
+
private const string XCORP_API_KEY = "...";
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Anonymized (case preservation maintained)
|
|
92
|
+
namespace Acme.Services {
|
|
93
|
+
public class AcmeBookManager : IAcmeService {
|
|
94
|
+
private const string ACME_API_KEY = "...";
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The engine handles:
|
|
100
|
+
- **PascalCase**: `XCorpService` becomes `AcmeService`
|
|
101
|
+
- **camelCase**: `xcorpClient` becomes `acmeClient`
|
|
102
|
+
- **SCREAMING_CASE**: `XCORP_KEY` becomes `ACME_KEY`
|
|
103
|
+
- **lowercase**: `xcorp` becomes `acme`
|
|
104
|
+
|
|
105
|
+
### 3. Path Anonymization
|
|
106
|
+
|
|
107
|
+
Beyond content, Repo-Cloak also anonymizes folder and file names. This prevents directory structures from revealing organizational patterns:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
Original: src/XCorpFrontEnd/XCorpComponents/XCorpButton.tsx
|
|
111
|
+
Cloaked: src/AcmeFrontEnd/AcmeComponents/AcmeButton.tsx
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 4. Encrypted Mapping with User-Specific Secrets
|
|
115
|
+
|
|
116
|
+
Here is where security becomes critical. The mapping file that tracks original-to-anonymized paths must itself be protected. Repo-Cloak uses AES-256-GCM encryption with a user-specific secret key:
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
120
|
+
│ ENCRYPTION ARCHITECTURE │
|
|
121
|
+
├──────────────────────────────────────────────────────────────┤
|
|
122
|
+
│ │
|
|
123
|
+
│ ~/.repo-cloak/secret.key │
|
|
124
|
+
│ ├── Generated automatically on first use │
|
|
125
|
+
│ ├── 256-bit cryptographically random key │
|
|
126
|
+
│ ├── Stored with 0600 permissions (owner-only) │
|
|
127
|
+
│ └── Unique per user/machine │
|
|
128
|
+
│ │
|
|
129
|
+
│ .repo-cloak-map.json │
|
|
130
|
+
│ ├── Source paths: ENCRYPTED │
|
|
131
|
+
│ ├── Original keywords: ENCRYPTED │
|
|
132
|
+
│ ├── Replacement keywords: VISIBLE (safe values) │
|
|
133
|
+
│ └── Cloaked file paths: VISIBLE (already anonymized) │
|
|
134
|
+
│ │
|
|
135
|
+
└──────────────────────────────────────────────────────────────┘
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The mapping file can be safely committed or shared because:
|
|
139
|
+
- The encrypted fields cannot be decrypted without the user's secret key
|
|
140
|
+
- The visible fields contain only anonymized, non-sensitive values
|
|
141
|
+
- Even if an attacker obtains the mapping, they cannot determine original values
|
|
142
|
+
|
|
143
|
+
If a user loses their secret key (machine reinstall, key deletion), the tool prompts for manual keyword entry during restoration. Since developers know their own codebase, they can provide the original terms when needed.
|
|
144
|
+
|
|
145
|
+
### 5. Incremental Extraction Support
|
|
146
|
+
|
|
147
|
+
Real-world usage rarely involves a single extraction. As AI assistants request additional context, developers need to pull more files. Repo-Cloak handles this with intelligent merging:
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
Pull #1: BookService.cs → Mapping tracks 1 file
|
|
151
|
+
Pull #2: UserService.cs → Mapping tracks 2 files (merged)
|
|
152
|
+
Pull #3: Same files again → Deduplication, no duplicates added
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Each pull is tracked in a history log:
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"pullHistory": [
|
|
159
|
+
{ "timestamp": "2024-01-15T10:30:00Z", "filesAdded": 5, "totalFiles": 5 },
|
|
160
|
+
{ "timestamp": "2024-01-15T14:22:00Z", "filesAdded": 3, "totalFiles": 8 }
|
|
161
|
+
]
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## The Workflow in Practice
|
|
168
|
+
|
|
169
|
+
### Step 1: Pull Files from Your Repository
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
repo-cloak pull
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
The interactive interface guides you through:
|
|
176
|
+
1. Selecting a source directory (your project)
|
|
177
|
+
2. Choosing specific files via the tree selector
|
|
178
|
+
3. Defining keyword replacements (e.g., "XCorp" to "Acme")
|
|
179
|
+
4. Specifying an output directory
|
|
180
|
+
|
|
181
|
+
### Step 2: Work with AI Assistants
|
|
182
|
+
|
|
183
|
+
Open the cloaked workspace in your preferred AI-enabled IDE. The anonymized code is syntactically valid and maintains all structural relationships. AI assistants can:
|
|
184
|
+
- Analyze patterns and suggest improvements
|
|
185
|
+
- Debug issues with full context
|
|
186
|
+
- Generate new code that follows your conventions
|
|
187
|
+
- Refactor existing implementations
|
|
188
|
+
|
|
189
|
+
### Step 3: Push Changes Back
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
repo-cloak push
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
The tool automatically:
|
|
196
|
+
1. Locates the mapping file in the cloaked directory
|
|
197
|
+
2. Decrypts the mapping using your secret key
|
|
198
|
+
3. Reverses all anonymization in both content and file paths
|
|
199
|
+
4. Copies modified files back to the original repository
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Security Considerations
|
|
204
|
+
|
|
205
|
+
### What Gets Protected
|
|
206
|
+
|
|
207
|
+
| Element | Protection Method |
|
|
208
|
+
|---------|------------------|
|
|
209
|
+
| File content keywords | Case-preserving replacement |
|
|
210
|
+
| Folder names | Path anonymization |
|
|
211
|
+
| File names | Path anonymization |
|
|
212
|
+
| Original keywords in mapping | AES-256-GCM encryption |
|
|
213
|
+
| Source directory path | AES-256-GCM encryption |
|
|
214
|
+
| Original file paths | AES-256-GCM encryption |
|
|
215
|
+
|
|
216
|
+
### What Remains Visible
|
|
217
|
+
|
|
218
|
+
| Element | Reason |
|
|
219
|
+
|---------|--------|
|
|
220
|
+
| Replacement keywords | Already anonymized, safe to expose |
|
|
221
|
+
| Cloaked file paths | Already anonymized, safe to expose |
|
|
222
|
+
| Code structure and logic | Required for AI assistance |
|
|
223
|
+
|
|
224
|
+
### Threat Model
|
|
225
|
+
|
|
226
|
+
Repo-Cloak protects against:
|
|
227
|
+
- **Accidental exposure**: AI tools cannot see original company names, project identifiers, or sensitive naming
|
|
228
|
+
- **Mapping file leakage**: Even if the mapping file is exposed, encryption prevents recovery of original values
|
|
229
|
+
- **Third-party logging**: Cloud-based AI services only receive anonymized content
|
|
230
|
+
|
|
231
|
+
Repo-Cloak does not protect against:
|
|
232
|
+
- **Logic inference**: Sufficiently advanced analysis might infer business purpose from code structure
|
|
233
|
+
- **Unique patterns**: Highly distinctive algorithms may be recognizable regardless of naming
|
|
234
|
+
- **Malicious insiders**: Users with the secret key have full access
|
|
235
|
+
|
|
236
|
+
### User Responsibility
|
|
237
|
+
|
|
238
|
+
Repo-Cloak is a tool that assists with anonymization, but it does not replace sound judgment. **The responsibility for selecting appropriate files lies entirely with the user.**
|
|
239
|
+
|
|
240
|
+
Before extracting any code, you should:
|
|
241
|
+
|
|
242
|
+
1. **Review your organization's policies**: Most companies have guidelines about sharing code with external tools or AI services. Ensure you understand and comply with these policies before using Repo-Cloak or any similar tool.
|
|
243
|
+
|
|
244
|
+
2. **Avoid proprietary algorithms**: Even with anonymized naming, core business logic, patented algorithms, or trade secrets should not be extracted. If an algorithm is proprietary, changing variable names does not make it safe to share.
|
|
245
|
+
|
|
246
|
+
3. **Verify file contents before extraction**: The selective file picker exists precisely so you can make informed decisions. Do not blindly select entire directories without understanding what they contain.
|
|
247
|
+
|
|
248
|
+
4. **Cross-check with your team**: When in doubt, consult with your security team, legal department, or engineering leadership. A quick conversation can prevent significant issues.
|
|
249
|
+
|
|
250
|
+
5. **Use the minimum necessary context**: Extract only what the AI assistant needs to help you. More files means more exposure, even if that exposure is anonymized.
|
|
251
|
+
|
|
252
|
+
This tool provides a layer of protection, but no tool can substitute for thoughtful decision-making about what code should or should not leave your environment.
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Installation and Usage
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
# Install globally via npm
|
|
260
|
+
npm install -g repo-cloak-cli
|
|
261
|
+
|
|
262
|
+
# Run the interactive interface
|
|
263
|
+
repo-cloak
|
|
264
|
+
|
|
265
|
+
# Or use specific commands
|
|
266
|
+
repo-cloak pull --source ./my-project --dest ./cloaked-output
|
|
267
|
+
repo-cloak push --source ./cloaked-output --dest ./my-project
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Technical Architecture
|
|
273
|
+
|
|
274
|
+
```
|
|
275
|
+
repo-cloak/
|
|
276
|
+
├── bin/
|
|
277
|
+
│ └── repo-cloak.js # CLI entry point
|
|
278
|
+
├── src/
|
|
279
|
+
│ ├── commands/
|
|
280
|
+
│ │ ├── pull.js # Extraction and anonymization
|
|
281
|
+
│ │ └── push.js # Restoration and de-anonymization
|
|
282
|
+
│ ├── core/
|
|
283
|
+
│ │ ├── anonymizer.js # Case-preserving replacement engine
|
|
284
|
+
│ │ ├── copier.js # File operations with transformation
|
|
285
|
+
│ │ ├── crypto.js # AES-256-GCM encryption
|
|
286
|
+
│ │ ├── mapper.js # Mapping file management
|
|
287
|
+
│ │ └── scanner.js # File discovery and filtering
|
|
288
|
+
│ └── ui/
|
|
289
|
+
│ ├── fileSelector.js # Interactive tree selector
|
|
290
|
+
│ └── prompts.js # User input handling
|
|
291
|
+
└── tests/
|
|
292
|
+
├── anonymizer.test.js # 13 test cases
|
|
293
|
+
├── copier.test.js # 5 test cases
|
|
294
|
+
├── crypto.test.js # 9 test cases
|
|
295
|
+
├── mapper.test.js # 10 test cases
|
|
296
|
+
└── scanner.test.js # 8 test cases
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
The project includes 45 unit tests covering all core functionality, ensuring reliability for production use.
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## Conclusion
|
|
304
|
+
|
|
305
|
+
The tension between leveraging AI tools and maintaining code security is real, but it does not have to be a binary choice. Repo-Cloak provides a practical middle ground: keep your proprietary information private while still benefiting from the productivity gains of modern AI coding assistants.
|
|
306
|
+
|
|
307
|
+
By implementing selective extraction, intelligent anonymization, and encrypted reversible mappings, developers can confidently use tools like Cursor, GitHub Copilot, or any AI coding assistant without exposing sensitive business logic or company-specific information.
|
|
308
|
+
|
|
309
|
+
The tool is open-source and available on npm. Contributions, feedback, and feature requests are welcome.
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
**Repository**: [github.com/iamshz97/repo-cloak](https://github.com/iamshz97/repo-cloak)
|
|
314
|
+
|
|
315
|
+
**npm**: `npm install -g repo-cloak-cli`
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
*Shazni Shiraz is a software engineer focused on developer tooling and enterprise software architecture.*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "repo-cloak-cli",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "🎭 Selectively extract and anonymize files from repositories. Perfect for sharing code with AI agents without exposing proprietary details.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
package/src/commands/pull.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pull Command
|
|
3
3
|
* Extract files and anonymize sensitive information
|
|
4
|
+
* Supports quick-add mode when pulling to existing cloaked directory
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
import ora from 'ora';
|
|
7
8
|
import chalk from 'chalk';
|
|
9
|
+
import inquirer from 'inquirer';
|
|
8
10
|
import { existsSync, mkdirSync } from 'fs';
|
|
9
11
|
import { resolve, relative } from 'path';
|
|
10
12
|
|
|
@@ -13,29 +15,117 @@ import {
|
|
|
13
15
|
promptSourceDirectory,
|
|
14
16
|
promptDestinationDirectory,
|
|
15
17
|
promptKeywordReplacements,
|
|
16
|
-
showSummaryAndConfirm
|
|
18
|
+
showSummaryAndConfirm,
|
|
19
|
+
confirmAction
|
|
17
20
|
} from '../ui/prompts.js';
|
|
18
21
|
import { showSuccess, showError, showInfo } from '../ui/banner.js';
|
|
19
22
|
import { getAllFiles } from '../core/scanner.js';
|
|
20
23
|
import { copyFiles } from '../core/copier.js';
|
|
21
24
|
import { createAnonymizer } from '../core/anonymizer.js';
|
|
22
|
-
import { createMapping, saveMapping, loadRawMapping, mergeMapping, hasMapping } from '../core/mapper.js';
|
|
25
|
+
import { createMapping, saveMapping, loadRawMapping, mergeMapping, hasMapping, decryptMapping } from '../core/mapper.js';
|
|
26
|
+
import { getOrCreateSecret, hasSecret, decryptReplacements } from '../core/crypto.js';
|
|
23
27
|
|
|
24
28
|
export async function pull(options = {}) {
|
|
25
29
|
try {
|
|
26
|
-
// Step 1: Get
|
|
27
|
-
|
|
28
|
-
? resolve(options.
|
|
29
|
-
: await
|
|
30
|
+
// Step 1: Get destination directory first (to check for existing mapping)
|
|
31
|
+
let destDir = options.dest
|
|
32
|
+
? resolve(options.dest)
|
|
33
|
+
: await promptDestinationDirectory();
|
|
34
|
+
|
|
35
|
+
let existingMapping = null;
|
|
36
|
+
let existingReplacements = [];
|
|
37
|
+
let sourceDir = null;
|
|
38
|
+
let isQuickAdd = false;
|
|
39
|
+
|
|
40
|
+
// Step 2: Check for existing mapping in destination
|
|
41
|
+
if (existsSync(destDir) && hasMapping(destDir)) {
|
|
42
|
+
existingMapping = loadRawMapping(destDir);
|
|
43
|
+
|
|
44
|
+
console.log(chalk.cyan('\n Existing cloaked directory detected'));
|
|
45
|
+
console.log(chalk.dim(` Created: ${existingMapping.timestamp}`));
|
|
46
|
+
console.log(chalk.dim(` Files: ${existingMapping.stats?.totalFiles || existingMapping.files?.length || 0}`));
|
|
47
|
+
console.log(chalk.dim(` Replacements: ${existingMapping.replacements?.length || 0}\n`));
|
|
48
|
+
|
|
49
|
+
// Ask if they want quick-add mode
|
|
50
|
+
const { mode } = await inquirer.prompt([
|
|
51
|
+
{
|
|
52
|
+
type: 'list',
|
|
53
|
+
name: 'mode',
|
|
54
|
+
message: 'What would you like to do?',
|
|
55
|
+
choices: [
|
|
56
|
+
{
|
|
57
|
+
name: 'Quick Add - Use existing replacements and add more files',
|
|
58
|
+
value: 'quick'
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'Add More Replacements - Add files with additional anonymization',
|
|
62
|
+
value: 'extend'
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: 'Fresh Start - Choose new destination',
|
|
66
|
+
value: 'fresh'
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
if (mode === 'fresh') {
|
|
73
|
+
// Get a new destination
|
|
74
|
+
destDir = await promptDestinationDirectory();
|
|
75
|
+
existingMapping = null;
|
|
76
|
+
} else {
|
|
77
|
+
isQuickAdd = mode === 'quick';
|
|
78
|
+
|
|
79
|
+
// Decrypt existing replacements
|
|
80
|
+
if (existingMapping.encrypted && hasSecret()) {
|
|
81
|
+
const secret = getOrCreateSecret();
|
|
82
|
+
try {
|
|
83
|
+
const decrypted = decryptReplacements(existingMapping.replacements || [], secret);
|
|
84
|
+
existingReplacements = decrypted.filter(r => !r.decryptFailed);
|
|
85
|
+
|
|
86
|
+
if (existingReplacements.length > 0) {
|
|
87
|
+
console.log(chalk.green(' Existing replacements loaded:\n'));
|
|
88
|
+
existingReplacements.forEach(r => {
|
|
89
|
+
console.log(chalk.dim(` "${r.original}" → "${r.replacement}"`));
|
|
90
|
+
});
|
|
91
|
+
console.log('');
|
|
92
|
+
}
|
|
93
|
+
} catch (err) {
|
|
94
|
+
console.log(chalk.yellow(' Could not decrypt existing replacements'));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Try to get original source path
|
|
99
|
+
if (existingMapping.encrypted && hasSecret()) {
|
|
100
|
+
const secret = getOrCreateSecret();
|
|
101
|
+
try {
|
|
102
|
+
const decrypted = decryptMapping(existingMapping, secret);
|
|
103
|
+
if (decrypted.source?.path && existsSync(decrypted.source.path)) {
|
|
104
|
+
sourceDir = decrypted.source.path;
|
|
105
|
+
console.log(chalk.dim(` Source: ${sourceDir}\n`));
|
|
106
|
+
}
|
|
107
|
+
} catch (err) {
|
|
108
|
+
// Source path couldn't be decrypted, will prompt
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Step 3: Get source directory if not already determined
|
|
115
|
+
if (!sourceDir) {
|
|
116
|
+
sourceDir = options.source
|
|
117
|
+
? resolve(options.source)
|
|
118
|
+
: await promptSourceDirectory();
|
|
119
|
+
}
|
|
30
120
|
|
|
31
121
|
if (!existsSync(sourceDir)) {
|
|
32
122
|
showError(`Source directory does not exist: ${sourceDir}`);
|
|
33
123
|
return;
|
|
34
124
|
}
|
|
35
125
|
|
|
36
|
-
console.log(chalk.dim(
|
|
126
|
+
console.log(chalk.dim(` Source: ${sourceDir}\n`));
|
|
37
127
|
|
|
38
|
-
// Step
|
|
128
|
+
// Step 4: Select files
|
|
39
129
|
const selectedFiles = await selectFiles(sourceDir);
|
|
40
130
|
|
|
41
131
|
if (selectedFiles.length === 0) {
|
|
@@ -45,15 +135,40 @@ export async function pull(options = {}) {
|
|
|
45
135
|
|
|
46
136
|
console.log(chalk.green(`\n✓ Selected ${selectedFiles.length} files\n`));
|
|
47
137
|
|
|
48
|
-
// Step
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
138
|
+
// Step 5: Handle replacements based on mode
|
|
139
|
+
let replacements = [...existingReplacements];
|
|
140
|
+
|
|
141
|
+
if (isQuickAdd) {
|
|
142
|
+
// Quick add mode - just use existing replacements
|
|
143
|
+
if (replacements.length > 0) {
|
|
144
|
+
console.log(chalk.cyan(' Using existing replacements (quick-add mode)\n'));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Ask if they want to add more
|
|
148
|
+
const { addMore } = await inquirer.prompt([
|
|
149
|
+
{
|
|
150
|
+
type: 'confirm',
|
|
151
|
+
name: 'addMore',
|
|
152
|
+
message: 'Add additional replacements?',
|
|
153
|
+
default: false
|
|
154
|
+
}
|
|
155
|
+
]);
|
|
52
156
|
|
|
53
|
-
|
|
54
|
-
|
|
157
|
+
if (addMore) {
|
|
158
|
+
const additionalReplacements = await promptKeywordReplacements();
|
|
159
|
+
replacements = [...replacements, ...additionalReplacements];
|
|
160
|
+
}
|
|
161
|
+
} else if (existingMapping) {
|
|
162
|
+
// Extend mode - prompt for more replacements to add to existing
|
|
163
|
+
console.log(chalk.cyan('\n Add more replacements (existing will be preserved):\n'));
|
|
164
|
+
const additionalReplacements = await promptKeywordReplacements();
|
|
165
|
+
replacements = [...replacements, ...additionalReplacements];
|
|
166
|
+
} else {
|
|
167
|
+
// Fresh start - prompt for all replacements
|
|
168
|
+
replacements = await promptKeywordReplacements();
|
|
169
|
+
}
|
|
55
170
|
|
|
56
|
-
// Step
|
|
171
|
+
// Step 6: Confirm
|
|
57
172
|
const confirmed = await showSummaryAndConfirm(
|
|
58
173
|
selectedFiles.length,
|
|
59
174
|
destDir,
|
|
@@ -65,13 +180,13 @@ export async function pull(options = {}) {
|
|
|
65
180
|
return;
|
|
66
181
|
}
|
|
67
182
|
|
|
68
|
-
// Step
|
|
183
|
+
// Step 7: Create destination directory
|
|
69
184
|
if (!existsSync(destDir)) {
|
|
70
185
|
mkdirSync(destDir, { recursive: true });
|
|
71
186
|
console.log(chalk.dim(` Created directory: ${destDir}`));
|
|
72
187
|
}
|
|
73
188
|
|
|
74
|
-
// Step
|
|
189
|
+
// Step 8: Copy and anonymize files
|
|
75
190
|
const spinner = ora('Copying and anonymizing files...').start();
|
|
76
191
|
|
|
77
192
|
const anonymizer = createAnonymizer(replacements);
|
|
@@ -106,7 +221,7 @@ export async function pull(options = {}) {
|
|
|
106
221
|
});
|
|
107
222
|
}
|
|
108
223
|
|
|
109
|
-
// Step
|
|
224
|
+
// Step 9: Prepare new file mappings
|
|
110
225
|
const newFiles = selectedFiles.map(f => {
|
|
111
226
|
const originalPath = relative(sourceDir, f);
|
|
112
227
|
let anonymizedPath = originalPath;
|
|
@@ -120,8 +235,7 @@ export async function pull(options = {}) {
|
|
|
120
235
|
};
|
|
121
236
|
});
|
|
122
237
|
|
|
123
|
-
// Step
|
|
124
|
-
const existingMapping = loadRawMapping(destDir);
|
|
238
|
+
// Step 10: Check for existing mapping and merge if found
|
|
125
239
|
let mapping;
|
|
126
240
|
let isIncremental = false;
|
|
127
241
|
|
|
@@ -129,7 +243,7 @@ export async function pull(options = {}) {
|
|
|
129
243
|
// Merge with existing
|
|
130
244
|
mapping = mergeMapping(existingMapping, newFiles);
|
|
131
245
|
isIncremental = true;
|
|
132
|
-
console.log(chalk.cyan(` 🔄 Merged with existing mapping
|
|
246
|
+
console.log(chalk.cyan(` 🔄 Merged with existing mapping`));
|
|
133
247
|
} else {
|
|
134
248
|
// Create new mapping
|
|
135
249
|
mapping = createMapping({
|
|
@@ -153,7 +267,12 @@ export async function pull(options = {}) {
|
|
|
153
267
|
// Done!
|
|
154
268
|
showSuccess('Extraction complete!');
|
|
155
269
|
console.log(chalk.white(` 📂 Files extracted to: ${chalk.cyan.bold(destDir)}`));
|
|
156
|
-
|
|
270
|
+
|
|
271
|
+
if (isQuickAdd || isIncremental) {
|
|
272
|
+
console.log(chalk.dim(`\n Tip: Run again to add more files quickly\n`));
|
|
273
|
+
} else {
|
|
274
|
+
console.log(chalk.dim(`\n To restore later, run: ${chalk.white('repo-cloak push')}\n`));
|
|
275
|
+
}
|
|
157
276
|
|
|
158
277
|
} catch (error) {
|
|
159
278
|
showError(`Pull failed: ${error.message}`);
|