@miruzero/zero-agent 0.3.4 → 0.3.6

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.
Files changed (38) hide show
  1. package/README.md +1 -1
  2. package/package.json +9 -2
  3. package/src/agent.js +167 -145
  4. package/src/autostart.js +53 -7
  5. package/src/capabilities.js +155 -89
  6. package/src/chatgpt-auth.js +121 -0
  7. package/src/cli.js +63 -21
  8. package/src/command/filesystem/append-file.js +33 -0
  9. package/src/command/filesystem/copy-directory.js +33 -0
  10. package/src/command/filesystem/copy-file.js +33 -0
  11. package/src/command/filesystem/create-directory.js +30 -0
  12. package/src/command/filesystem/create-file.js +35 -0
  13. package/src/command/filesystem/delete-directory.js +27 -0
  14. package/src/command/filesystem/delete-file.js +33 -0
  15. package/src/command/filesystem/exists.js +29 -0
  16. package/src/command/filesystem/get-file-info.js +39 -0
  17. package/src/command/filesystem/glob-files.js +39 -0
  18. package/src/command/filesystem/grep-files.js +54 -0
  19. package/src/command/filesystem/hash-file.js +35 -0
  20. package/src/command/filesystem/index.js +34 -0
  21. package/src/command/filesystem/list-directory.js +33 -0
  22. package/src/command/filesystem/move-directory.js +30 -0
  23. package/src/command/filesystem/move-file.js +33 -0
  24. package/src/command/filesystem/path-policy.js +31 -0
  25. package/src/command/filesystem/read-file.js +37 -0
  26. package/src/command/filesystem/read-files.js +56 -0
  27. package/src/command/filesystem/rename-file.js +20 -0
  28. package/src/command/filesystem/replace-file.js +45 -0
  29. package/src/command/filesystem/text.js +17 -0
  30. package/src/command/filesystem/touch-file.js +29 -0
  31. package/src/command/filesystem/truncate-file.js +26 -0
  32. package/src/command/filesystem/walk.js +39 -0
  33. package/src/command/filesystem/write-file.js +39 -0
  34. package/src/command/index.js +49 -0
  35. package/src/command/path/index.js +53 -0
  36. package/src/index.js +7 -0
  37. package/src/local-tools.js +205 -171
  38. package/src/zero-runtime.js +13 -0
