claude-yolo-extended 1.9.4 → 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.
@@ -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
+ };
@@ -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,15 +1,31 @@
1
1
  {
2
2
  "name": "claude-yolo-extended",
3
- "version": "1.9.4",
3
+ "version": "1.9.5",
4
4
  "description": "Claude CLI wrapper with YOLO mode (bypass safety) and SAFE mode support, auto-updates, and colorful loading messages",
5
5
  "bin": {
6
6
  "claude-yolo-extended": "bin/claude-yolo.js",
7
7
  "cl": "bin/cl.js"
8
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
+ },
9
18
  "dependencies": {
10
19
  "punycode": "latest",
11
20
  "@anthropic-ai/claude-code": "2.0.76"
12
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
+ },
13
29
  "type": "module",
14
30
  "repository": {
15
31
  "type": "git",
package/postinstall.js CHANGED
@@ -1,13 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import readline from 'readline';
4
-
5
- // ANSI color codes
6
- const RED = '\x1b[31m';
7
- const YELLOW = '\x1b[33m';
8
- const CYAN = '\x1b[36m';
9
- const RESET = '\x1b[0m';
10
- const BOLD = '\x1b[1m';
4
+ import { RED, YELLOW, CYAN, GREEN, RESET, BOLD } from './lib/constants.js';
11
5
 
12
6
  // Create readline interface for user input
13
7
  const rl = readline.createInterface({
@@ -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
+ });