navada-edge-cli 3.5.0 → 3.5.2
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/lib/agent.js +136 -1
- package/lib/commands/files.js +164 -0
- package/lib/commands/index.js +1 -1
- package/package.json +1 -1
package/lib/agent.js
CHANGED
|
@@ -21,7 +21,7 @@ const IDENTITY = {
|
|
|
21
21
|
You are professional, technical, concise, and helpful. You speak with authority about distributed systems, Docker, AI, and cloud infrastructure.
|
|
22
22
|
You have FULL ACCESS to the user's computer — you CAN and SHOULD use your tools to execute tasks:
|
|
23
23
|
- shell: run ANY bash, PowerShell, or system command on the user's machine
|
|
24
|
-
- read_file / write_file / list_files: full filesystem
|
|
24
|
+
- read_file / write_file / edit_file / delete_file / list_files: full filesystem CRUD — create, read, edit, delete any file
|
|
25
25
|
- python_exec / python_pip / python_script: run Python code directly
|
|
26
26
|
- sandbox_run: run code with syntax-highlighted output
|
|
27
27
|
- system_info: check CPU, RAM, disk, OS
|
|
@@ -34,6 +34,7 @@ You also connect to the NAVADA Edge Network (4 nodes via Tailscale VPN):
|
|
|
34
34
|
When users ask you to DO something — DO IT. Use write_file to create files. Use shell to run commands. Never say "I can't" when you have a tool for it.
|
|
35
35
|
When asked to generate diagrams — use write_file to create Mermaid (.mmd), SVG, or HTML files. You can also use python_exec with matplotlib/graphviz for complex diagrams.
|
|
36
36
|
When asked to create, edit, or delete files — use the file tools directly. You are a terminal agent with FULL access.
|
|
37
|
+
PLATFORM: This machine runs ` + (process.platform === 'win32' ? `Windows. Use Windows paths (C:\\Users\\...) not Unix paths (~/...). Desktop = ${path.join(os.homedir(), 'Desktop')}. Home = ${os.homedir()}.` : `${process.platform}. Home = ${os.homedir()}.`) + `
|
|
37
38
|
Keep responses short. Code blocks when needed. No fluff.`,
|
|
38
39
|
founder: {
|
|
39
40
|
name: 'Leslie (Lee) Akpareva',
|
|
@@ -224,6 +225,36 @@ const localTools = {
|
|
|
224
225
|
},
|
|
225
226
|
},
|
|
226
227
|
|
|
228
|
+
editFile: {
|
|
229
|
+
description: 'Edit a file by replacing a search string with new content',
|
|
230
|
+
execute: (filePath, search, replace) => {
|
|
231
|
+
try {
|
|
232
|
+
const resolved = path.resolve(filePath);
|
|
233
|
+
const content = fs.readFileSync(resolved, 'utf-8');
|
|
234
|
+
if (!content.includes(search)) return `Error: search string not found in ${resolved}`;
|
|
235
|
+
const updated = content.replace(search, replace);
|
|
236
|
+
fs.writeFileSync(resolved, updated);
|
|
237
|
+
return `Edited: ${resolved} (replaced ${search.length} chars)`;
|
|
238
|
+
} catch (e) { return `Error: ${e.message}`; }
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
deleteFile: {
|
|
243
|
+
description: 'Delete a file or empty directory from this machine',
|
|
244
|
+
execute: (filePath) => {
|
|
245
|
+
try {
|
|
246
|
+
const resolved = path.resolve(filePath);
|
|
247
|
+
const stat = fs.statSync(resolved);
|
|
248
|
+
if (stat.isDirectory()) {
|
|
249
|
+
fs.rmdirSync(resolved);
|
|
250
|
+
} else {
|
|
251
|
+
fs.unlinkSync(resolved);
|
|
252
|
+
}
|
|
253
|
+
return `Deleted: ${resolved}`;
|
|
254
|
+
} catch (e) { return `Error: ${e.message}`; }
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
|
|
227
258
|
systemInfo: {
|
|
228
259
|
description: 'Get system information',
|
|
229
260
|
execute: () => {
|
|
@@ -690,6 +721,8 @@ function openAITools() {
|
|
|
690
721
|
{ name: 'read_file', description: 'Read the contents of a file on the user\'s machine.', parameters: { type: 'object', properties: { path: { type: 'string', description: 'Absolute or relative file path' } }, required: ['path'] } },
|
|
691
722
|
{ name: 'write_file', description: 'Write content to a file. Creates parent directories if needed. Use for creating new files, scripts, configs, diagrams (Mermaid, SVG, HTML), code files.', parameters: { type: 'object', properties: { path: { type: 'string', description: 'File path to write' }, content: { type: 'string', description: 'Full content to write to the file' } }, required: ['path', 'content'] } },
|
|
692
723
|
{ name: 'list_files', description: 'List files and directories.', parameters: { type: 'object', properties: { path: { type: 'string', description: 'Directory path (default: current dir)' } } } },
|
|
724
|
+
{ name: 'edit_file', description: 'Edit a file by finding and replacing text. Use for targeted edits.', parameters: { type: 'object', properties: { path: { type: 'string', description: 'File path' }, search: { type: 'string', description: 'Exact text to find' }, replace: { type: 'string', description: 'Replacement text' } }, required: ['path', 'search', 'replace'] } },
|
|
725
|
+
{ name: 'delete_file', description: 'Delete a file or empty directory.', parameters: { type: 'object', properties: { path: { type: 'string', description: 'Path to delete' } }, required: ['path'] } },
|
|
693
726
|
{ name: 'system_info', description: 'Get local system information (CPU, RAM, disk, OS, hostname).', parameters: { type: 'object', properties: {} } },
|
|
694
727
|
{ name: 'python_exec', description: 'Execute Python code inline. Use for data analysis, calculations, generating content, processing files, ML tasks.', parameters: { type: 'object', properties: { code: { type: 'string', description: 'Python code to execute' } }, required: ['code'] } },
|
|
695
728
|
{ name: 'python_pip', description: 'Install a Python package via pip.', parameters: { type: 'object', properties: { package: { type: 'string', description: 'Package name' } }, required: ['package'] } },
|
|
@@ -871,6 +904,16 @@ async function chat(userMessage, conversationHistory = []) {
|
|
|
871
904
|
description: 'List files and directories.',
|
|
872
905
|
input_schema: { type: 'object', properties: { path: { type: 'string', description: 'Directory path (default: current dir)' } } },
|
|
873
906
|
},
|
|
907
|
+
{
|
|
908
|
+
name: 'edit_file',
|
|
909
|
+
description: 'Edit a file by finding and replacing text. Use for targeted edits without rewriting the whole file.',
|
|
910
|
+
input_schema: { type: 'object', properties: { path: { type: 'string', description: 'File path' }, search: { type: 'string', description: 'Exact text to find' }, replace: { type: 'string', description: 'Text to replace it with' } }, required: ['path', 'search', 'replace'] },
|
|
911
|
+
},
|
|
912
|
+
{
|
|
913
|
+
name: 'delete_file',
|
|
914
|
+
description: 'Delete a file or empty directory from the user\'s machine.',
|
|
915
|
+
input_schema: { type: 'object', properties: { path: { type: 'string', description: 'File or directory path to delete' } }, required: ['path'] },
|
|
916
|
+
},
|
|
874
917
|
{
|
|
875
918
|
name: 'system_info',
|
|
876
919
|
description: 'Get local system information (CPU, RAM, disk, OS, hostname).',
|
|
@@ -995,6 +1038,8 @@ async function executeTool(name, input) {
|
|
|
995
1038
|
case 'read_file': return localTools.readFile.execute(input.path);
|
|
996
1039
|
case 'write_file': return localTools.writeFile.execute(input.path, input.content);
|
|
997
1040
|
case 'list_files': return localTools.listFiles.execute(input.path);
|
|
1041
|
+
case 'edit_file': return localTools.editFile.execute(input.path, input.search, input.replace);
|
|
1042
|
+
case 'delete_file': return localTools.deleteFile.execute(input.path);
|
|
998
1043
|
case 'system_info': return localTools.systemInfo.execute();
|
|
999
1044
|
case 'network_status': return JSON.stringify(await navada.network.ping());
|
|
1000
1045
|
case 'lucas_exec': return JSON.stringify(await navada.lucas.exec(input.command));
|
|
@@ -1027,7 +1072,97 @@ async function executeTool(name, input) {
|
|
|
1027
1072
|
}
|
|
1028
1073
|
}
|
|
1029
1074
|
|
|
1075
|
+
// ---------------------------------------------------------------------------
|
|
1076
|
+
// Local action interceptor — executes file/shell actions WITHOUT needing LLM tool use
|
|
1077
|
+
// This ensures free tier users can still create, read, edit, delete files
|
|
1078
|
+
// ---------------------------------------------------------------------------
|
|
1079
|
+
function tryLocalAction(userMessage) {
|
|
1080
|
+
const msg = userMessage.trim();
|
|
1081
|
+
const lower = msg.toLowerCase();
|
|
1082
|
+
const home = os.homedir();
|
|
1083
|
+
const desktop = path.join(home, 'Desktop');
|
|
1084
|
+
|
|
1085
|
+
// Resolve common path references
|
|
1086
|
+
function resolvePath(p) {
|
|
1087
|
+
return p
|
|
1088
|
+
.replace(/^~\//, home + '/')
|
|
1089
|
+
.replace(/^~\\/, home + '\\')
|
|
1090
|
+
.replace(/\bmy desktop\b/i, desktop)
|
|
1091
|
+
.replace(/\bthe desktop\b/i, desktop)
|
|
1092
|
+
.replace(/\bdesktop\b/i, desktop);
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
// ── Create folder/directory ──
|
|
1096
|
+
const mkdirMatch = lower.match(/(?:create|make|new)\s+(?:a\s+)?(?:new\s+)?(?:folder|directory|dir)\s+(?:called|named|on|at|in)?\s*[""']?(.+?)[""']?\s*(?:on|at|in)\s+(.+)/i)
|
|
1097
|
+
|| lower.match(/(?:create|make|new)\s+(?:a\s+)?(?:new\s+)?(?:folder|directory|dir)\s+(?:on|at|in)\s+(.+?)\s+(?:called|named)\s+[""']?(.+?)[""']?$/i)
|
|
1098
|
+
|| lower.match(/(?:create|make|new)\s+(?:a\s+)?(?:new\s+)?(?:folder|directory|dir)\s+(?:called|named)\s+[""']?(.+?)[""']?\s*(?:on|at|in)\s+(.+)/i)
|
|
1099
|
+
|| lower.match(/(?:create|make|new)\s+(?:a\s+)?(?:new\s+)?(?:folder|directory|dir)\s+(?:called|named)\s+[""']?(.+?)[""']?\s*$/i);
|
|
1100
|
+
|
|
1101
|
+
if (mkdirMatch) {
|
|
1102
|
+
let folderName, location;
|
|
1103
|
+
if (mkdirMatch.length === 3) {
|
|
1104
|
+
folderName = mkdirMatch[1].trim().replace(/[""']/g, '');
|
|
1105
|
+
location = mkdirMatch[2].trim().replace(/[""']/g, '');
|
|
1106
|
+
} else {
|
|
1107
|
+
folderName = mkdirMatch[1].trim().replace(/[""']/g, '');
|
|
1108
|
+
location = lower.includes('desktop') ? desktop : process.cwd();
|
|
1109
|
+
}
|
|
1110
|
+
const resolved = path.resolve(resolvePath(location), folderName);
|
|
1111
|
+
const result = localTools.shell.execute(process.platform === 'win32' ? `mkdir "${resolved}"` : `mkdir -p "${resolved}"`);
|
|
1112
|
+
if (result.includes('Error')) return null; // let LLM handle
|
|
1113
|
+
return `Created folder: ${resolved}`;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// ── Create file ──
|
|
1117
|
+
const touchMatch = lower.match(/(?:create|make|new|touch)\s+(?:a\s+)?(?:new\s+)?(?:file|empty file)\s+(?:called|named)\s+[""']?(.+?)[""']?\s*(?:on|at|in)\s+(.+)/i)
|
|
1118
|
+
|| lower.match(/(?:create|make|new|touch)\s+(?:a\s+)?(?:new\s+)?(?:file)\s+(?:called|named)\s+[""']?(.+?)[""']?\s*$/i);
|
|
1119
|
+
|
|
1120
|
+
if (touchMatch) {
|
|
1121
|
+
let fileName = touchMatch[1].trim().replace(/[""']/g, '');
|
|
1122
|
+
let location = touchMatch[2] ? touchMatch[2].trim().replace(/[""']/g, '') : process.cwd();
|
|
1123
|
+
const resolved = path.resolve(resolvePath(location), fileName);
|
|
1124
|
+
return localTools.writeFile.execute(resolved, '');
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
// ── Read file ──
|
|
1128
|
+
const readMatch = lower.match(/(?:read|show|display|cat|open)\s+(?:the\s+)?(?:file\s+)?[""']?(.+\.\w+)[""']?/i);
|
|
1129
|
+
if (readMatch) {
|
|
1130
|
+
const filePath = resolvePath(readMatch[1].trim().replace(/[""']/g, ''));
|
|
1131
|
+
return localTools.readFile.execute(filePath);
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
// ── Delete file/folder ──
|
|
1135
|
+
const deleteMatch = lower.match(/(?:delete|remove|rm)\s+(?:the\s+)?(?:file|folder|directory)?\s*[""']?(.+?)[""']?\s*$/i);
|
|
1136
|
+
if (deleteMatch && (lower.includes('file') || lower.includes('folder') || lower.includes('directory'))) {
|
|
1137
|
+
const filePath = path.resolve(resolvePath(deleteMatch[1].trim().replace(/[""']/g, '')));
|
|
1138
|
+
return localTools.deleteFile.execute(filePath);
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
// ── List files ──
|
|
1142
|
+
const lsMatch = lower.match(/(?:list|show|ls|dir)\s+(?:the\s+)?(?:files|contents|items)\s+(?:in|on|at|of)\s+(.+)/i);
|
|
1143
|
+
if (lsMatch) {
|
|
1144
|
+
const dir = resolvePath(lsMatch[1].trim().replace(/[""']/g, ''));
|
|
1145
|
+
return localTools.listFiles.execute(dir);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
return null; // No match — let LLM handle
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1030
1151
|
async function grokChat(userMessage, conversationHistory = []) {
|
|
1152
|
+
// Try local action first — enables file ops on free tier
|
|
1153
|
+
const localResult = tryLocalAction(userMessage);
|
|
1154
|
+
if (localResult) {
|
|
1155
|
+
console.log(` ${localResult}`);
|
|
1156
|
+
// Send result to LLM for a natural confirmation
|
|
1157
|
+
const confirmMsg = `The user asked: "${userMessage}"\nI executed the action locally. Result: ${localResult}\nGive a brief, friendly confirmation and ask if they need anything else.`;
|
|
1158
|
+
try {
|
|
1159
|
+
const result = await callFreeTier([{ role: 'user', content: confirmMsg }], true);
|
|
1160
|
+
return result.content || localResult;
|
|
1161
|
+
} catch {
|
|
1162
|
+
return localResult;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1031
1166
|
const messages = [
|
|
1032
1167
|
...conversationHistory.slice(-20).map(m => ({
|
|
1033
1168
|
role: m.role,
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const ui = require('../ui');
|
|
6
|
+
|
|
7
|
+
module.exports = function(reg) {
|
|
8
|
+
|
|
9
|
+
// ── /read <path> ── Read a file
|
|
10
|
+
reg('read', 'Read a file from your machine', (args) => {
|
|
11
|
+
if (!args[0]) { console.log(ui.dim('Usage: /read <file-path>')); return; }
|
|
12
|
+
const filePath = path.resolve(args.join(' '));
|
|
13
|
+
try {
|
|
14
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
15
|
+
const lines = content.split('\n');
|
|
16
|
+
console.log(ui.header(`FILE: ${filePath}`));
|
|
17
|
+
console.log(ui.dim(`${lines.length} lines, ${Buffer.byteLength(content)} bytes`));
|
|
18
|
+
console.log('');
|
|
19
|
+
// Show with line numbers, cap at 200 lines
|
|
20
|
+
const show = lines.slice(0, 200);
|
|
21
|
+
show.forEach((line, i) => console.log(` ${String(i + 1).padStart(4)} ${line}`));
|
|
22
|
+
if (lines.length > 200) console.log(ui.dim(` ... ${lines.length - 200} more lines`));
|
|
23
|
+
} catch (e) {
|
|
24
|
+
console.log(ui.error(e.code === 'ENOENT' ? `File not found: ${filePath}` : e.message));
|
|
25
|
+
}
|
|
26
|
+
}, { category: 'FILES', aliases: ['cat'] });
|
|
27
|
+
|
|
28
|
+
// ── /write <path> <content> ── Write/create a file
|
|
29
|
+
reg('write', 'Write content to a file (creates dirs if needed)', (args) => {
|
|
30
|
+
if (args.length < 2) {
|
|
31
|
+
console.log(ui.dim('Usage: /write <file-path> <content>'));
|
|
32
|
+
console.log(ui.dim(' /write hello.txt Hello World'));
|
|
33
|
+
console.log(ui.dim(' /write src/config.json {"key":"value"}'));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const filePath = path.resolve(args[0]);
|
|
37
|
+
const content = args.slice(1).join(' ');
|
|
38
|
+
try {
|
|
39
|
+
const dir = path.dirname(filePath);
|
|
40
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
41
|
+
fs.writeFileSync(filePath, content);
|
|
42
|
+
console.log(ui.success(`Written: ${filePath} (${Buffer.byteLength(content)} bytes)`));
|
|
43
|
+
} catch (e) {
|
|
44
|
+
console.log(ui.error(e.message));
|
|
45
|
+
}
|
|
46
|
+
}, { category: 'FILES' });
|
|
47
|
+
|
|
48
|
+
// ── /edit <path> <search> -> <replace> ── Find and replace in a file
|
|
49
|
+
reg('edit', 'Edit a file (find and replace)', (args) => {
|
|
50
|
+
const raw = args.join(' ');
|
|
51
|
+
const sepIdx = raw.indexOf(' -> ');
|
|
52
|
+
if (!args[0] || sepIdx === -1) {
|
|
53
|
+
console.log(ui.dim('Usage: /edit <file-path> <search-text> -> <replace-text>'));
|
|
54
|
+
console.log(ui.dim(' /edit config.json "port": 3000 -> "port": 8080'));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
// First arg is the file path, rest is search -> replace
|
|
58
|
+
const filePath = path.resolve(args[0]);
|
|
59
|
+
const afterPath = raw.slice(raw.indexOf(args[0]) + args[0].length + 1);
|
|
60
|
+
const arrowIdx = afterPath.indexOf(' -> ');
|
|
61
|
+
if (arrowIdx === -1) {
|
|
62
|
+
console.log(ui.dim('Use " -> " to separate search and replace text'));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const search = afterPath.slice(0, arrowIdx);
|
|
66
|
+
const replace = afterPath.slice(arrowIdx + 4);
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
70
|
+
if (!content.includes(search)) {
|
|
71
|
+
console.log(ui.error('Search text not found in file'));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const updated = content.replace(search, replace);
|
|
75
|
+
fs.writeFileSync(filePath, updated);
|
|
76
|
+
console.log(ui.success(`Edited: ${filePath}`));
|
|
77
|
+
console.log(ui.dim(` "${search.slice(0, 40)}${search.length > 40 ? '...' : ''}" → "${replace.slice(0, 40)}${replace.length > 40 ? '...' : ''}"`));
|
|
78
|
+
} catch (e) {
|
|
79
|
+
console.log(ui.error(e.code === 'ENOENT' ? `File not found: ${filePath}` : e.message));
|
|
80
|
+
}
|
|
81
|
+
}, { category: 'FILES' });
|
|
82
|
+
|
|
83
|
+
// ── /delete <path> ── Delete a file
|
|
84
|
+
reg('delete', 'Delete a file or empty directory', (args) => {
|
|
85
|
+
if (!args[0]) { console.log(ui.dim('Usage: /delete <file-path>')); return; }
|
|
86
|
+
const filePath = path.resolve(args.join(' '));
|
|
87
|
+
try {
|
|
88
|
+
const stat = fs.statSync(filePath);
|
|
89
|
+
if (stat.isDirectory()) {
|
|
90
|
+
fs.rmdirSync(filePath);
|
|
91
|
+
console.log(ui.success(`Deleted directory: ${filePath}`));
|
|
92
|
+
} else {
|
|
93
|
+
fs.unlinkSync(filePath);
|
|
94
|
+
console.log(ui.success(`Deleted: ${filePath} (${stat.size} bytes)`));
|
|
95
|
+
}
|
|
96
|
+
} catch (e) {
|
|
97
|
+
if (e.code === 'ENOENT') console.log(ui.error(`Not found: ${filePath}`));
|
|
98
|
+
else if (e.code === 'ENOTEMPTY') console.log(ui.error('Directory not empty. Use /run rm -rf <path> for recursive delete.'));
|
|
99
|
+
else console.log(ui.error(e.message));
|
|
100
|
+
}
|
|
101
|
+
}, { category: 'FILES', aliases: ['rm'] });
|
|
102
|
+
|
|
103
|
+
// ── /ls [path] ── List files
|
|
104
|
+
reg('ls', 'List files and directories', (args) => {
|
|
105
|
+
const dir = path.resolve(args.join(' ') || '.');
|
|
106
|
+
try {
|
|
107
|
+
const items = fs.readdirSync(dir, { withFileTypes: true });
|
|
108
|
+
console.log(ui.header(`DIR: ${dir}`));
|
|
109
|
+
if (items.length === 0) { console.log(ui.dim(' (empty)')); return; }
|
|
110
|
+
|
|
111
|
+
const dirs = items.filter(i => i.isDirectory()).sort((a, b) => a.name.localeCompare(b.name));
|
|
112
|
+
const files = items.filter(i => !i.isDirectory()).sort((a, b) => a.name.localeCompare(b.name));
|
|
113
|
+
|
|
114
|
+
for (const d of dirs) console.log(` \x1b[34md\x1b[0m ${d.name}/`);
|
|
115
|
+
for (const f of files) {
|
|
116
|
+
try {
|
|
117
|
+
const stat = fs.statSync(path.join(dir, f.name));
|
|
118
|
+
const size = stat.size < 1024 ? `${stat.size}B` : stat.size < 1048576 ? `${(stat.size / 1024).toFixed(1)}K` : `${(stat.size / 1048576).toFixed(1)}M`;
|
|
119
|
+
console.log(` f ${f.name.padEnd(40)} ${size}`);
|
|
120
|
+
} catch {
|
|
121
|
+
console.log(` f ${f.name}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
console.log('');
|
|
125
|
+
console.log(ui.dim(` ${dirs.length} dirs, ${files.length} files`));
|
|
126
|
+
} catch (e) {
|
|
127
|
+
console.log(ui.error(e.code === 'ENOENT' ? `Not found: ${dir}` : e.message));
|
|
128
|
+
}
|
|
129
|
+
}, { category: 'FILES', aliases: ['dir'] });
|
|
130
|
+
|
|
131
|
+
// ── /mkdir <path> ── Create directory
|
|
132
|
+
reg('mkdir', 'Create a directory (including parents)', (args) => {
|
|
133
|
+
if (!args[0]) { console.log(ui.dim('Usage: /mkdir <dir-path>')); return; }
|
|
134
|
+
const dirPath = path.resolve(args.join(' '));
|
|
135
|
+
try {
|
|
136
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
137
|
+
console.log(ui.success(`Created: ${dirPath}`));
|
|
138
|
+
} catch (e) {
|
|
139
|
+
console.log(ui.error(e.message));
|
|
140
|
+
}
|
|
141
|
+
}, { category: 'FILES' });
|
|
142
|
+
|
|
143
|
+
// ── /touch <path> ── Create empty file
|
|
144
|
+
reg('touch', 'Create an empty file', (args) => {
|
|
145
|
+
if (!args[0]) { console.log(ui.dim('Usage: /touch <file-path>')); return; }
|
|
146
|
+
const filePath = path.resolve(args.join(' '));
|
|
147
|
+
try {
|
|
148
|
+
const dir = path.dirname(filePath);
|
|
149
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
150
|
+
if (fs.existsSync(filePath)) {
|
|
151
|
+
// Update mtime like Unix touch
|
|
152
|
+
const now = new Date();
|
|
153
|
+
fs.utimesSync(filePath, now, now);
|
|
154
|
+
console.log(ui.success(`Touched: ${filePath}`));
|
|
155
|
+
} else {
|
|
156
|
+
fs.writeFileSync(filePath, '');
|
|
157
|
+
console.log(ui.success(`Created: ${filePath}`));
|
|
158
|
+
}
|
|
159
|
+
} catch (e) {
|
|
160
|
+
console.log(ui.error(e.message));
|
|
161
|
+
}
|
|
162
|
+
}, { category: 'FILES' });
|
|
163
|
+
|
|
164
|
+
};
|
package/lib/commands/index.js
CHANGED
|
@@ -6,7 +6,7 @@ const { register } = require('../registry');
|
|
|
6
6
|
const moduleNames = [
|
|
7
7
|
'network', 'mcp', 'lucas', 'docker', 'database', 'cloudflare',
|
|
8
8
|
'ai', 'azure', 'agents', 'tasks', 'keys', 'setup', 'system',
|
|
9
|
-
'learn', 'sandbox', 'nvidia', 'edge', 'conversations', 'audit', 'compute',
|
|
9
|
+
'learn', 'sandbox', 'nvidia', 'edge', 'conversations', 'audit', 'compute', 'files',
|
|
10
10
|
];
|
|
11
11
|
|
|
12
12
|
function loadAll() {
|
package/package.json
CHANGED