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/eslint.config.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import js from '@eslint/js';
|
|
2
|
+
import globals from 'globals';
|
|
3
|
+
|
|
4
|
+
export default [
|
|
5
|
+
js.configs.recommended,
|
|
6
|
+
{
|
|
7
|
+
languageOptions: {
|
|
8
|
+
ecmaVersion: 2022,
|
|
9
|
+
sourceType: 'module',
|
|
10
|
+
globals: {
|
|
11
|
+
...globals.node
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
rules: {
|
|
15
|
+
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
|
16
|
+
'no-console': 'off',
|
|
17
|
+
'prefer-const': 'error',
|
|
18
|
+
'no-var': 'error',
|
|
19
|
+
'eqeqeq': ['error', 'always', { null: 'ignore' }]
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
ignores: ['node_modules/**', 'coverage/**']
|
|
24
|
+
}
|
|
25
|
+
];
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
testEnvironment: 'node',
|
|
3
|
+
transform: {},
|
|
4
|
+
moduleFileExtensions: ['js', 'mjs'],
|
|
5
|
+
testMatch: ['**/tests/**/*.test.js'],
|
|
6
|
+
collectCoverageFrom: ['lib/**/*.js', 'bin/**/*.js'],
|
|
7
|
+
coveragePathIgnorePatterns: ['/node_modules/'],
|
|
8
|
+
verbose: true
|
|
9
|
+
};
|
package/lib/constants.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared constants for claude-yolo-extended
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import os from 'os';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
|
|
8
|
+
// ANSI color codes for terminal output
|
|
9
|
+
export const COLORS = {
|
|
10
|
+
RED: '\x1b[31m',
|
|
11
|
+
YELLOW: '\x1b[33m',
|
|
12
|
+
CYAN: '\x1b[36m',
|
|
13
|
+
GREEN: '\x1b[32m',
|
|
14
|
+
ORANGE: '\x1b[38;5;208m',
|
|
15
|
+
RESET: '\x1b[0m',
|
|
16
|
+
BOLD: '\x1b[1m'
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Destructured exports for convenience
|
|
20
|
+
export const { RED, YELLOW, CYAN, GREEN, ORANGE, RESET, BOLD } = COLORS;
|
|
21
|
+
|
|
22
|
+
// Valid mode values
|
|
23
|
+
export const VALID_MODES = ['yolo', 'safe'];
|
|
24
|
+
|
|
25
|
+
// Path to persistent state file
|
|
26
|
+
export const STATE_FILE = path.join(os.homedir(), '.claude_yolo_state');
|
|
27
|
+
|
|
28
|
+
// Path to last update check timestamp file
|
|
29
|
+
export const UPDATE_CHECK_FILE = path.join(os.homedir(), '.claude_yolo_last_update_check');
|
|
30
|
+
|
|
31
|
+
// Update check interval (24 hours in milliseconds)
|
|
32
|
+
export const UPDATE_CHECK_INTERVAL = 24 * 60 * 60 * 1000;
|
|
33
|
+
|
|
34
|
+
// Valid commands for the cl wrapper
|
|
35
|
+
export const VALID_COMMANDS = ['/YON', '/YOFF', '/STATUS', '/HELP', '/H', '/?'];
|
|
36
|
+
|
|
37
|
+
// Dangerous shell characters for Windows command injection prevention
|
|
38
|
+
export const DANGEROUS_CHARS_PATTERN = /[;&|`$><]/;
|
|
39
|
+
|
|
40
|
+
// Timeouts (in milliseconds)
|
|
41
|
+
export const TIMEOUTS = {
|
|
42
|
+
NPM_VIEW: 30000, // 30 seconds for npm view
|
|
43
|
+
NPM_INSTALL: 300000, // 5 minutes for npm install
|
|
44
|
+
NPM_ROOT: 10000, // 10 seconds for npm -g root
|
|
45
|
+
DEFAULT: 120000 // 2 minutes default
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Maximum directory traversal depth (security limit)
|
|
49
|
+
export const MAX_TRAVERSAL_DEPTH = 10;
|
|
50
|
+
|
|
51
|
+
// Error severity levels
|
|
52
|
+
export const ErrorSeverity = {
|
|
53
|
+
FATAL: 'fatal', // Application must exit
|
|
54
|
+
ERROR: 'error', // Operation failed but can continue
|
|
55
|
+
WARNING: 'warning', // Non-critical issue
|
|
56
|
+
DEBUG: 'debug' // Debug info (only shown with DEBUG env)
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Consistent error logging function
|
|
61
|
+
* @param {string} message - Error message
|
|
62
|
+
* @param {string} severity - Error severity level
|
|
63
|
+
* @param {Error} [error] - Optional error object for stack trace
|
|
64
|
+
*/
|
|
65
|
+
export function logError(message, severity = ErrorSeverity.ERROR, error = null) {
|
|
66
|
+
const prefix =
|
|
67
|
+
{
|
|
68
|
+
[ErrorSeverity.FATAL]: `${RED}${BOLD}FATAL:${RESET}`,
|
|
69
|
+
[ErrorSeverity.ERROR]: `${RED}Error:${RESET}`,
|
|
70
|
+
[ErrorSeverity.WARNING]: `${YELLOW}Warning:${RESET}`,
|
|
71
|
+
[ErrorSeverity.DEBUG]: `${CYAN}Debug:${RESET}`
|
|
72
|
+
}[severity] || `${RED}Error:${RESET}`;
|
|
73
|
+
|
|
74
|
+
// Always log to stderr for errors
|
|
75
|
+
if (severity === ErrorSeverity.DEBUG) {
|
|
76
|
+
if (process.env.DEBUG) {
|
|
77
|
+
console.log(`${prefix} ${message}`);
|
|
78
|
+
if (error?.stack) console.log(error.stack);
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
console.error(`${prefix} ${message}`);
|
|
82
|
+
if (process.env.DEBUG && error?.stack) {
|
|
83
|
+
console.error(error.stack);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Handle fatal errors - log and exit
|
|
90
|
+
* @param {string} message - Error message
|
|
91
|
+
* @param {Error} [error] - Optional error object
|
|
92
|
+
* @param {number} [exitCode=1] - Process exit code
|
|
93
|
+
*/
|
|
94
|
+
export function handleFatalError(message, error = null, exitCode = 1) {
|
|
95
|
+
logError(message, ErrorSeverity.FATAL, error);
|
|
96
|
+
process.exit(exitCode);
|
|
97
|
+
}
|
package/package.json
CHANGED
|
@@ -1,41 +1,57 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "claude-yolo-extended",
|
|
3
|
-
"version": "1.9.
|
|
4
|
-
"description": "Claude CLI wrapper with YOLO mode (bypass safety) and SAFE mode support, auto-updates, and colorful loading messages",
|
|
5
|
-
"bin": {
|
|
6
|
-
"claude-yolo-extended": "bin/claude-yolo.js",
|
|
7
|
-
"cl": "bin/cl.js"
|
|
8
|
-
},
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
},
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-yolo-extended",
|
|
3
|
+
"version": "1.9.5",
|
|
4
|
+
"description": "Claude CLI wrapper with YOLO mode (bypass safety) and SAFE mode support, auto-updates, and colorful loading messages",
|
|
5
|
+
"bin": {
|
|
6
|
+
"claude-yolo-extended": "bin/claude-yolo.js",
|
|
7
|
+
"cl": "bin/cl.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"preuninstall": "node preuninstall.js",
|
|
11
|
+
"lint": "eslint bin lib",
|
|
12
|
+
"lint:fix": "eslint bin lib --fix",
|
|
13
|
+
"format": "prettier --write \"bin/**/*.js\" \"lib/**/*.js\"",
|
|
14
|
+
"format:check": "prettier --check \"bin/**/*.js\" \"lib/**/*.js\"",
|
|
15
|
+
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
16
|
+
"test:coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"punycode": "latest",
|
|
20
|
+
"@anthropic-ai/claude-code": "2.0.76"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@eslint/js": "^9.17.0",
|
|
24
|
+
"eslint": "^9.17.0",
|
|
25
|
+
"globals": "^15.14.0",
|
|
26
|
+
"jest": "^29.7.0",
|
|
27
|
+
"prettier": "^3.4.2"
|
|
28
|
+
},
|
|
29
|
+
"type": "module",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/jslitzkerttcu/claude-yolo.git"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"claude",
|
|
36
|
+
"cli",
|
|
37
|
+
"anthropic",
|
|
38
|
+
"wrapper",
|
|
39
|
+
"yolo",
|
|
40
|
+
"safe-mode",
|
|
41
|
+
"permissions"
|
|
42
|
+
],
|
|
43
|
+
"author": "jslitzkerttcu",
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/jslitzkerttcu/claude-yolo/issues"
|
|
47
|
+
},
|
|
48
|
+
"homepage": "https://github.com/jslitzkerttcu/claude-yolo#readme",
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=14.16"
|
|
51
|
+
},
|
|
52
|
+
"os": [
|
|
53
|
+
"win32",
|
|
54
|
+
"darwin",
|
|
55
|
+
"linux"
|
|
56
|
+
]
|
|
57
|
+
}
|
package/postinstall.js
CHANGED
|
@@ -1,52 +1,46 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import readline from 'readline';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
console.log(
|
|
19
|
-
|
|
20
|
-
console.log(`${BOLD}
|
|
21
|
-
console.log(`
|
|
22
|
-
console.log(`
|
|
23
|
-
console.log(`
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
console.log(
|
|
27
|
-
console.log(`
|
|
28
|
-
console.log(`
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
console.log(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
} else {
|
|
47
|
-
console.log(`\n${CYAN}Installation cancelled by user.${RESET}`);
|
|
48
|
-
console.log(`If you want the official Claude CLI with normal safety features, run:`);
|
|
49
|
-
console.log(`npm install -g @anthropic-ai/claude-code`);
|
|
50
|
-
process.exit(1); // Error exit code to abort installation
|
|
51
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import readline from 'readline';
|
|
4
|
+
import { RED, YELLOW, CYAN, GREEN, RESET, BOLD } from './lib/constants.js';
|
|
5
|
+
|
|
6
|
+
// Create readline interface for user input
|
|
7
|
+
const rl = readline.createInterface({
|
|
8
|
+
input: process.stdin,
|
|
9
|
+
output: process.stdout
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
console.log(`\n${BOLD}${YELLOW}🔥 CLAUDE-YOLO INSTALLATION CONSENT REQUIRED 🔥${RESET}\n`);
|
|
13
|
+
console.log(`${CYAN}----------------------------------------${RESET}`);
|
|
14
|
+
console.log(`${BOLD}What is claude-yolo?${RESET}`);
|
|
15
|
+
console.log(`This package creates a wrapper around the official Claude CLI tool that:`);
|
|
16
|
+
console.log(` 1. ${RED}BYPASSES safety checks${RESET} by automatically adding the --dangerously-skip-permissions flag`);
|
|
17
|
+
console.log(` 2. Automatically updates to the latest Claude CLI version`);
|
|
18
|
+
console.log(` 3. Adds colorful YOLO-themed loading messages\n`);
|
|
19
|
+
|
|
20
|
+
console.log(`${BOLD}${RED}⚠️ IMPORTANT SECURITY WARNING ⚠️${RESET}`);
|
|
21
|
+
console.log(`The ${BOLD}--dangerously-skip-permissions${RESET} flag was designed for use in containers`);
|
|
22
|
+
console.log(`and bypasses important safety checks. This includes ignoring file access`);
|
|
23
|
+
console.log(`permissions that protect your system and privacy.\n`);
|
|
24
|
+
|
|
25
|
+
console.log(`${BOLD}By using claude-yolo:${RESET}`);
|
|
26
|
+
console.log(` • You acknowledge these safety checks are being bypassed`);
|
|
27
|
+
console.log(` • You understand this may allow Claude CLI to access sensitive files`);
|
|
28
|
+
console.log(` • You accept full responsibility for any security implications\n`);
|
|
29
|
+
|
|
30
|
+
console.log(`${CYAN}----------------------------------------${RESET}\n`);
|
|
31
|
+
|
|
32
|
+
// Ask for explicit consent
|
|
33
|
+
rl.question(`${YELLOW}Do you consent to installing claude-yolo with these modifications? (yes/no): ${RESET}`, (answer) => {
|
|
34
|
+
const lowerAnswer = answer.toLowerCase().trim();
|
|
35
|
+
|
|
36
|
+
if (lowerAnswer === 'yes' || lowerAnswer === 'y') {
|
|
37
|
+
console.log(`\n${YELLOW}🔥 YOLO MODE INSTALLATION APPROVED 🔥${RESET}`);
|
|
38
|
+
console.log(`Installation will continue. Use 'claude-yolo' instead of 'claude' to run in YOLO mode.`);
|
|
39
|
+
process.exit(0); // Success exit code
|
|
40
|
+
} else {
|
|
41
|
+
console.log(`\n${CYAN}Installation cancelled by user.${RESET}`);
|
|
42
|
+
console.log(`If you want the official Claude CLI with normal safety features, run:`);
|
|
43
|
+
console.log(`npm install -g @anthropic-ai/claude-code`);
|
|
44
|
+
process.exit(1); // Error exit code to abort installation
|
|
45
|
+
}
|
|
52
46
|
});
|
package/preuninstall.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Preuninstall script for claude-yolo-extended
|
|
5
|
+
* Cleans up modified CLI files before package removal
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import { exec } from 'child_process';
|
|
11
|
+
import { promisify } from 'util';
|
|
12
|
+
import { fileURLToPath } from 'url';
|
|
13
|
+
|
|
14
|
+
const execAsync = promisify(exec);
|
|
15
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
|
|
17
|
+
console.log('Cleaning up claude-yolo-extended files...');
|
|
18
|
+
|
|
19
|
+
// Files to clean up in each location
|
|
20
|
+
const filesToClean = [
|
|
21
|
+
'cli-yolo.js',
|
|
22
|
+
'cli-yolo.mjs',
|
|
23
|
+
'.claude-yolo-extended-consent'
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
async function cleanup() {
|
|
27
|
+
const locations = [];
|
|
28
|
+
|
|
29
|
+
// Check local installation
|
|
30
|
+
try {
|
|
31
|
+
const localDir = path.join(__dirname, 'node_modules', '@anthropic-ai', 'claude-code');
|
|
32
|
+
if (fs.existsSync(localDir)) {
|
|
33
|
+
locations.push(localDir);
|
|
34
|
+
}
|
|
35
|
+
} catch (e) {
|
|
36
|
+
// Ignore errors
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Check global installation (async)
|
|
40
|
+
try {
|
|
41
|
+
const { stdout } = await execAsync('npm -g root');
|
|
42
|
+
const globalRoot = stdout.trim();
|
|
43
|
+
const globalDir = path.join(globalRoot, '@anthropic-ai', 'claude-code');
|
|
44
|
+
if (fs.existsSync(globalDir)) {
|
|
45
|
+
locations.push(globalDir);
|
|
46
|
+
}
|
|
47
|
+
} catch (e) {
|
|
48
|
+
// Ignore errors
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let totalCleaned = 0;
|
|
52
|
+
|
|
53
|
+
for (const location of locations) {
|
|
54
|
+
for (const file of filesToClean) {
|
|
55
|
+
const filePath = path.join(location, file);
|
|
56
|
+
try {
|
|
57
|
+
if (fs.existsSync(filePath)) {
|
|
58
|
+
fs.unlinkSync(filePath);
|
|
59
|
+
console.log(` Removed: ${filePath}`);
|
|
60
|
+
totalCleaned++;
|
|
61
|
+
}
|
|
62
|
+
} catch (err) {
|
|
63
|
+
console.error(` Could not remove ${filePath}: ${err.message}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (totalCleaned > 0) {
|
|
69
|
+
console.log(`Cleanup complete: removed ${totalCleaned} file(s)`);
|
|
70
|
+
} else {
|
|
71
|
+
console.log('No files needed cleanup');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
cleanup().catch(err => {
|
|
76
|
+
console.error(`Cleanup failed: ${err.message}`);
|
|
77
|
+
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { jest, describe, it, expect, beforeEach, afterEach } from '@jest/globals';
|
|
2
|
+
import {
|
|
3
|
+
COLORS,
|
|
4
|
+
RED, YELLOW, CYAN, GREEN, ORANGE, RESET, BOLD,
|
|
5
|
+
VALID_MODES,
|
|
6
|
+
STATE_FILE,
|
|
7
|
+
UPDATE_CHECK_FILE,
|
|
8
|
+
UPDATE_CHECK_INTERVAL,
|
|
9
|
+
VALID_COMMANDS,
|
|
10
|
+
DANGEROUS_CHARS_PATTERN,
|
|
11
|
+
TIMEOUTS,
|
|
12
|
+
MAX_TRAVERSAL_DEPTH,
|
|
13
|
+
ErrorSeverity,
|
|
14
|
+
logError,
|
|
15
|
+
handleFatalError
|
|
16
|
+
} from '../lib/constants.js';
|
|
17
|
+
import os from 'os';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
|
|
20
|
+
describe('constants', () => {
|
|
21
|
+
describe('COLORS', () => {
|
|
22
|
+
it('should export all color codes', () => {
|
|
23
|
+
expect(COLORS.RED).toBe('\x1b[31m');
|
|
24
|
+
expect(COLORS.YELLOW).toBe('\x1b[33m');
|
|
25
|
+
expect(COLORS.CYAN).toBe('\x1b[36m');
|
|
26
|
+
expect(COLORS.GREEN).toBe('\x1b[32m');
|
|
27
|
+
expect(COLORS.ORANGE).toBe('\x1b[38;5;208m');
|
|
28
|
+
expect(COLORS.RESET).toBe('\x1b[0m');
|
|
29
|
+
expect(COLORS.BOLD).toBe('\x1b[1m');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should export destructured color constants', () => {
|
|
33
|
+
expect(RED).toBe(COLORS.RED);
|
|
34
|
+
expect(YELLOW).toBe(COLORS.YELLOW);
|
|
35
|
+
expect(CYAN).toBe(COLORS.CYAN);
|
|
36
|
+
expect(GREEN).toBe(COLORS.GREEN);
|
|
37
|
+
expect(ORANGE).toBe(COLORS.ORANGE);
|
|
38
|
+
expect(RESET).toBe(COLORS.RESET);
|
|
39
|
+
expect(BOLD).toBe(COLORS.BOLD);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('VALID_MODES', () => {
|
|
44
|
+
it('should contain yolo and safe modes', () => {
|
|
45
|
+
expect(VALID_MODES).toContain('yolo');
|
|
46
|
+
expect(VALID_MODES).toContain('safe');
|
|
47
|
+
expect(VALID_MODES).toHaveLength(2);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('STATE_FILE', () => {
|
|
52
|
+
it('should be in home directory', () => {
|
|
53
|
+
expect(STATE_FILE).toBe(path.join(os.homedir(), '.claude_yolo_state'));
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('UPDATE_CHECK_FILE', () => {
|
|
58
|
+
it('should be in home directory', () => {
|
|
59
|
+
expect(UPDATE_CHECK_FILE).toBe(path.join(os.homedir(), '.claude_yolo_last_update_check'));
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('UPDATE_CHECK_INTERVAL', () => {
|
|
64
|
+
it('should be 24 hours in milliseconds', () => {
|
|
65
|
+
expect(UPDATE_CHECK_INTERVAL).toBe(24 * 60 * 60 * 1000);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('VALID_COMMANDS', () => {
|
|
70
|
+
it('should contain all valid cl commands', () => {
|
|
71
|
+
expect(VALID_COMMANDS).toContain('/YON');
|
|
72
|
+
expect(VALID_COMMANDS).toContain('/YOFF');
|
|
73
|
+
expect(VALID_COMMANDS).toContain('/STATUS');
|
|
74
|
+
expect(VALID_COMMANDS).toContain('/HELP');
|
|
75
|
+
expect(VALID_COMMANDS).toContain('/H');
|
|
76
|
+
expect(VALID_COMMANDS).toContain('/?');
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('DANGEROUS_CHARS_PATTERN', () => {
|
|
81
|
+
it('should match dangerous shell characters', () => {
|
|
82
|
+
expect(DANGEROUS_CHARS_PATTERN.test(';')).toBe(true);
|
|
83
|
+
expect(DANGEROUS_CHARS_PATTERN.test('&')).toBe(true);
|
|
84
|
+
expect(DANGEROUS_CHARS_PATTERN.test('|')).toBe(true);
|
|
85
|
+
expect(DANGEROUS_CHARS_PATTERN.test('`')).toBe(true);
|
|
86
|
+
expect(DANGEROUS_CHARS_PATTERN.test('$')).toBe(true);
|
|
87
|
+
expect(DANGEROUS_CHARS_PATTERN.test('>')).toBe(true);
|
|
88
|
+
expect(DANGEROUS_CHARS_PATTERN.test('<')).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should not match safe characters', () => {
|
|
92
|
+
expect(DANGEROUS_CHARS_PATTERN.test('hello')).toBe(false);
|
|
93
|
+
expect(DANGEROUS_CHARS_PATTERN.test('file.txt')).toBe(false);
|
|
94
|
+
expect(DANGEROUS_CHARS_PATTERN.test('path/to/file')).toBe(false);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('TIMEOUTS', () => {
|
|
99
|
+
it('should have reasonable timeout values', () => {
|
|
100
|
+
expect(TIMEOUTS.NPM_VIEW).toBe(30000);
|
|
101
|
+
expect(TIMEOUTS.NPM_INSTALL).toBe(300000);
|
|
102
|
+
expect(TIMEOUTS.NPM_ROOT).toBe(10000);
|
|
103
|
+
expect(TIMEOUTS.DEFAULT).toBe(120000);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('MAX_TRAVERSAL_DEPTH', () => {
|
|
108
|
+
it('should be a reasonable depth limit', () => {
|
|
109
|
+
expect(MAX_TRAVERSAL_DEPTH).toBe(10);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('ErrorSeverity', () => {
|
|
114
|
+
it('should have all severity levels', () => {
|
|
115
|
+
expect(ErrorSeverity.FATAL).toBe('fatal');
|
|
116
|
+
expect(ErrorSeverity.ERROR).toBe('error');
|
|
117
|
+
expect(ErrorSeverity.WARNING).toBe('warning');
|
|
118
|
+
expect(ErrorSeverity.DEBUG).toBe('debug');
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('logError', () => {
|
|
123
|
+
let consoleSpy;
|
|
124
|
+
let consoleErrorSpy;
|
|
125
|
+
|
|
126
|
+
beforeEach(() => {
|
|
127
|
+
consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
128
|
+
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
afterEach(() => {
|
|
132
|
+
consoleSpy.mockRestore();
|
|
133
|
+
consoleErrorSpy.mockRestore();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should log errors to stderr', () => {
|
|
137
|
+
logError('test error');
|
|
138
|
+
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should log warnings to stderr', () => {
|
|
142
|
+
logError('test warning', ErrorSeverity.WARNING);
|
|
143
|
+
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
});
|