claude-yolo-extended 1.9.2 → 1.9.5

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.
@@ -1,388 +1,678 @@
1
- #!/usr/bin/env node
2
-
3
-
4
- import fs from 'fs';
5
- import path from 'path';
6
- import { pathToFileURL } from 'url';
7
- import os from 'os';
8
- import { createRequire } from 'module';
9
- import { fileURLToPath } from 'url';
10
- import { execSync } from 'child_process';
11
- import readline from 'readline';
12
- import { showYoloActivated, showSafeActivated, showModeStatus, YOLO_ART, SAFE_ART } from './ascii-art.js';
13
-
14
- // ANSI color codes
15
- const RED = '\x1b[31m';
16
- const YELLOW = '\x1b[33m';
17
- const CYAN = '\x1b[36m';
18
- const GREEN = '\x1b[32m';
19
- const RESET = '\x1b[0m';
20
- const BOLD = '\x1b[1m';
21
-
22
- // Path to persistent state file
23
- const stateFile = path.join(os.homedir(), '.claude_yolo_state');
24
-
25
- // Function to get current mode from state file
26
- function getMode() {
27
- try {
28
- return fs.readFileSync(stateFile, 'utf8').trim();
29
- } catch {
30
- return 'YOLO'; // Default mode
31
- }
32
- }
33
-
34
- // Function to set mode in state file
35
- function setMode(mode) {
36
- fs.writeFileSync(stateFile, mode);
37
- }
38
-
39
- // Debug logging function that only logs if DEBUG env var is set
40
- const debug = (message) => {
41
- if (process.env.DEBUG) {
42
- console.log(message);
43
- }
44
- };
45
-
46
- // Function to ask for user consent
47
- function askForConsent() {
48
- return new Promise((resolve) => {
49
- const rl = readline.createInterface({
50
- input: process.stdin,
51
- output: process.stdout
52
- });
53
-
54
- console.log(`\n${BOLD}${YELLOW}🔥 CLAUDE-YOLO-EXTENDED CONSENT REQUIRED 🔥${RESET}\n`);
55
- console.log(`${CYAN}----------------------------------------${RESET}`);
56
- console.log(`${BOLD}What is claude-yolo-extended?${RESET}`);
57
- console.log(`This package creates a wrapper around the official Claude CLI tool that:`);
58
- console.log(` 1. ${RED}BYPASSES safety checks${RESET} by automatically adding the --dangerously-skip-permissions flag`);
59
- console.log(` 2. Automatically updates to the latest Claude CLI version`);
60
- console.log(` 3. Adds colorful YOLO-themed loading messages`);
61
- console.log(` 4. ${GREEN}NOW SUPPORTS SAFE MODE${RESET} with --safe flag\n`);
62
-
63
- console.log(`${BOLD}${RED}⚠️ IMPORTANT SECURITY WARNING ⚠️${RESET}`);
64
- console.log(`The ${BOLD}--dangerously-skip-permissions${RESET} flag was designed for use in containers`);
65
- console.log(`and bypasses important safety checks. This includes ignoring file access`);
66
- console.log(`permissions that protect your system and privacy.\n`);
67
-
68
- console.log(`${BOLD}By using claude-yolo-extended in YOLO mode:${RESET}`);
69
- console.log(` • You acknowledge these safety checks are being bypassed`);
70
- console.log(` • You understand this may allow Claude CLI to access sensitive files`);
71
- console.log(` • You accept full responsibility for any security implications\n`);
72
-
73
- console.log(`${CYAN}----------------------------------------${RESET}\n`);
74
-
75
- rl.question(`${YELLOW}Do you consent to using claude-yolo-extended with these modifications? (yes/no): ${RESET}`, (answer) => {
76
- rl.close();
77
- const lowerAnswer = answer.toLowerCase().trim();
78
- if (lowerAnswer === 'yes' || lowerAnswer === 'y') {
79
- console.log(`\n${YELLOW}🔥 YOLO MODE APPROVED 🔥${RESET}`);
80
- resolve(true);
81
- } else {
82
- console.log(`\n${CYAN}Aborted. YOLO mode not activated.${RESET}`);
83
- console.log(`If you want the official Claude CLI with normal safety features, run:`);
84
- console.log(`claude`);
85
- resolve(false);
86
- }
87
- });
88
- });
89
- }
90
-
91
- // Get the directory of the current module
92
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
93
- const require = createRequire(import.meta.url);
94
-
95
- // Find node_modules directory by walking up from current file
96
- let nodeModulesDir = path.resolve(__dirname, '..');
97
- while (!fs.existsSync(path.join(nodeModulesDir, 'node_modules')) && nodeModulesDir !== '/') {
98
- nodeModulesDir = path.resolve(nodeModulesDir, '..');
99
- }
100
-
101
- // Path to check package info
102
- const packageJsonPath = path.join(nodeModulesDir, 'package.json');
103
-
104
- // Check for updates to Claude package
105
- async function checkForUpdates() {
106
- try {
107
- debug("Checking for Claude package updates...");
108
-
109
- // Get the latest version available on npm
110
- const latestVersionCmd = "npm view @anthropic-ai/claude-code version";
111
- const latestVersion = execSync(latestVersionCmd).toString().trim();
112
- debug(`Latest Claude version on npm: ${latestVersion}`);
113
-
114
- // Get our current installed version
115
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
116
- const dependencies = packageJson.dependencies || {};
117
- const currentVersion = dependencies['@anthropic-ai/claude-code'];
118
-
119
- debug(`Claude version from package.json: ${currentVersion}`);
120
-
121
- // Get the global Claude version if available
122
- let globalVersion;
123
- if (globalClaudeDir) {
124
- try {
125
- const globalPackageJsonPath = path.join(globalClaudeDir, 'package.json');
126
- if (fs.existsSync(globalPackageJsonPath)) {
127
- const globalPackageJson = JSON.parse(fs.readFileSync(globalPackageJsonPath, 'utf8'));
128
- globalVersion = globalPackageJson.version;
129
- debug(`Global Claude version: ${globalVersion}`);
130
-
131
- // If global version is latest, inform user
132
- if (globalVersion === latestVersion) {
133
- debug(`Global Claude installation is already the latest version`);
134
- } else if (globalVersion && latestVersion) {
135
- debug(`Global Claude installation (${globalVersion}) differs from latest (${latestVersion})`);
136
- }
137
- }
138
- } catch (err) {
139
- debug(`Error getting global Claude version: ${err.message}`);
140
- }
141
- }
142
-
143
- // If using a specific version (not "latest"), and it's out of date, update
144
- if (currentVersion !== "latest" && currentVersion !== latestVersion) {
145
- console.log(`Updating Claude package from ${currentVersion || 'unknown'} to ${latestVersion}...`);
146
-
147
- // Update package.json
148
- packageJson.dependencies['@anthropic-ai/claude-code'] = latestVersion;
149
- fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
150
-
151
- // Run npm install
152
- console.log("Running npm install to update dependencies...");
153
- execSync("npm install", { stdio: 'inherit', cwd: nodeModulesDir });
154
- console.log("Update complete!");
155
- } else if (currentVersion === "latest") {
156
- // If using "latest", just make sure we have the latest version installed
157
- debug("Using 'latest' tag in package.json, running npm install to ensure we have the newest version");
158
- execSync("npm install", { stdio: 'inherit', cwd: nodeModulesDir });
159
- }
160
- } catch (error) {
161
- console.error("Error checking for updates:", error.message);
162
- debug(error.stack);
163
- }
164
- }
165
-
166
- // Try to find global installation of Claude CLI first
167
- let globalClaudeDir;
168
- try {
169
- const globalNodeModules = execSync('npm -g root').toString().trim();
170
- debug(`Global node_modules: ${globalNodeModules}`);
171
- const potentialGlobalDir = path.join(globalNodeModules, '@anthropic-ai', 'claude-code');
172
-
173
- if (fs.existsSync(potentialGlobalDir)) {
174
- globalClaudeDir = potentialGlobalDir;
175
- debug(`Found global Claude installation at: ${globalClaudeDir}`);
176
- }
177
- } catch (error) {
178
- debug(`Error finding global Claude installation: ${error.message}`);
179
- }
180
-
181
- // Path to the local Claude CLI installation
182
- const localClaudeDir = path.join(nodeModulesDir, 'node_modules', '@anthropic-ai', 'claude-code');
183
-
184
- // Prioritize global installation, fall back to local
185
- const claudeDir = globalClaudeDir || localClaudeDir;
186
- debug(`Using Claude installation from: ${claudeDir}`);
187
- debug(`Using ${claudeDir === globalClaudeDir ? 'GLOBAL' : 'LOCAL'} Claude installation`);
188
-
189
- // Check for both .js and .mjs versions of the CLI
190
- let mjs = path.join(claudeDir, 'cli.mjs');
191
- let js = path.join(claudeDir, 'cli.js');
192
- let originalCliPath;
193
- let yoloCliPath;
194
-
195
- if (fs.existsSync(js)) {
196
- originalCliPath = js;
197
- yoloCliPath = path.join(claudeDir, 'cli-yolo.js');
198
- debug(`Found Claude CLI at ${originalCliPath} (js version)`);
199
- } else if (fs.existsSync(mjs)) {
200
- originalCliPath = mjs;
201
- yoloCliPath = path.join(claudeDir, 'cli-yolo.mjs');
202
- debug(`Found Claude CLI at ${originalCliPath} (mjs version)`);
203
- } else {
204
- console.error(`Error: Claude CLI not found in ${claudeDir}. Make sure @anthropic-ai/claude-code is installed.`);
205
- process.exit(1);
206
- }
207
- const consentFlagPath = path.join(claudeDir, '.claude-yolo-extended-consent');
208
-
209
- // Main function to run the application
210
- async function run() {
211
- // Handle mode commands first
212
- const args = process.argv.slice(2);
213
- if (args[0] === 'mode') {
214
- if (args[1] === 'yolo') {
215
- showYoloActivated();
216
- setMode('YOLO');
217
- console.log(`${YELLOW}✓ YOLO mode activated${RESET}`);
218
- return;
219
- } else if (args[1] === 'safe') {
220
- showSafeActivated();
221
- setMode('SAFE');
222
- console.log(`${CYAN}✓ SAFE mode activated${RESET}`);
223
- return;
224
- } else {
225
- const currentMode = getMode();
226
- showModeStatus(currentMode);
227
- return;
228
- }
229
- }
230
-
231
- // Check for --safe or --no-yolo flags
232
- const safeMode = process.argv.includes('--safe') ||
233
- process.argv.includes('--no-yolo') ||
234
- getMode() === 'SAFE';
235
-
236
- if (safeMode) {
237
- // Remove our flags before passing to original CLI
238
- process.argv = process.argv.filter(arg =>
239
- arg !== '--safe' && arg !== '--no-yolo'
240
- );
241
-
242
- console.log(`${CYAN}[SAFE] Running Claude in SAFE mode${RESET}`);
243
-
244
- // Update if needed
245
- await checkForUpdates();
246
-
247
- // Ensure original CLI exists
248
- if (!fs.existsSync(originalCliPath)) {
249
- console.error(`Error: ${originalCliPath} not found. Make sure @anthropic-ai/claude-code is installed.`);
250
- process.exit(1);
251
- }
252
-
253
- // Run original CLI without modifications
254
- const cliUrl = pathToFileURL(originalCliPath).href;
255
- await import(cliUrl);
256
- return; // Exit early
257
- }
258
-
259
- // YOLO MODE continues below
260
- console.log(`${YELLOW}[YOLO] Running Claude in YOLO mode${RESET}`);
261
-
262
- // Temporarily fake non-root for YOLO mode
263
- if (process.getuid && process.getuid() === 0) {
264
- console.log(`${YELLOW}⚠️ Running as root - applying YOLO bypass...${RESET}`);
265
- // Store original getuid
266
- const originalGetuid = process.getuid;
267
- // Override getuid to return non-root
268
- process.getuid = () => 1000; // Fake regular user ID
269
- // Restore after a delay to allow CLI to start
270
- setTimeout(() => {
271
- process.getuid = originalGetuid;
272
- }, 100);
273
- }
274
-
275
- // Check and update Claude package first
276
- await checkForUpdates();
277
-
278
- if (!fs.existsSync(originalCliPath)) {
279
- console.error(`Error: ${originalCliPath} not found. Make sure @anthropic-ai/claude-code is installed.`);
280
- process.exit(1);
281
- }
282
-
283
- // Check if consent is needed
284
- const consentNeeded = !fs.existsSync(yoloCliPath) || !fs.existsSync(consentFlagPath);
285
-
286
- // If consent is needed and not already given, ask for it
287
- if (consentNeeded) {
288
- const consent = await askForConsent();
289
- if (!consent) {
290
- // User didn't consent, exit
291
- process.exit(1);
292
- }
293
-
294
- // Create a flag file to remember that consent was given
295
- try {
296
- fs.writeFileSync(consentFlagPath, 'consent-given');
297
- debug("Created consent flag file");
298
- } catch (err) {
299
- debug(`Error creating consent flag file: ${err.message}`);
300
- // Continue anyway
301
- }
302
- }
303
-
304
- // Read the original CLI file content
305
- let cliContent = fs.readFileSync(originalCliPath, 'utf8');
306
-
307
- if (claudeDir === localClaudeDir) {
308
- cliContent = cliContent.replace(/"punycode"/g, '"punycode/"');
309
- debug('Replaced all instances of "punycode" with "punycode/"');
310
- }
311
-
312
- // Replace getIsDocker() calls with true
313
- cliContent = cliContent.replace(/[a-zA-Z0-9_]*\.getIsDocker\(\)/g, 'true');
314
- debug("Replaced all instances of *.getIsDocker() with true");
315
-
316
- // Replace hasInternetAccess() calls with false
317
- cliContent = cliContent.replace(/[a-zA-Z0-9_]*\.hasInternetAccess\(\)/g, 'false');
318
- debug("Replaced all instances of *.hasInternetAccess() with false");
319
-
320
- // Replace root check patterns
321
- // Pattern 1: process.getuid() === 0
322
- cliContent = cliContent.replace(/process\.getuid\(\)\s*===\s*0/g, 'false');
323
- debug("Replaced process.getuid() === 0 checks with false");
324
-
325
- // Pattern 2: process.getuid?.() === 0
326
- cliContent = cliContent.replace(/process\.getuid\?\.\(\)\s*===\s*0/g, 'false');
327
- debug("Replaced process.getuid?.() === 0 checks with false");
328
-
329
- // Pattern 3: getuid() === 0 (with any variable)
330
- cliContent = cliContent.replace(/(\w+)\.getuid\(\)\s*===\s*0/g, 'false');
331
- debug("Replaced all getuid() === 0 checks with false");
332
-
333
- // Pattern 4: Replace any EUID checks
334
- cliContent = cliContent.replace(/process\.geteuid\(\)\s*===\s*0/g, 'false');
335
- cliContent = cliContent.replace(/process\.geteuid\?\.\(\)\s*===\s*0/g, 'false');
336
- debug("Replaced geteuid() checks with false");
337
-
338
- // Add warning message
339
- console.log(YOLO_ART);
340
- console.log(`${YELLOW}🔥 YOLO MODE ACTIVATED 🔥${RESET}`);
341
-
342
- // Replace the loading messages array with YOLO versions
343
- const originalArray = '["Accomplishing","Actioning","Actualizing","Baking","Brewing","Calculating","Cerebrating","Churning","Clauding","Coalescing","Cogitating","Computing","Conjuring","Considering","Cooking","Crafting","Creating","Crunching","Deliberating","Determining","Doing","Effecting","Finagling","Forging","Forming","Generating","Hatching","Herding","Honking","Hustling","Ideating","Inferring","Manifesting","Marinating","Moseying","Mulling","Mustering","Musing","Noodling","Percolating","Pondering","Processing","Puttering","Reticulating","Ruminating","Schlepping","Shucking","Simmering","Smooshing","Spinning","Stewing","Synthesizing","Thinking","Transmuting","Vibing","Working"]';
344
- const yoloSuffixes = [
345
- ` ${RED}(safety's off, hold on tight)${RESET}`,
346
- ` ${YELLOW}(all gas, no brakes, lfg)${RESET}`,
347
- ` ${BOLD}\x1b[35m(yolo mode engaged)${RESET}`,
348
- ` ${CYAN}(dangerous mode! I guess you can just do things)${RESET}`
349
- ];
350
-
351
- // Function to add a random YOLO suffix to each word in the array
352
- const addYoloSuffixes = (arrayStr) => {
353
- try {
354
- const array = JSON.parse(arrayStr);
355
- const yoloArray = array.map(word => {
356
- const randomSuffix = yoloSuffixes[Math.floor(Math.random() * yoloSuffixes.length)];
357
- return word + randomSuffix;
358
- });
359
- return JSON.stringify(yoloArray);
360
- } catch (e) {
361
- debug(`Error modifying loading messages array: ${e.message}`);
362
- return arrayStr;
363
- }
364
- };
365
-
366
- cliContent = cliContent.replace(originalArray, addYoloSuffixes(originalArray));
367
- debug("Replaced loading messages with YOLO versions");
368
-
369
- // Write the modified content to a new file, leaving the original untouched
370
- fs.writeFileSync(yoloCliPath, cliContent);
371
- debug(`Created modified CLI at ${yoloCliPath}`);
372
- debug("Modifications complete. The --dangerously-skip-permissions flag should now work everywhere.");
373
-
374
- // Add the --dangerously-skip-permissions flag to the command line arguments
375
- // This will ensure it's passed to the CLI even if the user didn't specify it
376
- process.argv.splice(2, 0, '--dangerously-skip-permissions');
377
- debug("Added --dangerously-skip-permissions flag to command line arguments");
378
-
379
- // Now import the modified CLI
380
- const yoloCliUrl = pathToFileURL(yoloCliPath).href;
381
- await import(yoloCliUrl);
382
- }
383
-
384
- // Run the main function
385
- run().catch(err => {
386
- console.error("Error:", err);
387
- process.exit(1);
388
- });
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import { pathToFileURL } from 'url';
6
+ import { fileURLToPath } from 'url';
7
+ import { exec, spawn } from 'child_process';
8
+ import { promisify } from 'util';
9
+ import readline from 'readline';
10
+
11
+ // Promisified exec for async operations
12
+ const execAsync = promisify(exec);
13
+
14
+ /**
15
+ * Run a command with spawn (for interactive/inherited stdio)
16
+ * @param {string} command - Command to run
17
+ * @param {string[]} args - Command arguments
18
+ * @param {object} options - Spawn options
19
+ * @returns {Promise<number>} Exit code
20
+ */
21
+ function spawnAsync(command, args, options = {}) {
22
+ return new Promise((resolve, reject) => {
23
+ const child = spawn(command, args, { shell: true, ...options });
24
+
25
+ child.on('error', reject);
26
+ child.on('close', (code) => {
27
+ if (code === 0) {
28
+ resolve(code);
29
+ } else {
30
+ reject(new Error(`Command failed with exit code ${code}`));
31
+ }
32
+ });
33
+ });
34
+ }
35
+
36
+ /**
37
+ * Execute command with timeout (async version)
38
+ * @param {string} command - Command to execute
39
+ * @param {number} timeout - Timeout in milliseconds
40
+ * @returns {Promise<string>} Command output
41
+ */
42
+ async function execWithTimeout(command, timeout) {
43
+ const controller = new AbortController();
44
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
45
+
46
+ try {
47
+ const { stdout } = await execAsync(command, { signal: controller.signal });
48
+ return stdout.trim();
49
+ } finally {
50
+ clearTimeout(timeoutId);
51
+ }
52
+ }
53
+ import { showYoloActivated, showSafeActivated, showModeStatus, YOLO_ART } from './ascii-art.js';
54
+ import {
55
+ RED,
56
+ YELLOW,
57
+ CYAN,
58
+ GREEN,
59
+ RESET,
60
+ BOLD,
61
+ STATE_FILE,
62
+ UPDATE_CHECK_FILE,
63
+ UPDATE_CHECK_INTERVAL,
64
+ VALID_MODES,
65
+ TIMEOUTS,
66
+ MAX_TRAVERSAL_DEPTH,
67
+ logError,
68
+ handleFatalError,
69
+ ErrorSeverity
70
+ } from '../lib/constants.js';
71
+
72
+ // Cached mode value to avoid repeated disk reads
73
+ let cachedMode = null;
74
+
75
+ /**
76
+ * Get current mode from state file (with caching)
77
+ * @returns {string} Current mode ('YOLO' or 'SAFE')
78
+ */
79
+ function getMode() {
80
+ if (cachedMode !== null) {
81
+ return cachedMode;
82
+ }
83
+ try {
84
+ cachedMode = fs.readFileSync(STATE_FILE, 'utf8').trim();
85
+ return cachedMode;
86
+ } catch {
87
+ cachedMode = 'YOLO'; // Default mode
88
+ return cachedMode;
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Set mode in state file (updates cache)
94
+ * @param {string} mode - Mode to set ('YOLO' or 'SAFE')
95
+ */
96
+ function setMode(mode) {
97
+ fs.writeFileSync(STATE_FILE, mode);
98
+ cachedMode = mode;
99
+ }
100
+
101
+ /**
102
+ * Debug logging - only logs if DEBUG=true or DEBUG=1
103
+ * @param {string} message - Message to log
104
+ */
105
+ const debug = (message) => {
106
+ const debugVal = process.env.DEBUG;
107
+ if (debugVal === 'true' || debugVal === '1') {
108
+ console.log(message);
109
+ }
110
+ };
111
+
112
+ /**
113
+ * Compare semantic versions
114
+ * @param {string} a - First version string
115
+ * @param {string} b - Second version string
116
+ * @returns {number} -1 if a < b, 0 if equal, 1 if a > b
117
+ */
118
+ function compareVersions(a, b) {
119
+ if (!a || !b) return 0;
120
+ const partsA = a.split('.').map(Number);
121
+ const partsB = b.split('.').map(Number);
122
+ for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
123
+ const numA = partsA[i] || 0;
124
+ const numB = partsB[i] || 0;
125
+ if (numA > numB) return 1;
126
+ if (numA < numB) return -1;
127
+ }
128
+ return 0;
129
+ }
130
+
131
+ /**
132
+ * Ask user for consent to use YOLO mode
133
+ * @returns {Promise<boolean>} True if user consented
134
+ */
135
+ function askForConsent() {
136
+ return new Promise((resolve) => {
137
+ const rl = readline.createInterface({
138
+ input: process.stdin,
139
+ output: process.stdout
140
+ });
141
+
142
+ console.log(`\n${BOLD}${YELLOW}🔥 CLAUDE-YOLO-EXTENDED CONSENT REQUIRED 🔥${RESET}\n`);
143
+ console.log(`${CYAN}----------------------------------------${RESET}`);
144
+ console.log(`${BOLD}What is claude-yolo-extended?${RESET}`);
145
+ console.log(`This package creates a wrapper around the official Claude CLI tool that:`);
146
+ console.log(
147
+ ` 1. ${RED}BYPASSES safety checks${RESET} by automatically adding the --dangerously-skip-permissions flag`
148
+ );
149
+ console.log(` 2. Automatically updates to the latest Claude CLI version`);
150
+ console.log(` 3. Adds colorful YOLO-themed loading messages`);
151
+ console.log(` 4. ${GREEN}NOW SUPPORTS SAFE MODE${RESET} with --safe flag\n`);
152
+
153
+ console.log(`${BOLD}${RED}⚠️ IMPORTANT SECURITY WARNING ⚠️${RESET}`);
154
+ console.log(
155
+ `The ${BOLD}--dangerously-skip-permissions${RESET} flag was designed for use in containers`
156
+ );
157
+ console.log(`and bypasses important safety checks. This includes ignoring file access`);
158
+ console.log(`permissions that protect your system and privacy.\n`);
159
+
160
+ console.log(`${BOLD}By using claude-yolo-extended in YOLO mode:${RESET}`);
161
+ console.log(` • You acknowledge these safety checks are being bypassed`);
162
+ console.log(` • You understand this may allow Claude CLI to access sensitive files`);
163
+ console.log(` • You accept full responsibility for any security implications\n`);
164
+
165
+ console.log(`${CYAN}----------------------------------------${RESET}\n`);
166
+
167
+ rl.question(
168
+ `${YELLOW}Do you consent to using claude-yolo-extended with these modifications? (yes/no): ${RESET}`,
169
+ (answer) => {
170
+ rl.close();
171
+ const lowerAnswer = answer.toLowerCase().trim();
172
+ if (lowerAnswer === 'yes' || lowerAnswer === 'y') {
173
+ console.log(`\n${YELLOW}🔥 YOLO MODE APPROVED 🔥${RESET}`);
174
+ resolve(true);
175
+ } else {
176
+ console.log(`\n${CYAN}Aborted. YOLO mode not activated.${RESET}`);
177
+ console.log(`If you want the official Claude CLI with normal safety features, run:`);
178
+ console.log(`claude`);
179
+ resolve(false);
180
+ }
181
+ }
182
+ );
183
+ });
184
+ }
185
+
186
+ // Get the directory of the current module
187
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
188
+
189
+ // Find node_modules directory by walking up from current file
190
+ // Cross-platform root detection (works on both Unix '/' and Windows 'C:\')
191
+ const isAtRoot = (dir) => path.parse(dir).root === dir;
192
+
193
+ let nodeModulesDir = path.resolve(__dirname, '..');
194
+ let traversalDepth = 0;
195
+ while (
196
+ !fs.existsSync(path.join(nodeModulesDir, 'node_modules')) &&
197
+ !isAtRoot(nodeModulesDir) &&
198
+ traversalDepth < MAX_TRAVERSAL_DEPTH
199
+ ) {
200
+ nodeModulesDir = path.resolve(nodeModulesDir, '..');
201
+ traversalDepth++;
202
+ }
203
+
204
+ // Path to check package info
205
+ const packageJsonPath = path.join(nodeModulesDir, 'package.json');
206
+
207
+ /**
208
+ * Check if an update check is needed based on rate limiting
209
+ * @returns {boolean} True if update check should be performed
210
+ */
211
+ function shouldCheckForUpdates() {
212
+ try {
213
+ if (!fs.existsSync(UPDATE_CHECK_FILE)) {
214
+ return true;
215
+ }
216
+ const lastCheck = parseInt(fs.readFileSync(UPDATE_CHECK_FILE, 'utf8').trim(), 10);
217
+ const timeSinceLastCheck = Date.now() - lastCheck;
218
+ return timeSinceLastCheck >= UPDATE_CHECK_INTERVAL;
219
+ } catch {
220
+ return true; // Check if file is corrupted or unreadable
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Update the last check timestamp
226
+ */
227
+ function updateLastCheckTimestamp() {
228
+ try {
229
+ fs.writeFileSync(UPDATE_CHECK_FILE, Date.now().toString());
230
+ } catch (err) {
231
+ debug(`Could not update last check timestamp: ${err.message}`);
232
+ }
233
+ }
234
+
235
+ // Check for updates to Claude package (with rate limiting)
236
+ async function checkForUpdates() {
237
+ // Rate limiting: only check once per 24 hours
238
+ if (!shouldCheckForUpdates()) {
239
+ debug('Skipping update check (checked within last 24 hours)');
240
+ return;
241
+ }
242
+
243
+ try {
244
+ debug('Checking for Claude package updates...');
245
+
246
+ // Get the latest version available on npm (async with timeout)
247
+ const latestVersionCmd = 'npm view @anthropic-ai/claude-code version';
248
+ const latestVersion = await execWithTimeout(latestVersionCmd, TIMEOUTS.NPM_VIEW);
249
+
250
+ // Update the timestamp after successful version check
251
+ updateLastCheckTimestamp();
252
+ debug(`Latest Claude version on npm: ${latestVersion}`);
253
+
254
+ // Get our current installed version
255
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
256
+ const dependencies = packageJson.dependencies || {};
257
+ const currentVersion = dependencies['@anthropic-ai/claude-code'];
258
+
259
+ debug(`Claude version from package.json: ${currentVersion}`);
260
+
261
+ // Get the global Claude version if available
262
+ let globalVersion;
263
+ if (globalClaudeDir) {
264
+ try {
265
+ const globalPackageJsonPath = path.join(globalClaudeDir, 'package.json');
266
+ if (fs.existsSync(globalPackageJsonPath)) {
267
+ const globalPackageJson = JSON.parse(fs.readFileSync(globalPackageJsonPath, 'utf8'));
268
+ globalVersion = globalPackageJson.version;
269
+ debug(`Global Claude version: ${globalVersion}`);
270
+
271
+ // If global version is latest, inform user
272
+ if (globalVersion === latestVersion) {
273
+ debug(`Global Claude installation is already the latest version`);
274
+ } else if (globalVersion && latestVersion) {
275
+ debug(
276
+ `Global Claude installation (${globalVersion}) differs from latest (${latestVersion})`
277
+ );
278
+ }
279
+ }
280
+ } catch (err) {
281
+ debug(`Error getting global Claude version: ${err.message}`);
282
+ }
283
+ }
284
+
285
+ // If using a specific version (not "latest"), and it's out of date, update
286
+ // Use semantic version comparison to correctly handle cases like 2.0.10 > 2.0.9
287
+ if (currentVersion !== 'latest' && compareVersions(currentVersion, latestVersion) < 0) {
288
+ console.log(
289
+ `Updating Claude package from ${currentVersion || 'unknown'} to ${latestVersion}...`
290
+ );
291
+
292
+ // Update package.json
293
+ packageJson.dependencies['@anthropic-ai/claude-code'] = latestVersion;
294
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
295
+
296
+ // Run npm install (async with inherited stdio)
297
+ console.log('Running npm install to update dependencies...');
298
+ await spawnAsync('npm', ['install'], { stdio: 'inherit', cwd: nodeModulesDir });
299
+ console.log('Update complete!');
300
+ } else if (currentVersion === 'latest') {
301
+ // If using "latest", just make sure we have the latest version installed
302
+ debug(
303
+ "Using 'latest' tag in package.json, running npm install to ensure we have the newest version"
304
+ );
305
+ await spawnAsync('npm', ['install'], { stdio: 'inherit', cwd: nodeModulesDir });
306
+ }
307
+ } catch (error) {
308
+ logError(`Failed to check for updates: ${error.message}`, ErrorSeverity.WARNING, error);
309
+ }
310
+ }
311
+
312
+ // Try to find global installation of Claude CLI first (async)
313
+ let globalClaudeDir;
314
+ try {
315
+ const globalNodeModules = await execWithTimeout('npm -g root', TIMEOUTS.NPM_ROOT);
316
+ debug(`Global node_modules: ${globalNodeModules}`);
317
+ const potentialGlobalDir = path.join(globalNodeModules, '@anthropic-ai', 'claude-code');
318
+
319
+ if (fs.existsSync(potentialGlobalDir)) {
320
+ globalClaudeDir = potentialGlobalDir;
321
+ debug(`Found global Claude installation at: ${globalClaudeDir}`);
322
+ }
323
+ } catch (error) {
324
+ logError(
325
+ `Could not find global Claude installation: ${error.message}`,
326
+ ErrorSeverity.DEBUG,
327
+ error
328
+ );
329
+ }
330
+
331
+ // Path to the local Claude CLI installation
332
+ const localClaudeDir = path.join(nodeModulesDir, 'node_modules', '@anthropic-ai', 'claude-code');
333
+
334
+ // Prioritize global installation, fall back to local
335
+ const claudeDir = globalClaudeDir || localClaudeDir;
336
+ debug(`Using Claude installation from: ${claudeDir}`);
337
+ debug(`Using ${claudeDir === globalClaudeDir ? 'GLOBAL' : 'LOCAL'} Claude installation`);
338
+
339
+ // Check for both .js and .mjs versions of the CLI
340
+ const mjsPath = path.join(claudeDir, 'cli.mjs');
341
+ const jsPath = path.join(claudeDir, 'cli.js');
342
+ let originalCliPath;
343
+ let yoloCliPath;
344
+
345
+ if (fs.existsSync(jsPath)) {
346
+ originalCliPath = jsPath;
347
+ yoloCliPath = path.join(claudeDir, 'cli-yolo.js');
348
+ debug(`Found Claude CLI at ${originalCliPath} (js version)`);
349
+ } else if (fs.existsSync(mjsPath)) {
350
+ originalCliPath = mjsPath;
351
+ yoloCliPath = path.join(claudeDir, 'cli-yolo.mjs');
352
+ debug(`Found Claude CLI at ${originalCliPath} (mjs version)`);
353
+ } else {
354
+ handleFatalError(
355
+ `Claude CLI not found in ${claudeDir}. Make sure @anthropic-ai/claude-code is installed.`
356
+ );
357
+ }
358
+ const consentFlagPath = path.join(claudeDir, '.claude-yolo-extended-consent');
359
+
360
+ /**
361
+ * Clean up modified YOLO CLI files
362
+ * Called when switching to SAFE mode or uninstalling
363
+ */
364
+ function cleanupYoloFiles() {
365
+ const filesToClean = [
366
+ path.join(claudeDir, 'cli-yolo.js'),
367
+ path.join(claudeDir, 'cli-yolo.mjs'),
368
+ consentFlagPath
369
+ ];
370
+
371
+ let cleaned = 0;
372
+ for (const file of filesToClean) {
373
+ try {
374
+ if (fs.existsSync(file)) {
375
+ fs.unlinkSync(file);
376
+ debug(`Cleaned up: ${file}`);
377
+ cleaned++;
378
+ }
379
+ } catch (err) {
380
+ logError(`Could not remove ${file}: ${err.message}`, ErrorSeverity.WARNING, err);
381
+ }
382
+ }
383
+
384
+ if (cleaned > 0) {
385
+ console.log(`${CYAN}Cleaned up ${cleaned} YOLO file(s)${RESET}`);
386
+ }
387
+
388
+ return cleaned;
389
+ }
390
+
391
+ /**
392
+ * Handle mode commands (yolo/safe/status)
393
+ * @param {string[]} args - Command line arguments
394
+ * @returns {boolean} True if a mode command was handled, false otherwise
395
+ */
396
+ function handleModeCommands(args) {
397
+ if (args[0] !== 'mode') {
398
+ return false;
399
+ }
400
+
401
+ const requestedMode = args[1]?.toLowerCase();
402
+
403
+ if (requestedMode === 'yolo') {
404
+ showYoloActivated();
405
+ setMode('YOLO');
406
+ console.log(`${YELLOW}✓ YOLO mode activated${RESET}`);
407
+ return true;
408
+ }
409
+
410
+ if (requestedMode === 'safe') {
411
+ showSafeActivated();
412
+ setMode('SAFE');
413
+ cleanupYoloFiles();
414
+ console.log(`${CYAN}✓ SAFE mode activated${RESET}`);
415
+ return true;
416
+ }
417
+
418
+ if (requestedMode && !VALID_MODES.includes(requestedMode)) {
419
+ logError(`Invalid mode '${args[1]}'`);
420
+ console.log(`Valid modes: ${VALID_MODES.join(', ')}`);
421
+ console.log(`Usage: claude-yolo-extended mode [yolo|safe]`);
422
+ process.exit(1);
423
+ }
424
+
425
+ // No mode specified, show current status
426
+ const currentMode = getMode();
427
+ showModeStatus(currentMode);
428
+ return true;
429
+ }
430
+
431
+ /**
432
+ * Run Claude in SAFE mode (original CLI without modifications)
433
+ */
434
+ async function runSafeMode() {
435
+ // Remove our flags before passing to original CLI
436
+ process.argv = process.argv.filter((arg) => arg !== '--safe' && arg !== '--no-yolo');
437
+
438
+ console.log(`${CYAN}[SAFE] Running Claude in SAFE mode${RESET}`);
439
+
440
+ await checkForUpdates();
441
+
442
+ if (!fs.existsSync(originalCliPath)) {
443
+ handleFatalError(
444
+ `${originalCliPath} not found. Make sure @anthropic-ai/claude-code is installed.`
445
+ );
446
+ }
447
+
448
+ const cliUrl = pathToFileURL(originalCliPath).href;
449
+ await import(cliUrl);
450
+ }
451
+
452
+ /**
453
+ * Ensure user consent for YOLO mode
454
+ * Consent is tracked separately from the modified CLI file
455
+ * @returns {Promise<boolean>} True if consent was given or already exists
456
+ */
457
+ async function ensureConsent() {
458
+ // Only check the consent flag file (not the yoloCliPath)
459
+ // This allows regenerating the CLI without re-asking for consent
460
+ const consentNeeded = !fs.existsSync(consentFlagPath);
461
+
462
+ if (!consentNeeded) {
463
+ return true;
464
+ }
465
+
466
+ const consent = await askForConsent();
467
+ if (!consent) {
468
+ return false;
469
+ }
470
+
471
+ try {
472
+ fs.writeFileSync(consentFlagPath, 'consent-given');
473
+ debug('Created consent flag file');
474
+ } catch (err) {
475
+ logError(`Could not create consent flag file: ${err.message}`, ErrorSeverity.WARNING, err);
476
+ }
477
+
478
+ return true;
479
+ }
480
+
481
+ // Combined regex patterns for YOLO modifications
482
+ // Note: These patterns are specific to method calls and unlikely to match in comments/strings.
483
+ // Full AST parsing would be overkill for this use case.
484
+ const YOLO_REPLACEMENTS = [
485
+ // getIsDocker() -> true (matches identifier.getIsDocker())
486
+ {
487
+ pattern: /\b[a-zA-Z_$][a-zA-Z0-9_$]*\.getIsDocker\(\)/g,
488
+ replacement: 'true',
489
+ desc: 'getIsDocker()'
490
+ },
491
+ // hasInternetAccess() -> false
492
+ {
493
+ pattern: /\b[a-zA-Z_$][a-zA-Z0-9_$]*\.hasInternetAccess\(\)/g,
494
+ replacement: 'false',
495
+ desc: 'hasInternetAccess()'
496
+ },
497
+ // process.getuid() === 0 -> false (various forms, order matters - optional chaining first)
498
+ {
499
+ pattern: /\bprocess\.getuid\?\.\(\)\s*===\s*0/g,
500
+ replacement: 'false',
501
+ desc: 'process.getuid?.() === 0'
502
+ },
503
+ {
504
+ pattern: /\bprocess\.getuid\(\)\s*===\s*0/g,
505
+ replacement: 'false',
506
+ desc: 'process.getuid() === 0'
507
+ },
508
+ {
509
+ pattern: /\b([a-zA-Z_$][a-zA-Z0-9_$]*)\.getuid\(\)\s*===\s*0/g,
510
+ replacement: 'false',
511
+ desc: 'getuid() === 0'
512
+ },
513
+ // process.geteuid() === 0 -> false
514
+ {
515
+ pattern: /\bprocess\.geteuid\?\.\(\)\s*===\s*0/g,
516
+ replacement: 'false',
517
+ desc: 'process.geteuid?.() === 0'
518
+ },
519
+ {
520
+ pattern: /\bprocess\.geteuid\(\)\s*===\s*0/g,
521
+ replacement: 'false',
522
+ desc: 'process.geteuid() === 0'
523
+ }
524
+ ];
525
+
526
+ /**
527
+ * Modify CLI file content for YOLO mode
528
+ * Applies various patches to bypass safety checks
529
+ * @returns {string} Modified CLI content
530
+ */
531
+ function modifyCliFile() {
532
+ let cliContent = fs.readFileSync(originalCliPath, 'utf8');
533
+
534
+ // Fix punycode import (deprecated in Node.js 21+)
535
+ // Apply the fix if the file contains "punycode" without trailing slash
536
+ // This prevents the deprecation warning regardless of installation location
537
+ if (cliContent.includes('"punycode"') && !cliContent.includes('"punycode/"')) {
538
+ cliContent = cliContent.replace(/"punycode"/g, '"punycode/"');
539
+ debug('Fixed punycode import for Node.js compatibility');
540
+ }
541
+
542
+ // Apply all YOLO replacements
543
+ let replacementCount = 0;
544
+ for (const { pattern, replacement } of YOLO_REPLACEMENTS) {
545
+ const matches = cliContent.match(pattern);
546
+ if (matches) {
547
+ replacementCount += matches.length;
548
+ cliContent = cliContent.replace(pattern, replacement);
549
+ }
550
+ }
551
+ debug(`Applied ${replacementCount} YOLO modifications`);
552
+
553
+ return cliContent;
554
+ }
555
+
556
+ /**
557
+ * Add YOLO-themed loading messages
558
+ * @param {string} cliContent - CLI file content
559
+ * @returns {string} Modified content with YOLO messages
560
+ */
561
+ function addYoloLoadingMessages(cliContent) {
562
+ const originalArray =
563
+ '["Accomplishing","Actioning","Actualizing","Baking","Brewing","Calculating","Cerebrating","Churning","Clauding","Coalescing","Cogitating","Computing","Conjuring","Considering","Cooking","Crafting","Creating","Crunching","Deliberating","Determining","Doing","Effecting","Finagling","Forging","Forming","Generating","Hatching","Herding","Honking","Hustling","Ideating","Inferring","Manifesting","Marinating","Moseying","Mulling","Mustering","Musing","Noodling","Percolating","Pondering","Processing","Puttering","Reticulating","Ruminating","Schlepping","Shucking","Simmering","Smooshing","Spinning","Stewing","Synthesizing","Thinking","Transmuting","Vibing","Working"]';
564
+ const yoloSuffixes = [
565
+ ` ${RED}(safety's off, hold on tight)${RESET}`,
566
+ ` ${YELLOW}(all gas, no brakes, lfg)${RESET}`,
567
+ ` ${BOLD}\x1b[35m(yolo mode engaged)${RESET}`,
568
+ ` ${CYAN}(dangerous mode! I guess you can just do things)${RESET}`
569
+ ];
570
+
571
+ const addSuffixes = (arrayStr) => {
572
+ try {
573
+ const array = JSON.parse(arrayStr);
574
+ const yoloArray = array.map((word) => {
575
+ const randomSuffix = yoloSuffixes[Math.floor(Math.random() * yoloSuffixes.length)];
576
+ return word + randomSuffix;
577
+ });
578
+ return JSON.stringify(yoloArray);
579
+ } catch (e) {
580
+ logError(`Could not modify loading messages: ${e.message}`, ErrorSeverity.DEBUG, e);
581
+ return arrayStr;
582
+ }
583
+ };
584
+
585
+ const modified = cliContent.replace(originalArray, addSuffixes(originalArray));
586
+ debug('Replaced loading messages with YOLO versions');
587
+ return modified;
588
+ }
589
+
590
+ /**
591
+ * Run Claude in YOLO mode (with safety bypasses)
592
+ */
593
+ async function runYoloMode() {
594
+ console.log(`${YELLOW}[YOLO] Running Claude in YOLO mode${RESET}`);
595
+
596
+ if (process.getuid && process.getuid() === 0) {
597
+ console.log(
598
+ `${YELLOW}⚠️ Running as root - YOLO bypass will be applied via code modification${RESET}`
599
+ );
600
+ }
601
+
602
+ await checkForUpdates();
603
+
604
+ if (!fs.existsSync(originalCliPath)) {
605
+ handleFatalError(
606
+ `${originalCliPath} not found. Make sure @anthropic-ai/claude-code is installed.`
607
+ );
608
+ }
609
+
610
+ const hasConsent = await ensureConsent();
611
+ if (!hasConsent) {
612
+ process.exit(1);
613
+ }
614
+
615
+ // Modify CLI content
616
+ let cliContent = modifyCliFile();
617
+
618
+ // Add YOLO art and message
619
+ console.log(YOLO_ART);
620
+ console.log(`${YELLOW}🔥 YOLO MODE ACTIVATED 🔥${RESET}`);
621
+
622
+ // Add YOLO loading messages
623
+ cliContent = addYoloLoadingMessages(cliContent);
624
+
625
+ // Write the modified content
626
+ fs.writeFileSync(yoloCliPath, cliContent);
627
+ debug(`Created modified CLI at ${yoloCliPath}`);
628
+
629
+ // Verify the file was written correctly
630
+ try {
631
+ const writtenContent = fs.readFileSync(yoloCliPath, 'utf8');
632
+ if (writtenContent.length !== cliContent.length) {
633
+ handleFatalError('Modified CLI file verification failed: content mismatch');
634
+ }
635
+ debug('File verification passed');
636
+ } catch (err) {
637
+ handleFatalError(`Could not verify modified CLI file: ${err.message}`, err);
638
+ }
639
+
640
+ debug(
641
+ 'Modifications complete. The --dangerously-skip-permissions flag should now work everywhere.'
642
+ );
643
+
644
+ // Add the --dangerously-skip-permissions flag
645
+ process.argv.splice(2, 0, '--dangerously-skip-permissions');
646
+ debug('Added --dangerously-skip-permissions flag to command line arguments');
647
+
648
+ // Import and run the modified CLI
649
+ const yoloCliUrl = pathToFileURL(yoloCliPath).href;
650
+ await import(yoloCliUrl);
651
+ }
652
+
653
+ /**
654
+ * Main application entry point
655
+ */
656
+ async function run() {
657
+ const args = process.argv.slice(2);
658
+
659
+ // Handle mode commands first
660
+ if (handleModeCommands(args)) {
661
+ return;
662
+ }
663
+
664
+ // Check for safe mode flags
665
+ const safeMode =
666
+ process.argv.includes('--safe') || process.argv.includes('--no-yolo') || getMode() === 'SAFE';
667
+
668
+ if (safeMode) {
669
+ await runSafeMode();
670
+ } else {
671
+ await runYoloMode();
672
+ }
673
+ }
674
+
675
+ // Run the main function
676
+ run().catch((err) => {
677
+ handleFatalError(`Unexpected error: ${err.message}`, err);
678
+ });