vcode-cli 1.0.0

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,165 @@
1
+ /**
2
+ * V Code — File System Operations.
3
+ * Read, write, create, delete, rename, move files and directories.
4
+ */
5
+
6
+ import {
7
+ readFileSync, writeFileSync, unlinkSync, renameSync, copyFileSync,
8
+ mkdirSync, rmSync, existsSync, readdirSync, statSync, readFile,
9
+ } from 'fs';
10
+ import { join, dirname, basename } from 'path';
11
+ import { requestPermission } from '../permissions.js';
12
+
13
+ /**
14
+ * Read a file's contents.
15
+ */
16
+ export async function readFileOp(filePath) {
17
+ const allowed = await requestPermission('READ_FILE', filePath);
18
+ if (!allowed) return { success: false, error: 'Permission denied' };
19
+
20
+ try {
21
+ const content = readFileSync(filePath, 'utf8');
22
+ return { success: true, content, path: filePath };
23
+ } catch (err) {
24
+ return { success: false, error: err.message };
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Read specific lines from a file.
30
+ */
31
+ export async function readFileLinesOp(filePath, startLine, endLine) {
32
+ const detail = `${filePath} (lines ${startLine}-${endLine})`;
33
+ const allowed = await requestPermission('READ_FILE_LINES', detail);
34
+ if (!allowed) return { success: false, error: 'Permission denied' };
35
+
36
+ try {
37
+ const lines = readFileSync(filePath, 'utf8').split('\n');
38
+ const selected = lines.slice(startLine - 1, endLine);
39
+ return { success: true, content: selected.join('\n'), path: filePath, startLine, endLine };
40
+ } catch (err) {
41
+ return { success: false, error: err.message };
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Write content to a file (create or overwrite).
47
+ */
48
+ export async function writeFileOp(filePath, content) {
49
+ const action = existsSync(filePath) ? 'WRITE_FILE' : 'CREATE_FILE';
50
+ const allowed = await requestPermission(action, filePath);
51
+ if (!allowed) return { success: false, error: 'Permission denied' };
52
+
53
+ try {
54
+ mkdirSync(dirname(filePath), { recursive: true });
55
+ writeFileSync(filePath, content, 'utf8');
56
+ return { success: true, path: filePath };
57
+ } catch (err) {
58
+ return { success: false, error: err.message };
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Delete a file.
64
+ */
65
+ export async function deleteFileOp(filePath) {
66
+ const allowed = await requestPermission('DELETE_FILE', filePath);
67
+ if (!allowed) return { success: false, error: 'Permission denied' };
68
+
69
+ try {
70
+ unlinkSync(filePath);
71
+ return { success: true, path: filePath };
72
+ } catch (err) {
73
+ return { success: false, error: err.message };
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Rename / move a file.
79
+ */
80
+ export async function renameFileOp(oldPath, newPath) {
81
+ const detail = `${oldPath} → ${newPath}`;
82
+ const allowed = await requestPermission('RENAME_FILE', detail);
83
+ if (!allowed) return { success: false, error: 'Permission denied' };
84
+
85
+ try {
86
+ mkdirSync(dirname(newPath), { recursive: true });
87
+ renameSync(oldPath, newPath);
88
+ return { success: true, oldPath, newPath };
89
+ } catch (err) {
90
+ return { success: false, error: err.message };
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Move a file (alias for rename with cross-device support).
96
+ */
97
+ export async function moveFileOp(oldPath, newPath) {
98
+ const detail = `${oldPath} → ${newPath}`;
99
+ const allowed = await requestPermission('MOVE_FILE', detail);
100
+ if (!allowed) return { success: false, error: 'Permission denied' };
101
+
102
+ try {
103
+ mkdirSync(dirname(newPath), { recursive: true });
104
+ try {
105
+ renameSync(oldPath, newPath);
106
+ } catch {
107
+ // Cross-device move: copy then delete
108
+ copyFileSync(oldPath, newPath);
109
+ unlinkSync(oldPath);
110
+ }
111
+ return { success: true, oldPath, newPath };
112
+ } catch (err) {
113
+ return { success: false, error: err.message };
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Create a directory.
119
+ */
120
+ export async function createDirOp(dirPath) {
121
+ const allowed = await requestPermission('CREATE_DIR', dirPath);
122
+ if (!allowed) return { success: false, error: 'Permission denied' };
123
+
124
+ try {
125
+ mkdirSync(dirPath, { recursive: true });
126
+ return { success: true, path: dirPath };
127
+ } catch (err) {
128
+ return { success: false, error: err.message };
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Delete a directory recursively.
134
+ */
135
+ export async function deleteDirOp(dirPath) {
136
+ const allowed = await requestPermission('DELETE_DIR', `${dirPath} (recursive)`);
137
+ if (!allowed) return { success: false, error: 'Permission denied' };
138
+
139
+ try {
140
+ rmSync(dirPath, { recursive: true, force: true });
141
+ return { success: true, path: dirPath };
142
+ } catch (err) {
143
+ return { success: false, error: err.message };
144
+ }
145
+ }
146
+
147
+ /**
148
+ * List directory contents.
149
+ */
150
+ export async function listDirOp(dirPath) {
151
+ const allowed = await requestPermission('LIST_DIR', dirPath);
152
+ if (!allowed) return { success: false, error: 'Permission denied' };
153
+
154
+ try {
155
+ const entries = readdirSync(dirPath, { withFileTypes: true });
156
+ const items = entries.map(e => ({
157
+ name: e.name,
158
+ type: e.isDirectory() ? 'directory' : 'file',
159
+ size: e.isFile() ? statSync(join(dirPath, e.name)).size : undefined,
160
+ }));
161
+ return { success: true, path: dirPath, items };
162
+ } catch (err) {
163
+ return { success: false, error: err.message };
164
+ }
165
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * V Code — Keyboard Control Operations.
3
+ * Type text, press keys, keyboard shortcuts.
4
+ */
5
+
6
+ import { requestPermission } from '../permissions.js';
7
+
8
+ let nativeAvailable = false;
9
+ let keyboard, Key;
10
+
11
+ try {
12
+ const nutjs = await import('@nut-tree/nut-js');
13
+ keyboard = nutjs.keyboard;
14
+ Key = nutjs.Key;
15
+ nativeAvailable = true;
16
+ } catch {
17
+ // Native module not installed
18
+ }
19
+
20
+ function checkNative() {
21
+ if (!nativeAvailable) {
22
+ return { success: false, error: 'Keyboard control requires @nut-tree/nut-js. Install with: npm install -g @nut-tree/nut-js' };
23
+ }
24
+ return null;
25
+ }
26
+
27
+ /**
28
+ * Type text string.
29
+ */
30
+ export async function keyType(text) {
31
+ const err = checkNative();
32
+ if (err) return err;
33
+
34
+ const preview = text.length > 60 ? text.substring(0, 60) + '...' : text;
35
+ const detail = `Type: "${preview}"`;
36
+ const allowed = await requestPermission('KEY_TYPE', detail);
37
+ if (!allowed) return { success: false, error: 'Permission denied' };
38
+
39
+ try {
40
+ await keyboard.type(text);
41
+ return { success: true, text };
42
+ } catch (e) {
43
+ return { success: false, error: e.message };
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Press a key or key combination (e.g. 'ctrl+c', 'enter', 'alt+tab').
49
+ */
50
+ export async function keyPress(keys) {
51
+ const err = checkNative();
52
+ if (err) return err;
53
+
54
+ const detail = `Press: ${keys}`;
55
+ const allowed = await requestPermission('KEY_PRESS', detail);
56
+ if (!allowed) return { success: false, error: 'Permission denied' };
57
+
58
+ try {
59
+ const keyParts = keys.split('+').map(k => k.trim().toLowerCase());
60
+ const mappedKeys = keyParts.map(k => {
61
+ const keyMap = {
62
+ 'ctrl': Key.LeftControl, 'control': Key.LeftControl,
63
+ 'alt': Key.LeftAlt, 'option': Key.LeftAlt,
64
+ 'shift': Key.LeftShift,
65
+ 'super': Key.LeftSuper, 'meta': Key.LeftSuper, 'cmd': Key.LeftSuper, 'command': Key.LeftSuper,
66
+ 'enter': Key.Return, 'return': Key.Return,
67
+ 'tab': Key.Tab, 'escape': Key.Escape, 'esc': Key.Escape,
68
+ 'space': Key.Space, 'backspace': Key.Backspace, 'delete': Key.Delete,
69
+ 'up': Key.Up, 'down': Key.Down, 'left': Key.Left, 'right': Key.Right,
70
+ 'home': Key.Home, 'end': Key.End, 'pageup': Key.PageUp, 'pagedown': Key.PageDown,
71
+ 'f1': Key.F1, 'f2': Key.F2, 'f3': Key.F3, 'f4': Key.F4,
72
+ 'f5': Key.F5, 'f6': Key.F6, 'f7': Key.F7, 'f8': Key.F8,
73
+ 'f9': Key.F9, 'f10': Key.F10, 'f11': Key.F11, 'f12': Key.F12,
74
+ };
75
+ return keyMap[k] || Key[k.charAt(0).toUpperCase() + k.slice(1)] || Key[k.toUpperCase()];
76
+ }).filter(Boolean);
77
+
78
+ if (mappedKeys.length === 0) {
79
+ return { success: false, error: `Unknown key combination: ${keys}` };
80
+ }
81
+
82
+ await keyboard.pressKey(...mappedKeys);
83
+ await keyboard.releaseKey(...mappedKeys);
84
+ return { success: true, keys };
85
+ } catch (e) {
86
+ return { success: false, error: e.message };
87
+ }
88
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * V Code — Mouse Control Operations.
3
+ * Move, click, drag using native bindings.
4
+ * Falls back gracefully if native modules aren't available.
5
+ */
6
+
7
+ import { requestPermission } from '../permissions.js';
8
+
9
+ let nativeAvailable = false;
10
+ let mouse, screen, Point, Button;
11
+
12
+ try {
13
+ const nutjs = await import('@nut-tree/nut-js');
14
+ mouse = nutjs.mouse;
15
+ screen = nutjs.screen;
16
+ Point = nutjs.Point;
17
+ Button = nutjs.Button;
18
+ nativeAvailable = true;
19
+ } catch {
20
+ // Native module not installed — operations will report unavailable
21
+ }
22
+
23
+ function checkNative() {
24
+ if (!nativeAvailable) {
25
+ return { success: false, error: 'Mouse control requires @nut-tree/nut-js. Install with: npm install -g @nut-tree/nut-js' };
26
+ }
27
+ return null;
28
+ }
29
+
30
+ /**
31
+ * Move mouse to coordinates.
32
+ */
33
+ export async function mouseMove(x, y) {
34
+ const err = checkNative();
35
+ if (err) return err;
36
+
37
+ const detail = `Move mouse to (${x}, ${y})`;
38
+ const allowed = await requestPermission('MOUSE_MOVE', detail);
39
+ if (!allowed) return { success: false, error: 'Permission denied' };
40
+
41
+ try {
42
+ await mouse.setPosition(new Point(x, y));
43
+ return { success: true, x, y };
44
+ } catch (e) {
45
+ return { success: false, error: e.message };
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Click at current or specified position.
51
+ */
52
+ export async function mouseClick(button = 'left', x, y) {
53
+ const err = checkNative();
54
+ if (err) return err;
55
+
56
+ const detail = `${button} click` + (x !== undefined ? ` at (${x}, ${y})` : ' at current position');
57
+ const allowed = await requestPermission('MOUSE_CLICK', detail);
58
+ if (!allowed) return { success: false, error: 'Permission denied' };
59
+
60
+ try {
61
+ if (x !== undefined && y !== undefined) {
62
+ await mouse.setPosition(new Point(x, y));
63
+ }
64
+ const btn = button === 'right' ? Button.RIGHT : Button.LEFT;
65
+ await mouse.click(btn);
66
+ return { success: true, button, x, y };
67
+ } catch (e) {
68
+ return { success: false, error: e.message };
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Double click.
74
+ */
75
+ export async function mouseDoubleClick(x, y) {
76
+ const err = checkNative();
77
+ if (err) return err;
78
+
79
+ const detail = `Double click` + (x !== undefined ? ` at (${x}, ${y})` : ' at current position');
80
+ const allowed = await requestPermission('MOUSE_CLICK', detail);
81
+ if (!allowed) return { success: false, error: 'Permission denied' };
82
+
83
+ try {
84
+ if (x !== undefined && y !== undefined) {
85
+ await mouse.setPosition(new Point(x, y));
86
+ }
87
+ await mouse.doubleClick(Button.LEFT);
88
+ return { success: true, x, y };
89
+ } catch (e) {
90
+ return { success: false, error: e.message };
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Drag from one point to another.
96
+ */
97
+ export async function mouseDrag(fromX, fromY, toX, toY) {
98
+ const err = checkNative();
99
+ if (err) return err;
100
+
101
+ const detail = `Drag from (${fromX}, ${fromY}) to (${toX}, ${toY})`;
102
+ const allowed = await requestPermission('MOUSE_DRAG', detail);
103
+ if (!allowed) return { success: false, error: 'Permission denied' };
104
+
105
+ try {
106
+ await mouse.setPosition(new Point(fromX, fromY));
107
+ await mouse.pressButton(Button.LEFT);
108
+ await mouse.setPosition(new Point(toX, toY));
109
+ await mouse.releaseButton(Button.LEFT);
110
+ return { success: true, fromX, fromY, toX, toY };
111
+ } catch (e) {
112
+ return { success: false, error: e.message };
113
+ }
114
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * V Code — Process Management Operations.
3
+ * List and kill processes.
4
+ */
5
+
6
+ import { exec } from 'child_process';
7
+ import { requestPermission } from '../permissions.js';
8
+
9
+ /**
10
+ * List running processes.
11
+ */
12
+ export async function listProcessesOp() {
13
+ const allowed = await requestPermission('LIST_PROCESSES', 'List all running processes');
14
+ if (!allowed) return { success: false, error: 'Permission denied' };
15
+
16
+ const isWin = process.platform === 'win32';
17
+ const cmd = isWin ? 'tasklist /fo csv /nh' : 'ps aux --sort=-%mem | head -50';
18
+
19
+ return new Promise((resolve) => {
20
+ exec(cmd, { maxBuffer: 5 * 1024 * 1024 }, (err, stdout) => {
21
+ if (err) {
22
+ resolve({ success: false, error: err.message });
23
+ return;
24
+ }
25
+
26
+ let processes;
27
+ if (isWin) {
28
+ processes = stdout.trim().split('\n').map(line => {
29
+ const parts = line.replace(/"/g, '').split(',');
30
+ return { name: parts[0], pid: parseInt(parts[1]), mem: parts[4] };
31
+ }).filter(p => !isNaN(p.pid));
32
+ } else {
33
+ const lines = stdout.trim().split('\n');
34
+ const header = lines[0];
35
+ processes = lines.slice(1).map(line => {
36
+ const parts = line.trim().split(/\s+/);
37
+ return {
38
+ user: parts[0],
39
+ pid: parseInt(parts[1]),
40
+ cpu: parseFloat(parts[2]),
41
+ mem: parseFloat(parts[3]),
42
+ command: parts.slice(10).join(' '),
43
+ };
44
+ });
45
+ }
46
+
47
+ resolve({ success: true, processes });
48
+ });
49
+ });
50
+ }
51
+
52
+ /**
53
+ * Kill a process by PID.
54
+ */
55
+ export async function killProcessOp(pid, signal = 'SIGTERM') {
56
+ const detail = `Kill process PID ${pid} with ${signal}`;
57
+ const allowed = await requestPermission('KILL_PROCESS', detail);
58
+ if (!allowed) return { success: false, error: 'Permission denied' };
59
+
60
+ try {
61
+ process.kill(pid, signal);
62
+ return { success: true, pid, signal };
63
+ } catch (err) {
64
+ return { success: false, error: err.message };
65
+ }
66
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * V Code — Screenshot Operations.
3
+ * Capture full screen or specific windows.
4
+ */
5
+
6
+ import { requestPermission } from '../permissions.js';
7
+ import { tmpdir } from 'os';
8
+ import { join } from 'path';
9
+ import { readFileSync, unlinkSync } from 'fs';
10
+
11
+ let screenshotDesktop;
12
+ try {
13
+ screenshotDesktop = (await import('screenshot-desktop')).default;
14
+ } catch {
15
+ // Not available
16
+ }
17
+
18
+ /**
19
+ * Capture a screenshot and return as base64.
20
+ */
21
+ export async function screenshotOp(display = 0) {
22
+ const detail = `Capture screenshot (display ${display})`;
23
+ const allowed = await requestPermission('SCREENSHOT', detail);
24
+ if (!allowed) return { success: false, error: 'Permission denied' };
25
+
26
+ if (!screenshotDesktop) {
27
+ return { success: false, error: 'screenshot-desktop not available. Install with: npm install -g screenshot-desktop' };
28
+ }
29
+
30
+ try {
31
+ const imgBuffer = await screenshotDesktop({ format: 'png', screen: display });
32
+ const base64 = imgBuffer.toString('base64');
33
+ return { success: true, format: 'png', data: base64 };
34
+ } catch (err) {
35
+ return { success: false, error: err.message };
36
+ }
37
+ }
38
+
39
+ /**
40
+ * List available displays.
41
+ */
42
+ export async function listDisplaysOp() {
43
+ const allowed = await requestPermission('SCREENSHOT', 'List available displays');
44
+ if (!allowed) return { success: false, error: 'Permission denied' };
45
+
46
+ if (!screenshotDesktop || !screenshotDesktop.listDisplays) {
47
+ return { success: true, displays: [{ id: 0, name: 'Primary' }] };
48
+ }
49
+
50
+ try {
51
+ const displays = await screenshotDesktop.listDisplays();
52
+ return { success: true, displays };
53
+ } catch (err) {
54
+ return { success: false, error: err.message };
55
+ }
56
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * V Code — Terminal / Shell Operations.
3
+ * Execute shell commands with permission checks.
4
+ */
5
+
6
+ import { exec, spawn } from 'child_process';
7
+ import { requestPermission } from '../permissions.js';
8
+
9
+ /**
10
+ * Execute a shell command and return its output.
11
+ * @param {string} command — the command to run
12
+ * @param {string} [cwd] — working directory
13
+ * @param {number} [timeout] — timeout in ms (default 60000)
14
+ */
15
+ export async function execCommandOp(command, cwd = process.cwd(), timeout = 60000) {
16
+ const detail = `\`${command}\` in ${cwd}`;
17
+ const allowed = await requestPermission('EXEC_COMMAND', detail);
18
+ if (!allowed) return { success: false, error: 'Permission denied' };
19
+
20
+ return new Promise((resolve) => {
21
+ exec(command, { cwd, timeout, maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
22
+ if (error) {
23
+ resolve({
24
+ success: false,
25
+ exitCode: error.code,
26
+ stdout: stdout?.toString() || '',
27
+ stderr: stderr?.toString() || error.message,
28
+ });
29
+ } else {
30
+ resolve({
31
+ success: true,
32
+ exitCode: 0,
33
+ stdout: stdout?.toString() || '',
34
+ stderr: stderr?.toString() || '',
35
+ });
36
+ }
37
+ });
38
+ });
39
+ }
40
+
41
+ /**
42
+ * Spawn a long-running command and stream output.
43
+ * Returns a handle to the running process.
44
+ */
45
+ export async function spawnCommandOp(command, args = [], cwd = process.cwd()) {
46
+ const detail = `\`${command} ${args.join(' ')}\` in ${cwd}`;
47
+ const allowed = await requestPermission('EXEC_COMMAND', detail);
48
+ if (!allowed) return { success: false, error: 'Permission denied' };
49
+
50
+ try {
51
+ const child = spawn(command, args, {
52
+ cwd,
53
+ shell: true,
54
+ stdio: ['pipe', 'pipe', 'pipe'],
55
+ });
56
+
57
+ let stdout = '';
58
+ let stderr = '';
59
+
60
+ child.stdout.on('data', (data) => { stdout += data.toString(); });
61
+ child.stderr.on('data', (data) => { stderr += data.toString(); });
62
+
63
+ return new Promise((resolve) => {
64
+ child.on('close', (code) => {
65
+ resolve({
66
+ success: code === 0,
67
+ exitCode: code,
68
+ stdout,
69
+ stderr,
70
+ });
71
+ });
72
+
73
+ child.on('error', (err) => {
74
+ resolve({
75
+ success: false,
76
+ exitCode: -1,
77
+ stdout,
78
+ stderr: err.message,
79
+ });
80
+ });
81
+ });
82
+ } catch (err) {
83
+ return { success: false, error: err.message };
84
+ }
85
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * V Code — Permission System.
3
+ * Every operation requires explicit user approval.
4
+ * Color-coded by operation severity.
5
+ */
6
+
7
+ import chalk from 'chalk';
8
+ import inquirer from 'inquirer';
9
+ import { logAudit } from './audit.js';
10
+
11
+ /**
12
+ * Operation risk levels and their colors.
13
+ */
14
+ const RISK_COLORS = {
15
+ destructive: chalk.red, // delete, kill, format
16
+ read: chalk.yellow, // read file, screenshot, list
17
+ write: chalk.blue, // create, edit, write
18
+ execute: chalk.magenta, // run command, open URL
19
+ neutral: chalk.white, // clipboard read, status
20
+ };
21
+
22
+ /**
23
+ * Classify an operation type to a risk level.
24
+ */
25
+ function classifyRisk(opType) {
26
+ const op = opType.toUpperCase();
27
+ if (['DELETE_FILE', 'DELETE_DIR', 'KILL_PROCESS', 'MOVE_FILE'].includes(op)) return 'destructive';
28
+ if (['READ_FILE', 'LIST_DIR', 'SCREENSHOT', 'LIST_PROCESSES', 'CLIPBOARD_READ', 'READ_FILE_LINES'].includes(op)) return 'read';
29
+ if (['WRITE_FILE', 'CREATE_FILE', 'CREATE_DIR', 'RENAME_FILE', 'CLIPBOARD_WRITE'].includes(op)) return 'write';
30
+ if (['EXEC_COMMAND', 'OPEN_URL', 'OPEN_FOLDER', 'MOUSE_CLICK', 'MOUSE_MOVE', 'MOUSE_DRAG', 'KEY_TYPE', 'KEY_PRESS'].includes(op)) return 'execute';
31
+ return 'neutral';
32
+ }
33
+
34
+ /**
35
+ * Format the permission prompt for an operation.
36
+ */
37
+ function formatPrompt(opType, detail) {
38
+ const risk = classifyRisk(opType);
39
+ const color = RISK_COLORS[risk];
40
+ const label = {
41
+ destructive: '🔴 DESTRUCTIVE',
42
+ read: '🟡 READ',
43
+ write: '🔵 WRITE',
44
+ execute: '🟣 EXECUTE',
45
+ neutral: '⚪ INFO',
46
+ }[risk];
47
+
48
+ return color(`\n ${label} V Code wants to ${opType.replace(/_/g, ' ').toLowerCase()}\n`) +
49
+ chalk.gray(` Details: `) + chalk.white(detail) + '\n';
50
+ }
51
+
52
+ // Session-wide auto-approve flag
53
+ let sessionApproveAll = false;
54
+
55
+ /**
56
+ * Set session-wide auto-approve.
57
+ */
58
+ export function setSessionApproveAll(val) {
59
+ sessionApproveAll = val;
60
+ }
61
+
62
+ /**
63
+ * Check if session is in auto-approve mode.
64
+ */
65
+ export function isSessionApproveAll() {
66
+ return sessionApproveAll;
67
+ }
68
+
69
+ /**
70
+ * Request permission from the user for an operation.
71
+ * Returns true if approved, false if denied.
72
+ */
73
+ export async function requestPermission(opType, detail) {
74
+ if (sessionApproveAll) {
75
+ logAudit(opType, detail, 'approved');
76
+ const risk = classifyRisk(opType);
77
+ const color = RISK_COLORS[risk];
78
+ console.log(color(` ✓ Auto-approved: ${opType.replace(/_/g, ' ').toLowerCase()} — ${detail}`));
79
+ return true;
80
+ }
81
+
82
+ console.log(formatPrompt(opType, detail));
83
+
84
+ const { choice } = await inquirer.prompt([
85
+ {
86
+ type: 'expand',
87
+ name: 'choice',
88
+ message: 'Allow this operation?',
89
+ default: 'n',
90
+ choices: [
91
+ { key: 'y', name: 'Yes — approve this operation', value: 'yes' },
92
+ { key: 'n', name: 'No — deny this operation', value: 'no' },
93
+ { key: 'a', name: 'Approve all for this session', value: 'all' },
94
+ ],
95
+ },
96
+ ]);
97
+
98
+ if (choice === 'all') {
99
+ sessionApproveAll = true;
100
+ logAudit(opType, detail, 'approved');
101
+ console.log(chalk.yellow(' ⚠ All operations will be auto-approved for this session.\n'));
102
+ return true;
103
+ }
104
+
105
+ if (choice === 'yes') {
106
+ logAudit(opType, detail, 'approved');
107
+ return true;
108
+ }
109
+
110
+ logAudit(opType, detail, 'denied');
111
+ console.log(chalk.red(' ✗ Operation denied.\n'));
112
+ return false;
113
+ }