@@ -0,0 +1,27 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const zero = require('../../zero-runtime');
5
+ const pathPolicy = require('./path-policy');
6
+
7
+ const descriptor = zero.descriptor.validate({
8
+ name: 'zero.command.filesystem.deleteDirectory',
9
+ aliases: ['zero.command.deleteDirectory'],
10
+ description: 'Delete a directory inside allowed roots.',
11
+ capability: 'filesystem.write',
12
+ risk: 'write',
13
+ execution: 'target',
14
+ adapters: ['native'],
15
+ inputSchema: { type: 'object', properties: { path: { type: 'string' }, recursive: { type: 'boolean', default: false } }, required: ['path'], additionalProperties: false }
16
+ });
17
+
18
+ const fail = (code, message) => Object.assign(new Error(message), { code });
19
+ const deleteDirectory = (args = {}, context = {}) => {
20
+ const dir = pathPolicy.resolveAllowed(args.path, context);
21
+ if (!fs.statSync(dir).isDirectory()) throw fail('NOT_A_DIRECTORY', 'path is not a directory');
22
+ fs.rmSync(dir, { recursive: args.recursive === true, force: false });
23
+ return { ok: true, path: dir, deleted: true };
24
+ };
25
+
26
+ const deleteDirectoryModule = { descriptor, deleteDirectory };
27
+ Object.assign(exports, deleteDirectoryModule);
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const zero = require('../../zero-runtime');
5
+ const pathPolicy = require('./path-policy');
6
+
7
+ const descriptor = zero.descriptor.validate({
8
+ name: 'zero.command.filesystem.deleteFile',
9
+ aliases: ['zero.command.deleteFile'],
10
+ description: 'Delete one file inside allowed roots. Directories are rejected.',
11
+ capability: 'filesystem.write',
12
+ risk: 'write',
13
+ execution: 'target',
14
+ adapters: ['native'],
15
+ inputSchema: {
16
+ type: 'object',
17
+ properties: { path: { type: 'string' } },
18
+ required: ['path'],
19
+ additionalProperties: false
20
+ }
21
+ });
22
+
23
+ const fail = (code, message) => Object.assign(new Error(message), { code });
24
+ const deleteFile = (args = {}, context = {}) => {
25
+ const file = pathPolicy.resolveAllowed(args.path, context);
26
+ if (!fs.existsSync(file)) throw fail('FILE_NOT_FOUND', 'file does not exist');
27
+ if (!fs.statSync(file).isFile()) throw fail('NOT_A_FILE', 'deleteFile supports files only');
28
+ fs.unlinkSync(file);
29
+ return { ok: true, path: file, deleted: true };
30
+ };
31
+
32
+ const deleteFileModule = { descriptor, deleteFile };
33
+ Object.assign(exports, deleteFileModule);
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const zero = require('../../zero-runtime');
5
+ const pathPolicy = require('./path-policy');
6
+
7
+ const descriptor = zero.descriptor.validate({
8
+ name: 'zero.command.filesystem.exists',
9
+ aliases: ['zero.command.exists'],
10
+ description: 'Check whether a path exists inside allowed roots.',
11
+ capability: 'filesystem.read',
12
+ risk: 'read',
13
+ execution: 'target',
14
+ adapters: ['native'],
15
+ inputSchema: {
16
+ type: 'object',
17
+ properties: { path: { type: 'string' } },
18
+ required: ['path'],
19
+ additionalProperties: false
20
+ }
21
+ });
22
+
23
+ const exists = (args = {}, context = {}) => {
24
+ const pathValue = pathPolicy.resolveAllowed(args.path, context);
25
+ return { ok: true, path: pathValue, exists: fs.existsSync(pathValue) };
26
+ };
27
+
28
+ const existsModule = { descriptor, exists };
29
+ Object.assign(exports, existsModule);
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const zero = require('../../zero-runtime');
5
+ const pathPolicy = require('./path-policy');
6
+ const text = require('./text');
7
+
8
+ const descriptor = zero.descriptor.validate({
9
+ name: 'zero.command.filesystem.getFileInfo',
10
+ aliases: ['zero.command.getFileInfo'],
11
+ description: 'Get metadata for a file or directory inside allowed roots.',
12
+ capability: 'filesystem.read',
13
+ risk: 'read',
14
+ execution: 'target',
15
+ adapters: ['native'],
16
+ inputSchema: {
17
+ type: 'object',
18
+ properties: { path: { type: 'string' } },
19
+ required: ['path'],
20
+ additionalProperties: false
21
+ }
22
+ });
23
+
24
+ const getFileInfo = (args = {}, context = {}) => {
25
+ const target = pathPolicy.resolveAllowed(args.path, context);
26
+ const stat = fs.statSync(target);
27
+ return {
28
+ ok: true,
29
+ path: target,
30
+ type: stat.isDirectory() ? 'directory' : 'file',
31
+ size: stat.size,
32
+ createdAt: stat.birthtime.toISOString(),
33
+ modifiedAt: stat.mtime.toISOString(),
34
+ lineCount: stat.isFile() ? text.readUtf8(target).split(/\r?\n/).length : null
35
+ };
36
+ };
37
+
38
+ const getFileInfoModule = { descriptor, getFileInfo };
39
+ Object.assign(exports, getFileInfoModule);
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+
3
+ const path = require('node:path');
4
+ const zero = require('../../zero-runtime');
5
+ const pathPolicy = require('./path-policy');
6
+ const walk = require('./walk');
7
+
8
+ const descriptor = zero.descriptor.validate({
9
+ name: 'zero.command.filesystem.globFiles',
10
+ aliases: ['zero.command.globFiles'],
11
+ description: 'Find files by wildcard pattern inside allowed roots.',
12
+ capability: 'filesystem.read',
13
+ risk: 'read',
14
+ execution: 'target',
15
+ adapters: ['native'],
16
+ inputSchema: {
17
+ type: 'object',
18
+ properties: {
19
+ path: { type: 'string' },
20
+ pattern: { type: 'string' },
21
+ maxResults: { type: 'integer', minimum: 1, maximum: 1000, default: 200 }
22
+ },
23
+ required: ['path', 'pattern'],
24
+ additionalProperties: false
25
+ }
26
+ });
27
+
28
+ const globFiles = (args = {}, context = {}) => {
29
+ const root = pathPolicy.resolveAllowed(args.path, context);
30
+ const rx = walk.wildcard(args.pattern || '*');
31
+ const max = Math.max(1, Math.min(args.maxResults || 200, 1000));
32
+ const files = walk.walk(root, { filesOnly: true })
33
+ .filter((item) => rx.test(path.basename(item.path)) || rx.test(item.relativePath))
34
+ .slice(0, max);
35
+ return { ok: true, path: root, pattern: args.pattern, files };
36
+ };
37
+
38
+ const globFilesModule = { descriptor, globFiles };
39
+ Object.assign(exports, globFilesModule);
@@ -0,0 +1,54 @@
1
+ 'use strict';
2
+
3
+ const path = require('node:path');
4
+ const zero = require('../../zero-runtime');
5
+ const pathPolicy = require('./path-policy');
6
+ const textModule = require('./text');
7
+ const walk = require('./walk');
8
+
9
+ const descriptor = zero.descriptor.validate({
10
+ name: 'zero.command.filesystem.grepFiles',
11
+ aliases: ['zero.command.grepFiles'],
12
+ description: 'Search text content in files inside allowed roots.',
13
+ capability: 'filesystem.read',
14
+ risk: 'read',
15
+ execution: 'target',
16
+ adapters: ['native'],
17
+ inputSchema: {
18
+ type: 'object',
19
+ properties: {
20
+ path: { type: 'string' },
21
+ pattern: { type: 'string' },
22
+ literal: { type: 'boolean', default: false },
23
+ filePattern: { type: 'string' },
24
+ maxResults: { type: 'integer', minimum: 1, maximum: 1000, default: 200 }
25
+ },
26
+ required: ['path', 'pattern'],
27
+ additionalProperties: false
28
+ }
29
+ });
30
+
31
+ const grepFiles = (args = {}, context = {}) => {
32
+ const root = pathPolicy.resolveAllowed(args.path, context);
33
+ const max = Math.max(1, Math.min(args.maxResults || 200, 1000));
34
+ const rx = args.literal ? null : new RegExp(String(args.pattern), 'i');
35
+ const fileRx = args.filePattern ? walk.wildcard(args.filePattern) : null;
36
+ const matches = [];
37
+ for (const file of walk.walk(root, { filesOnly: true })) {
38
+ if (fileRx && !fileRx.test(path.basename(file.path)) && !fileRx.test(file.relativePath)) continue;
39
+ let text;
40
+ try { text = textModule.readUtf8(file.path); } catch { continue; }
41
+ const lines = text.split(/\r?\n/);
42
+ for (let index = 0; index < lines.length; index += 1) {
43
+ const line = lines[index];
44
+ const hit = args.literal ? line.includes(String(args.pattern)) : rx.test(line);
45
+ if (!hit) continue;
46
+ matches.push({ path: file.path, relativePath: file.relativePath, line: index + 1, text: line });
47
+ if (matches.length >= max) return { ok: true, path: root, pattern: args.pattern, matches };
48
+ }
49
+ }
50
+ return { ok: true, path: root, pattern: args.pattern, matches };
51
+ };
52
+
53
+ const grepFilesModule = { descriptor, grepFiles };
54
+ Object.assign(exports, grepFilesModule);
@@ -0,0 +1,35 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+ const fs = require('node:fs');
5
+ const zero = require('../../zero-runtime');
6
+ const pathPolicy = require('./path-policy');
7
+
8
+ const descriptor = zero.descriptor.validate({
9
+ name: 'zero.command.filesystem.hashFile',
10
+ aliases: ['zero.command.hashFile'],
11
+ description: 'Calculate SHA-256 hash of a file inside allowed roots.',
12
+ capability: 'filesystem.read',
13
+ risk: 'read',
14
+ execution: 'target',
15
+ adapters: ['native'],
16
+ inputSchema: {
17
+ type: 'object',
18
+ properties: { path: { type: 'string' } },
19
+ required: ['path'],
20
+ additionalProperties: false
21
+ }
22
+ });
23
+
24
+ const hashFile = (args = {}, context = {}) => {
25
+ const file = pathPolicy.resolveAllowed(args.path, context);
26
+ return {
27
+ ok: true,
28
+ path: file,
29
+ algorithm: 'sha256',
30
+ hash: crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex')
31
+ };
32
+ };
33
+
34
+ const hashFileModule = { descriptor, hashFile };
35
+ Object.assign(exports, hashFileModule);
@@ -0,0 +1,34 @@
1
+ 'use strict';
2
+
3
+ const modules = {
4
+ readFile: require('./read-file'),
5
+ readFiles: require('./read-files'),
6
+ listDirectory: require('./list-directory'),
7
+ getFileInfo: require('./get-file-info'),
8
+ globFiles: require('./glob-files'),
9
+ grepFiles: require('./grep-files'),
10
+ writeFile: require('./write-file'),
11
+ appendFile: require('./append-file'),
12
+ createFile: require('./create-file'),
13
+ touchFile: require('./touch-file'),
14
+ replaceFile: require('./replace-file'),
15
+ createDirectory: require('./create-directory'),
16
+ deleteFile: require('./delete-file'),
17
+ copyDirectory: require('./copy-directory'),
18
+ moveDirectory: require('./move-directory'),
19
+ deleteDirectory: require('./delete-directory'),
20
+ truncateFile: require('./truncate-file'),
21
+ copyFile: require('./copy-file'),
22
+ moveFile: require('./move-file'),
23
+ renameFile: require('./rename-file'),
24
+ exists: require('./exists'),
25
+ hashFile: require('./hash-file')
26
+ };
27
+
28
+ const filesystem = {};
29
+ for (const [name, item] of Object.entries(modules)) {
30
+ filesystem[name] = item[name];
31
+ filesystem[`${name}Descriptor`] = item.descriptor;
32
+ }
33
+
34
+ Object.assign(exports, filesystem);
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ const zero = require('../../zero-runtime');
4
+ const pathPolicy = require('./path-policy');
5
+ const walk = require('./walk');
6
+
7
+ const descriptor = zero.descriptor.validate({
8
+ name: 'zero.command.filesystem.listDirectory',
9
+ aliases: ['zero.command.listDirectory'],
10
+ description: 'List files and directories recursively inside allowed roots.',
11
+ capability: 'filesystem.read',
12
+ risk: 'read',
13
+ execution: 'target',
14
+ adapters: ['native'],
15
+ inputSchema: {
16
+ type: 'object',
17
+ properties: {
18
+ path: { type: 'string' },
19
+ depth: { type: 'integer', minimum: 1, maximum: 5, default: 2 }
20
+ },
21
+ required: ['path'],
22
+ additionalProperties: false
23
+ }
24
+ });
25
+
26
+ const listDirectory = (args = {}, context = {}) => {
27
+ const root = pathPolicy.resolveAllowed(args.path, context);
28
+ const depth = Math.max(1, Math.min(args.depth || 2, 5));
29
+ return { ok: true, path: root, depth, entries: walk.walk(root, { depth }) };
30
+ };
31
+
32
+ const listDirectoryModule = { descriptor, listDirectory };
33
+ Object.assign(exports, listDirectoryModule);
@@ -0,0 +1,30 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const zero = require('../../zero-runtime');
6
+ const pathPolicy = require('./path-policy');
7
+
8
+ const descriptor = zero.descriptor.validate({
9
+ name: 'zero.command.filesystem.moveDirectory',
10
+ aliases: ['zero.command.moveDirectory'],
11
+ description: 'Move a directory inside allowed roots.',
12
+ capability: 'filesystem.write',
13
+ risk: 'write',
14
+ execution: 'target',
15
+ adapters: ['native'],
16
+ inputSchema: { type: 'object', properties: { source: { type: 'string' }, destination: { type: 'string' } }, required: ['source', 'destination'], additionalProperties: false }
17
+ });
18
+
19
+ const fail = (code, message) => Object.assign(new Error(message), { code });
20
+ const moveDirectory = (args = {}, context = {}) => {
21
+ const source = pathPolicy.resolveAllowed(args.source, context);
22
+ const destination = pathPolicy.resolveAllowed(args.destination, context);
23
+ if (!fs.statSync(source).isDirectory()) throw fail('NOT_A_DIRECTORY', 'source is not a directory');
24
+ fs.mkdirSync(path.dirname(destination), { recursive: true });
25
+ fs.renameSync(source, destination);
26
+ return { ok: true, source, destination };
27
+ };
28
+
29
+ const moveDirectoryModule = { descriptor, moveDirectory };
30
+ Object.assign(exports, moveDirectoryModule);
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const zero = require('../../zero-runtime');
6
+ const pathPolicy = require('./path-policy');
7
+
8
+ const descriptor = zero.descriptor.validate({
9
+ name: 'zero.command.filesystem.moveFile',
10
+ aliases: ['zero.command.moveFile'],
11
+ description: 'Move one file or directory inside allowed roots.',
12
+ capability: 'filesystem.write',
13
+ risk: 'write',
14
+ execution: 'target',
15
+ adapters: ['native'],
16
+ inputSchema: {
17
+ type: 'object',
18
+ properties: { source: { type: 'string' }, destination: { type: 'string' } },
19
+ required: ['source', 'destination'],
20
+ additionalProperties: false
21
+ }
22
+ });
23
+
24
+ const moveFile = (args = {}, context = {}) => {
25
+ const source = pathPolicy.resolveAllowed(args.source, context);
26
+ const destination = pathPolicy.resolveAllowed(args.destination, context);
27
+ fs.mkdirSync(path.dirname(destination), { recursive: true });
28
+ fs.renameSync(source, destination);
29
+ return { ok: true, source, destination };
30
+ };
31
+
32
+ const moveFileModule = { descriptor, moveFile };
33
+ Object.assign(exports, moveFileModule);
@@ -0,0 +1,31 @@
1
+ 'use strict';
2
+
3
+ const os = require('node:os');
4
+ const path = require('node:path');
5
+
6
+ const err = (code, message) => Object.assign(new Error(message), { code });
7
+ const normalize = (value) => path.resolve(String(value)).toLowerCase();
8
+ const rootsFor = (context = {}, env = context.env || process.env) => {
9
+ const explicit = Array.isArray(context.commandRoots) && context.commandRoots.length
10
+ ? context.commandRoots
11
+ : (Array.isArray(context.roots) ? context.roots : []);
12
+ const configured = explicit.length ? explicit : String(env.ZERO_AGENT_ROOTS || env.ZERO_COMMAND_ROOTS || '')
13
+ .split(';').map((item) => item.trim()).filter(Boolean);
14
+ const roots = configured.length ? configured : [os.homedir(), process.cwd()];
15
+ return roots.map((item) => path.resolve(String(item)));
16
+ };
17
+ const resolveAllowed = (inputPath, context = {}, env = context.env || process.env) => {
18
+ if (!inputPath || typeof inputPath !== 'string') throw err('INVALID_PATH', 'path is required');
19
+ const roots = rootsFor(context, env);
20
+ const target = path.resolve(inputPath);
21
+ const targetNorm = normalize(target);
22
+ const ok = roots.some((root) => {
23
+ const rootNorm = normalize(root);
24
+ return targetNorm === rootNorm || targetNorm.startsWith(rootNorm + path.sep.toLowerCase());
25
+ });
26
+ if (!ok) throw err('PATH_NOT_ALLOWED', 'path is outside agent allowed roots');
27
+ return target;
28
+ };
29
+
30
+ const pathPolicy = { rootsFor, resolveAllowed };
31
+ Object.assign(exports, pathPolicy);
@@ -0,0 +1,37 @@
1
+ 'use strict';
2
+
3
+ const zero = require('../../zero-runtime');
4
+ const pathPolicy = require('./path-policy');
5
+ const text = require('./text');
6
+
7
+ const descriptor = zero.descriptor.validate({
8
+ name: 'zero.command.filesystem.readFile',
9
+ aliases: ['zero.command.readFile'],
10
+ description: 'Read a UTF-8 text file inside allowed roots with line pagination.',
11
+ capability: 'filesystem.read',
12
+ risk: 'read',
13
+ execution: 'target',
14
+ adapters: ['native'],
15
+ inputSchema: {
16
+ type: 'object',
17
+ properties: {
18
+ path: { type: 'string' },
19
+ offset: { type: 'integer', default: 0 },
20
+ length: { type: 'integer', minimum: 1, maximum: 1000, default: 200 }
21
+ },
22
+ required: ['path'],
23
+ additionalProperties: false
24
+ }
25
+ });
26
+ const readFile = (args = {}, context = {}) => {
27
+ const file = pathPolicy.resolveAllowed(args.path, context);
28
+ const lines = text.readUtf8(file).split(/\r?\n/);
29
+ const offset = Number.isInteger(args.offset) ? args.offset : 0;
30
+ const length = Number.isInteger(args.length) ? args.length : 200;
31
+ const start = offset < 0 ? Math.max(lines.length + offset, 0) : Math.max(offset, 0);
32
+ const slice = lines.slice(start, start + length);
33
+ return { ok: true, path: file, totalLines: lines.length, offset: start, length: slice.length, content: text.numbered(slice, start) };
34
+ };
35
+
36
+ const readFileModule = { descriptor, readFile };
37
+ Object.assign(exports, readFileModule);
@@ -0,0 +1,56 @@
1
+ 'use strict';
2
+
3
+ const zero = require('../../zero-runtime');
4
+ const readFileModule = require('./read-file');
5
+
6
+ const descriptor = zero.descriptor.validate({
7
+ name: 'zero.command.filesystem.readFiles',
8
+ aliases: ['zero.command.readFiles'],
9
+ description: 'Read multiple UTF-8 text files inside allowed roots.',
10
+ capability: 'filesystem.read',
11
+ risk: 'read',
12
+ execution: 'target',
13
+ adapters: ['native'],
14
+ inputSchema: {
15
+ type: 'object',
16
+ properties: {
17
+ paths: { type: 'array', items: { type: 'string' }, minItems: 1, maxItems: 50 },
18
+ files: {
19
+ type: 'array', minItems: 1, maxItems: 50,
20
+ items: {
21
+ type: 'object',
22
+ properties: {
23
+ path: { type: 'string' },
24
+ offset: { type: 'integer' },
25
+ length: { type: 'integer', minimum: 1, maximum: 1000 }
26
+ },
27
+ required: ['path'],
28
+ additionalProperties: false
29
+ }
30
+ },
31
+ offset: { type: 'integer', default: 0 },
32
+ length: { type: 'integer', minimum: 1, maximum: 1000, default: 200 }
33
+ },
34
+ additionalProperties: false
35
+ }
36
+ });
37
+
38
+ const fail = (code, message) => Object.assign(new Error(message), { code });
39
+ const readFiles = (args = {}, context = {}) => {
40
+ const requests = Array.isArray(args.files) && args.files.length
41
+ ? args.files.map((item) => ({ path: item.path, offset: item.offset, length: item.length }))
42
+ : (Array.isArray(args.paths) ? args.paths : []).map((path) => ({ path, offset: args.offset, length: args.length }));
43
+ if (!requests.length) throw fail('INVALID_PATHS', 'paths or files must contain at least one file');
44
+ if (requests.length > 50) throw fail('TOO_MANY_PATHS', 'readFiles supports up to 50 files');
45
+ const files = requests.map((request) => {
46
+ try { return readFileModule.readFile(request, context); }
47
+ catch (error) {
48
+ return { ok: false, path: String(request.path), error: { code: error.code || 'READ_FAILED', message: error.message || String(error) } };
49
+ }
50
+ });
51
+ const succeeded = files.filter((item) => item.ok).length;
52
+ return { ok: succeeded === files.length, count: files.length, succeeded, failed: files.length - succeeded, files };
53
+ };
54
+
55
+ const readFilesModule = { descriptor, readFiles };
56
+ Object.assign(exports, readFilesModule);
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+
3
+ const zero = require('../../zero-runtime');
4
+ const moveFileModule = require('./move-file');
5
+
6
+ const descriptor = zero.descriptor.validate({
7
+ name: 'zero.command.filesystem.renameFile',
8
+ aliases: ['zero.command.renameFile'],
9
+ description: 'Rename one file or directory inside allowed roots.',
10
+ capability: 'filesystem.write',
11
+ risk: 'write',
12
+ execution: 'target',
13
+ adapters: ['native'],
14
+ inputSchema: moveFileModule.descriptor.inputSchema
15
+ });
16
+
17
+ const renameFile = (args = {}, context = {}) => moveFileModule.moveFile(args, context);
18
+
19
+ const renameFileModule = { descriptor, renameFile };
20
+ Object.assign(exports, renameFileModule);
@@ -0,0 +1,45 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const zero = require('../../zero-runtime');
5
+ const pathPolicy = require('./path-policy');
6
+ const textModule = require('./text');
7
+
8
+ const descriptor = zero.descriptor.validate({
9
+ name: 'zero.command.filesystem.replaceFile',
10
+ aliases: ['zero.command.replaceFile'],
11
+ description: 'Replace exact text inside a UTF-8 file in allowed roots.',
12
+ capability: 'filesystem.write',
13
+ risk: 'write',
14
+ execution: 'target',
15
+ adapters: ['native'],
16
+ inputSchema: {
17
+ type: 'object',
18
+ properties: {
19
+ path: { type: 'string' },
20
+ oldString: { type: 'string' },
21
+ newString: { type: 'string' },
22
+ expectedReplacements: { type: 'integer', minimum: 0 }
23
+ },
24
+ required: ['path', 'oldString', 'newString'],
25
+ additionalProperties: false
26
+ }
27
+ });
28
+
29
+ const fail = (code, message) => Object.assign(new Error(message), { code });
30
+ const replaceFile = (args = {}, context = {}) => {
31
+ const file = pathPolicy.resolveAllowed(args.path, context);
32
+ const oldString = String(args.oldString ?? '');
33
+ if (!oldString) throw fail('INVALID_REPLACE', 'oldString must not be empty');
34
+ const newString = String(args.newString ?? '');
35
+ const text = textModule.readUtf8(file);
36
+ const replacements = text.split(oldString).length - 1;
37
+ if (Number.isInteger(args.expectedReplacements) && replacements !== args.expectedReplacements) {
38
+ throw fail('REPLACE_COUNT_MISMATCH', `expected ${args.expectedReplacements} replacements, got ${replacements}`);
39
+ }
40
+ fs.writeFileSync(file, text.split(oldString).join(newString), 'utf8');
41
+ return { ok: true, path: file, replacements };
42
+ };
43
+
44
+ const replaceFileModule = { descriptor, replaceFile };
45
+ Object.assign(exports, replaceFileModule);
@@ -0,0 +1,17 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+
5
+ const textDecoder = new TextDecoder('utf-8', { fatal: false });
6
+ const err = (code, message) => Object.assign(new Error(message), { code });
7
+ const readUtf8 = (file) => {
8
+ const data = fs.readFileSync(file);
9
+ if (data.includes(0)) throw err('BINARY_FILE', 'binary file is not supported');
10
+ return textDecoder.decode(data);
11
+ };
12
+ const numbered = (lines, startIndex) => lines
13
+ .map((line, index) => String(startIndex + index + 1).padStart(4, ' ') + '\t' + line)
14
+ .join('\n');
15
+
16
+ const text = { readUtf8, numbered };
17
+ Object.assign(exports, text);
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const zero = require('../../zero-runtime');
6
+ const pathPolicy = require('./path-policy');
7
+
8
+ const descriptor = zero.descriptor.validate({
9
+ name: 'zero.command.filesystem.touchFile',
10
+ aliases: ['zero.command.touchFile'],
11
+ description: 'Create or update a file timestamp inside allowed roots.',
12
+ capability: 'filesystem.write',
13
+ risk: 'write',
14
+ execution: 'target',
15
+ adapters: ['native'],
16
+ inputSchema: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'], additionalProperties: false }
17
+ });
18
+
19
+ const touchFile = (args = {}, context = {}) => {
20
+ const file = pathPolicy.resolveAllowed(args.path, context);
21
+ fs.mkdirSync(path.dirname(file), { recursive: true });
22
+ if (!fs.existsSync(file)) fs.closeSync(fs.openSync(file, 'w'));
23
+ const now = new Date();
24
+ fs.utimesSync(file, now, now);
25
+ return { ok: true, path: file, touched: true };
26
+ };
27
+
28
+ const touchFileModule = { descriptor, touchFile };
29
+ Object.assign(exports, touchFileModule);
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const zero = require('../../zero-runtime');
5
+ const pathPolicy = require('./path-policy');
6
+
7
+ const descriptor = zero.descriptor.validate({
8
+ name: 'zero.command.filesystem.truncateFile',
9
+ aliases: ['zero.command.truncateFile'],
10
+ description: 'Truncate a file to a specific byte size inside allowed roots.',
11
+ capability: 'filesystem.write',
12
+ risk: 'write',
13
+ execution: 'target',
14
+ adapters: ['native'],
15
+ inputSchema: { type: 'object', properties: { path: { type: 'string' }, size: { type: 'integer', minimum: 0, default: 0 } }, required: ['path'], additionalProperties: false }
16
+ });
17
+
18
+ const truncateFile = (args = {}, context = {}) => {
19
+ const file = pathPolicy.resolveAllowed(args.path, context);
20
+ const size = Number.isInteger(args.size) && args.size >= 0 ? args.size : 0;
21
+ fs.truncateSync(file, size);
22
+ return { ok: true, path: file, size };
23
+ };
24
+
25
+ const truncateFileModule = { descriptor, truncateFile };
26
+ Object.assign(exports, truncateFileModule);