claude-yolo-extended 1.9.2 → 1.9.4
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/.github/workflows/publish.yml +34 -0
- package/AI_HANDOFF.md +13 -15
- package/CLAUDE.md +83 -83
- package/README.md +243 -243
- package/bin/ascii-art.js +64 -64
- package/bin/cl +116 -116
- package/bin/cl.js +136 -136
- package/bin/cl.ps1 +36 -36
- package/bin/claude-yolo.js +387 -387
- package/package.json +41 -41
- package/postinstall.js +51 -51
package/bin/claude-yolo.js
CHANGED
|
@@ -1,388 +1,388 @@
|
|
|
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);
|
|
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
388
|
});
|