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.
- package/.claude/commands/shutdown.md +17 -17
- package/.claude/commands/startup.md +13 -13
- package/.editorconfig +21 -0
- package/.github/workflows/publish.yml +34 -0
- package/.prettierrc +8 -0
- package/AI_HANDOFF.md +17 -15
- package/CLAUDE.md +83 -83
- package/CONTRIBUTING.md +84 -0
- package/README.md +243 -243
- package/SECURITY.md +36 -0
- package/bin/ascii-art.js +58 -65
- package/bin/cl +116 -116
- package/bin/cl.js +174 -137
- package/bin/cl.ps1 +36 -36
- package/bin/claude-yolo.js +678 -388
- package/eslint.config.js +25 -0
- package/jest.config.js +9 -0
- package/lib/constants.js +97 -0
- package/package.json +57 -41
- package/postinstall.js +45 -51
- package/preuninstall.js +77 -0
- package/tests/constants.test.js +146 -0
package/bin/claude-yolo.js
CHANGED
|
@@ -1,388 +1,678 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
console.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
//
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
let
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
|
+
});
|