@miruzero/zero-agent 0.3.6 → 0.3.7

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 (68) hide show
  1. package/README.md +31 -22
  2. package/package.json +6 -3
  3. package/src/agent.js +193 -167
  4. package/src/args.js +1 -3
  5. package/src/autostart.js +44 -68
  6. package/src/capabilities.js +95 -138
  7. package/src/chatgpt/auth/index.js +114 -0
  8. package/src/chatgpt/config/paths.js +25 -0
  9. package/src/chatgpt/index.js +5 -0
  10. package/src/chatgpt/session/direct-transport.js +68 -0
  11. package/src/chatgpt/session/exchange.js +17 -0
  12. package/src/chatgpt/session/index.js +102 -0
  13. package/src/chatgpt/session/local-storage.js +106 -0
  14. package/src/chatgpt/transport/direct/http-client.js +191 -0
  15. package/src/chatgpt-auth.js +17 -111
  16. package/src/chatgpt-runtime.js +8 -0
  17. package/src/chatgpt-session-exchange.js +8 -0
  18. package/src/chatgpt-session.js +26 -0
  19. package/src/cli.js +108 -71
  20. package/src/command/exec.js +34 -0
  21. package/src/command/filesystem/append-file.js +7 -12
  22. package/src/command/filesystem/copy-directory.js +8 -12
  23. package/src/command/filesystem/copy-file.js +8 -13
  24. package/src/command/filesystem/create-directory.js +7 -11
  25. package/src/command/filesystem/create-file.js +8 -13
  26. package/src/command/filesystem/delete-directory.js +7 -11
  27. package/src/command/filesystem/delete-file.js +8 -12
  28. package/src/command/filesystem/exists.js +6 -10
  29. package/src/command/filesystem/get-file-info.js +7 -12
  30. package/src/command/filesystem/glob-files.js +8 -13
  31. package/src/command/filesystem/grep-files.js +9 -15
  32. package/src/command/filesystem/hash-file.js +6 -11
  33. package/src/command/filesystem/index.js +33 -34
  34. package/src/command/filesystem/list-directory.js +6 -10
  35. package/src/command/filesystem/move-directory.js +9 -14
  36. package/src/command/filesystem/move-file.js +8 -13
  37. package/src/command/filesystem/path-policy.js +33 -16
  38. package/src/command/filesystem/read-file.js +7 -11
  39. package/src/command/filesystem/read-files.js +5 -8
  40. package/src/command/filesystem/rename-file.js +5 -8
  41. package/src/command/filesystem/replace-file.js +7 -12
  42. package/src/command/filesystem/text.js +5 -7
  43. package/src/command/filesystem/touch-file.js +8 -13
  44. package/src/command/filesystem/truncate-file.js +6 -10
  45. package/src/command/filesystem/walk.js +36 -39
  46. package/src/command/filesystem/write-file.js +8 -13
  47. package/src/command/index.js +19 -24
  48. package/src/command/path/index.js +46 -53
  49. package/src/config.js +21 -33
  50. package/src/device-paths.js +53 -0
  51. package/src/enrollment-client.js +19 -0
  52. package/src/http.js +27 -2
  53. package/src/identity-state.js +27 -0
  54. package/src/index.js +2 -4
  55. package/src/instance.js +85 -0
  56. package/src/local-server.js +174 -0
  57. package/src/local-tools.js +205 -205
  58. package/src/shared-zero/catalog.js +33 -0
  59. package/src/shared-zero/contract.js +18 -0
  60. package/src/shared-zero/descriptor.js +25 -0
  61. package/src/shared-zero/errors.js +12 -0
  62. package/src/shared-zero/index.js +9 -0
  63. package/src/shared-zero/runtime.js +14 -0
  64. package/src/shared-zero/target.js +19 -0
  65. package/src/shared-zero/tool-id.js +26 -0
  66. package/src/storage-roots.js +21 -0
  67. package/src/zero-runtime.js +1 -11
  68. package/src/zero.js +78 -0
package/src/cli.js CHANGED
@@ -1,15 +1,9 @@
1
1
  'use strict';
2
2
 
3
- const childProcess = require('node:child_process');
4
- const os = require('node:os');
5
- const pkg = require('../package.json');
6
- const config = require('./config');
7
- const autostart = require('./autostart');
8
- const caps = require('./capabilities');
9
- const agent = require('./agent');
10
- const { parseArgs } = require('./args');
3
+ const zero = require('./zero');
11
4
 
12
- const usage = () => `Zero Agent
5
+
6
+ exports.usage = () => `Zero Agent
13
7
 
14
8
  Usage:
15
9
  zero-agent setup [--server <url>] [--name <device>] [--code <pairing-code>] [--no-global]
@@ -28,7 +22,7 @@ const printJson = (value) => console.log(JSON.stringify(value, null, 2));
28
22
  const okMark = (ok) => ok ? 'OK' : 'FAIL';
29
23
  const DEFAULT_SERVER = 'https://zero.miru.work';
30
24
 
31
- const autostartInterval = (parsed) => Number(parsed.interval || autostart.DEFAULT_INTERVAL);
25
+ const autostartInterval = (parsed) => Number(parsed.interval || zero.autostart.DEFAULT_INTERVAL);
32
26
 
33
27
  const printAutostartInfo = (prepared) => {
34
28
  console.log('Autostart file');
@@ -39,24 +33,24 @@ const printAutostartInfo = (prepared) => {
39
33
  console.log(prepared.launcherFile);
40
34
  console.log('ตั้งค่า:');
41
35
  console.log('ZERO_TRACE=1');
42
- console.log('ZERO_AGENT_ROOTS=' + prepared.roots);
36
+ console.log(prepared.roots ? ('ZERO_AGENT_ROOTS=' + prepared.roots) : 'storageRoots=auto');
43
37
  console.log('interval=' + prepared.interval + 'ms');
44
38
  console.log('log=' + prepared.logFile);
45
39
  };
46
40
 
47
41
  const ensureLocalBootstrap = async (parsed, env) => {
48
- let device = config.loadDevice(env);
42
+ let device = zero.config.loadDevice(env);
49
43
  if (!device) {
50
- const init = agent.initDevice({ name: parsed.name, server: parsed.server || DEFAULT_SERVER }, env);
44
+ const init = zero.agent.initDevice({ name: parsed.name, server: parsed.server || DEFAULT_SERVER }, env);
51
45
  console.log('autoInit=OK file=' + init.file);
52
- device = config.loadDevice(env);
46
+ device = zero.config.loadDevice(env);
53
47
  }
54
- const prepared = autostart.isPrepared(env)
55
- ? { ...autostart.paths(env), ok: true, reused: true }
56
- : autostart.prepare({ interval: autostartInterval(parsed) }, env);
48
+ const prepared = zero.autostart.isPrepared(env)
49
+ ? { ...zero.autostart.paths(env), ok: true, reused: true }
50
+ : zero.autostart.prepare({ interval: autostartInterval(parsed) }, env);
57
51
  console.log('autoSetup=' + (prepared.reused ? 'REUSED' : 'OK') + ' launcher=' + prepared.launcherFile);
58
52
  if (parsed.code && (!device || !device.device_id)) {
59
- const paired = await agent.pairDevice({
53
+ const paired = await zero.agent.pairDevice({
60
54
  name: parsed.name || device?.name,
61
55
  server: parsed.server || device?.server || DEFAULT_SERVER,
62
56
  code: parsed.code
@@ -71,10 +65,10 @@ const runGlobalInstall = (parsed, env) => {
71
65
  console.log('globalInstall=SKIPPED');
72
66
  return { skipped: true };
73
67
  }
74
- const spec = parsed.package || (pkg.name + '@' + pkg.version);
68
+ const spec = parsed.package || (zero.pkg.name + '@' + zero.pkg.version);
75
69
  const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
76
70
  console.log('globalInstall=START ' + spec);
77
- const result = childProcess.spawnSync(npmCmd, ['install', '-g', spec], { stdio: 'inherit', env });
71
+ const result = zero.childProcess.spawnSync(npmCmd, ['install', '-g', spec], { stdio: 'inherit', env });
78
72
  if (result.status === 0) {
79
73
  console.log('globalInstall=OK');
80
74
  return { ok: true };
@@ -88,23 +82,23 @@ const runSetup = async (parsed, env) => {
88
82
  const name = parsed.name || undefined;
89
83
  console.log('Zero Agent Setup');
90
84
  console.log('server=' + server);
91
- console.log('name=' + (name || os.hostname()));
85
+ console.log('name=' + (name || zero.os.hostname()));
92
86
  const installed = runGlobalInstall(parsed, env);
93
87
  if (!installed.skipped && !installed.ok) {
94
88
  console.error('setup=FAILED global installation did not complete');
95
89
  return 70;
96
90
  }
97
- const init = agent.initDevice({ name, server }, env);
91
+ const init = zero.agent.initDevice({ name, server }, env);
98
92
  console.log('init=OK file=' + init.file);
99
- const prepared = autostart.prepare({ interval: autostartInterval(parsed) }, env);
93
+ const prepared = zero.autostart.prepare({ interval: autostartInterval(parsed) }, env);
100
94
  printAutostartInfo(prepared);
101
95
  if (!parsed.code) {
102
96
  console.log('pairing=NEEDS_CODE');
103
97
  console.log('Next: zero-agent pair --server ' + server + ' --code ZERO-XXXXXX --name ' + init.device.name);
104
- console.log('Or: npx ' + pkg.name + '@latest setup --server ' + server + ' --name ' + init.device.name + ' --code ZERO-XXXXXX');
98
+ console.log('Or: npx ' + zero.pkg.name + '@latest setup --server ' + server + ' --name ' + init.device.name + ' --code ZERO-XXXXXX');
105
99
  return 0;
106
100
  }
107
- const paired = await agent.pairDevice({ name: init.device.name, server, code: parsed.code }, env);
101
+ const paired = await zero.agent.pairDevice({ name: init.device.name, server, code: parsed.code }, env);
108
102
  console.log('pair=OK file=' + paired.file);
109
103
  printJson(paired.device);
110
104
  const heartbeat = await runStartOnce(env);
@@ -115,19 +109,19 @@ const runSetup = async (parsed, env) => {
115
109
  };
116
110
 
117
111
 
118
- const status = (env) => {
119
- const device = config.loadDevice(env);
120
- if (!device) return { paired: false, home: config.agentHome(env) };
121
- return { paired: Boolean(device.device_id), home: config.agentHome(env), device: config.redactedDevice(device) };
112
+ exports.status = (env) => {
113
+ const device = zero.config.loadDevice(env);
114
+ if (!device) return { paired: false, home: zero.config.agentHome(env) };
115
+ return { paired: Boolean(device.device_id), home: zero.config.agentHome(env), device: zero.config.redactedDevice(device) };
122
116
  };
123
117
 
124
- const doctor = (env) => {
125
- const capability = caps.buildCapabilities(env);
118
+ exports.doctor = (env) => {
119
+ const capability = zero.capabilities.buildCapabilities(env);
126
120
  return {
127
121
  ok: true,
128
122
  node: process.version,
129
123
  platform: process.platform,
130
- agent_home: config.agentHome(env),
124
+ agent_home: zero.config.agentHome(env),
131
125
  mcp_root: capability.mcp_root,
132
126
  providers: capability.providers.length,
133
127
  capability_hash: capability.capability_hash
@@ -135,14 +129,13 @@ const doctor = (env) => {
135
129
  };
136
130
 
137
131
  const runStartOnce = async (env) => {
138
- const heartbeat = await agent.heartbeatOnce(env);
132
+ const heartbeat = await zero.agent.heartbeatOnce(env);
139
133
  if (!heartbeat.ok && heartbeat.code === 'NOT_PAIRED') return heartbeat;
140
- const capability = await agent.syncCapabilitiesOnce(env);
141
- const task = await agent.pollTaskOnce(env);
142
- return { heartbeat, capability, task };
134
+ const capability = await zero.agent.syncCapabilitiesOnce(env);
135
+ return { heartbeat, capability };
143
136
  };
144
137
 
145
- const runSafeStartCycle = async (env, runOnce = runStartOnce) => {
138
+ exports.runSafeStartCycle = async (env, runOnce = runStartOnce) => {
146
139
  try {
147
140
  return { ok: true, result: await runOnce(env) };
148
141
  } catch (error) {
@@ -158,15 +151,18 @@ const runSafeStartCycle = async (env, runOnce = runStartOnce) => {
158
151
  };
159
152
 
160
153
  const printStartBanner = (env) => {
161
- const device = config.loadDevice(env);
154
+ const device = zero.config.loadDevice(env);
155
+ let chatgptAuth = 'unknown';
156
+ try { chatgptAuth = zero.chatgptAuth.status({ env }).auth?.status || 'unknown'; } catch {}
162
157
  console.log('🚀 Starting Zero Agent...');
163
- console.log('🔁 Session restored: ' + (device?.device_id ? 'yes' : 'no'));
158
+ console.log('🔁 Device pairing restored: ' + (device?.device_id ? 'yes' : 'no'));
159
+ console.log('🔐 ChatGPT Auth: ' + chatgptAuth);
164
160
  console.log('👤 Device Name: ' + (device?.name || '(unpaired)'));
165
161
  console.log('🆔 Device ID: ' + (device?.device_id || '(not paired)'));
166
162
  console.log('🌐 Server: ' + (device?.server || 'https://zero.miru.work'));
167
163
  };
168
164
 
169
- const loopResultEvent = (result) => {
165
+ exports.loopResultEvent = (result) => {
170
166
  if (!result || result.code === 'NOT_PAIRED') {
171
167
  return { level: 'error', text: '❌ Device not paired: run zero-agent pair first' };
172
168
  }
@@ -183,6 +179,10 @@ const loopResultEvent = (result) => {
183
179
  at: new Date().toISOString(),
184
180
  heartbeat: okMark(Boolean(hb.ok)),
185
181
  capabilities: okMark(Boolean(cap.ok)),
182
+ heartbeat_http_status: hb.status ?? null,
183
+ heartbeat_error_code: hb.body?.error?.code || hb.code || null,
184
+ capabilities_http_status: cap.status ?? null,
185
+ capabilities_error_code: cap.body?.error?.code || cap.code || null,
186
186
  status: device?.status || null,
187
187
  name: device?.name || null,
188
188
  host: device?.host || null,
@@ -194,48 +194,76 @@ const loopResultEvent = (result) => {
194
194
  };
195
195
 
196
196
  const printLoopResult = (result) => {
197
- const event = loopResultEvent(result);
197
+ const event = exports.loopResultEvent(result);
198
198
  if (!event) return;
199
199
  if (event.level === 'error') console.error(event.text);
200
200
  else console.log(event.text);
201
201
  };
202
202
 
203
- async function run(argv = [], env = process.env) {
204
- const parsed = parseArgs(argv);
203
+ exports.connectionTransition = (previous = 'connecting', cycle = {}, env = process.env) => {
204
+ const result = cycle.result || {};
205
+ const hb = result.heartbeat || {};
206
+ const cap = result.capability || {};
207
+ const healthy = Boolean(cycle.ok) && Boolean(hb.ok) && Boolean(cap.ok);
208
+ const device = cap.body?.device || hb.body?.device || zero.config.loadDevice(env) || {};
209
+ const lastSeen = hb.body?.device?.last_seen || cap.body?.device?.last_seen || null;
210
+ const server = zero.config.loadDevice(env)?.server || env.ZERO_SERVER_URL || DEFAULT_SERVER;
211
+
212
+ if (healthy) {
213
+ if (previous === 'connected') return { state: 'connected', level: 'info', text: null };
214
+ const verb = previous === 'disconnected' ? 'Reconnected' : 'Connected';
215
+ return {
216
+ state: 'connected',
217
+ level: 'info',
218
+ text: `✅ ${verb} to Zero Core\n Status: Online\n Device: ${device.name || device.host || '(unnamed)'}\n Core: ${server}\n Heartbeat: OK (HTTP ${hb.status ?? 'OK'})\n Capabilities: Synced (HTTP ${cap.status ?? 'OK'})\n Last seen: ${lastSeen || 'now'}\n Agent version: ${zero.pkg.version}\n Contract: ${cap.body?.device?.contract_version || hb.body?.device?.contract_version || zero.contract.version}\n Compatibility: ${String(cap.body?.device?.compatibility?.status || hb.body?.device?.compatibility?.status || 'unknown').toUpperCase()}`
219
+ };
220
+ }
221
+
222
+ if (previous === 'disconnected') return { state: 'disconnected', level: 'error', text: null };
223
+ const status = hb.status != null ? `heartbeat HTTP ${hb.status}` : cap.status != null ? `capabilities HTTP ${cap.status}` : (cycle.error?.code || 'connection error');
224
+ return {
225
+ state: 'disconnected',
226
+ level: 'error',
227
+ text: `🔴 Disconnected from Zero Core — ${server}\n↻ Reconnecting automatically (${status})`
228
+ };
229
+ };
230
+
231
+ exports.run = async (argv = [], env = process.env) => {
232
+ const parsed = zero.args.parseArgs(argv);
205
233
  const command = parsed._[0] || 'help';
206
234
  if (command === 'help' || parsed.help) {
207
- console.log(usage());
235
+ console.log(exports.usage());
208
236
  return 0;
209
237
  }
210
238
  if (command === 'setup') {
211
239
  return runSetup(parsed, env);
212
240
  }
213
241
  if (command === 'init') {
214
- const result = agent.initDevice({ name: parsed.name, server: parsed.server }, env);
242
+ const result = zero.agent.initDevice({ name: parsed.name, server: parsed.server }, env);
215
243
  console.log(`init=OK file=${result.file}`);
216
244
  printJson(result.device);
217
245
  return 0;
218
246
  }
219
247
  if (command === 'pair') {
220
- const result = await agent.pairDevice({ name: parsed.name, server: parsed.server, code: parsed.code }, env);
248
+ const result = await zero.agent.pairDevice({ name: parsed.name, server: parsed.server, code: parsed.code }, env);
221
249
  console.log(`pair=OK file=${result.file}`);
222
250
  printJson(result.device);
223
251
  return 0;
224
252
  }
225
253
  if (command === 'status') {
226
- printJson(status(env));
254
+ printJson(exports.status(env));
227
255
  return 0;
228
256
  }
229
257
  if (command === 'devices') {
230
- printJson(await agent.listDevices(env));
258
+ printJson(await zero.agent.listDevices(env));
231
259
  return 0;
232
260
  }
233
261
  if (command === 'doctor') {
234
- printJson(doctor(env));
262
+ printJson(exports.doctor(env));
235
263
  return 0;
236
264
  }
237
265
  if (command === 'providers') {
238
- printJson(caps.manifestProviders(env).map((item) => ({
266
+ printJson(zero.capabilities.manifestProviders(env).map((item) => ({
239
267
  name: item.name,
240
268
  type: item.type,
241
269
  description: item.description,
@@ -244,37 +272,46 @@ async function run(argv = [], env = process.env) {
244
272
  return 0;
245
273
  }
246
274
  if (command === 'tools' || command === 'capabilities') {
247
- printJson(caps.buildCapabilities(env));
275
+ printJson(zero.capabilities.buildCapabilities(env));
248
276
  return 0;
249
277
  }
250
278
  if (command === 'start') {
251
279
  await ensureLocalBootstrap(parsed, env);
280
+ const ownership = await zero.instance.acquire(env);
281
+ console.log(JSON.stringify({ event: 'zero.agent.zero.instance.active', pid: ownership.pid, replaced_pid: ownership.replaced_pid }));
282
+ const local = await zero.localServer.startLocalServer({ env });
283
+ console.log(JSON.stringify({ event: 'zero.agent.local.ready', host: '127.0.0.1', port: local.port }));
252
284
  if (parsed.once) {
253
- printJson(await runStartOnce(env));
285
+ try { printJson(await runStartOnce(env)); }
286
+ finally {
287
+ await new Promise((resolve) => local.server.close(resolve));
288
+ ownership.release();
289
+ }
254
290
  return 0;
255
291
  }
256
- const interval = Number(parsed.interval || autostart.DEFAULT_INTERVAL);
257
292
  printStartBanner(env);
258
- console.log('⏱️ Heartbeat interval: ' + interval + 'ms');
259
- console.log('🟢 Device ready; press Ctrl+C to stop');
260
- while (true) {
261
- const cycle = await runSafeStartCycle(env);
262
- if (cycle.ok) {
263
- printLoopResult(cycle.result);
264
- } else {
265
- console.error(JSON.stringify({
266
- at: new Date().toISOString(),
267
- heartbeat: 'FAIL',
268
- reconnect: 'WAIT',
269
- error: cycle.error
270
- }));
293
+ console.log('🔌 Connecting to Zero Core...');
294
+ const cycle = await exports.runSafeStartCycle(env);
295
+ const transition = exports.connectionTransition('connecting', cycle, env);
296
+ if (transition.text) {
297
+ if (transition.level === 'error') console.error(transition.text);
298
+ else console.log(transition.text);
299
+ }
300
+ if (transition.state !== 'connected') return 69;
301
+ console.log('📡 Task channel: listening for Core events');
302
+ for (;;) {
303
+ try {
304
+ const stream = await zero.agent.listenTaskStreamOnce(env);
305
+ if (!stream.ok) throw Object.assign(new Error('task stream unavailable'), { code: `HTTP_${stream.status || 0}` });
306
+ console.error('🔴 Task channel closed by Zero Core');
307
+ } catch (error) {
308
+ console.error(`🔴 Task channel disconnected (${error.code || error.message || 'connection error'})`);
271
309
  }
272
- await new Promise((resolve) => setTimeout(resolve, interval));
310
+ console.log('↻ Reconnecting task channel after disconnect event...');
311
+ await new Promise((resolve) => setTimeout(resolve, 1000));
273
312
  }
274
313
  }
275
314
  console.error('unknown command: ' + command);
276
- console.error(usage());
315
+ console.error(exports.usage());
277
316
  return 64;
278
- }
279
-
280
- module.exports = { run, usage, status, doctor, runSafeStartCycle, loopResultEvent };
317
+ };
@@ -0,0 +1,34 @@
1
+ 'use strict';
2
+
3
+ const zero = require('../zero');
4
+
5
+
6
+ exports.execDescriptor = zero.descriptor.validate({
7
+ name: 'zero.command.exec.run',
8
+ aliases: ['zero.command.exec'],
9
+ description: 'Execute an allowed local command through the agent process adapter.',
10
+ capability: 'process.execute',
11
+ risk: 'execute',
12
+ execution: 'target',
13
+ adapters: ['native'],
14
+ inputSchema: {
15
+ type: 'object',
16
+ properties: {
17
+ command: { type: 'string' },
18
+ args: { type: 'array', items: { type: 'string' } }
19
+ },
20
+ required: ['command'],
21
+ additionalProperties: false
22
+ }
23
+ });
24
+
25
+ exports.exec = async (input = {}) => new Promise((resolve, reject) => {
26
+ zero.childProcess.execFile(input.command, Array.isArray(input.args) ? input.args : [], { shell: false }, (error, stdout, stderr) => {
27
+ if (error) {
28
+ reject(error);
29
+ return;
30
+ }
31
+ resolve({ ok: true, stdout, stderr });
32
+ });
33
+ });
34
+
@@ -1,11 +1,9 @@
1
1
  'use strict';
2
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');
3
+ const zero = require('../../zero');
7
4
 
8
- const descriptor = zero.descriptor.validate({
5
+
6
+ exports.descriptor = zero.descriptor.validate({
9
7
  name: 'zero.command.filesystem.appendFile',
10
8
  aliases: ['zero.command.appendFile'],
11
9
  description: 'Append UTF-8 text content to a file inside allowed roots.',
@@ -21,13 +19,10 @@ const descriptor = zero.descriptor.validate({
21
19
  }
22
20
  });
23
21
 
24
- const appendFile = (args = {}, context = {}) => {
25
- const file = pathPolicy.resolveAllowed(args.path, context);
22
+ exports.appendFile = (args = {}, context = {}) => {
23
+ const file = zero.pathPolicy.resolveAllowed(args.path, context);
26
24
  const data = String(args.content ?? '');
27
- fs.mkdirSync(path.dirname(file), { recursive: true });
28
- fs.appendFileSync(file, data, 'utf8');
25
+ zero.fs.mkdirSync(zero.path.dirname(file), { recursive: true });
26
+ zero.fs.appendFileSync(file, data, 'utf8');
29
27
  return { ok: true, path: file, mode: 'append', bytes: Buffer.byteLength(data) };
30
28
  };
31
-
32
- const appendFileModule = { descriptor, appendFile };
33
- Object.assign(exports, appendFileModule);
@@ -1,10 +1,9 @@
1
1
  'use strict';
2
2
 
3
- const fs = require('node:fs');
4
- const zero = require('../../zero-runtime');
5
- const pathPolicy = require('./path-policy');
3
+ const zero = require('../../zero');
6
4
 
7
- const descriptor = zero.descriptor.validate({
5
+
6
+ exports.descriptor = zero.descriptor.validate({
8
7
  name: 'zero.command.filesystem.copyDirectory',
9
8
  aliases: ['zero.command.copyDirectory'],
10
9
  description: 'Copy a directory recursively inside allowed roots.',
@@ -21,13 +20,10 @@ const descriptor = zero.descriptor.validate({
21
20
  });
22
21
 
23
22
  const fail = (code, message) => Object.assign(new Error(message), { code });
24
- const copyDirectory = (args = {}, context = {}) => {
25
- const source = pathPolicy.resolveAllowed(args.source, context);
26
- const destination = pathPolicy.resolveAllowed(args.destination, context);
27
- if (!fs.statSync(source).isDirectory()) throw fail('NOT_A_DIRECTORY', 'source is not a directory');
28
- fs.cpSync(source, destination, { recursive: true, force: args.overwrite === true, errorOnExist: args.overwrite !== true });
23
+ exports.copyDirectory = (args = {}, context = {}) => {
24
+ const source = zero.pathPolicy.resolveAllowed(args.source, context);
25
+ const destination = zero.pathPolicy.resolveAllowed(args.destination, context);
26
+ if (!zero.fs.statSync(source).isDirectory()) throw fail('NOT_A_DIRECTORY', 'source is not a directory');
27
+ zero.fs.cpSync(source, destination, { recursive: true, force: args.overwrite === true, errorOnExist: args.overwrite !== true });
29
28
  return { ok: true, source, destination };
30
29
  };
31
-
32
- const copyDirectoryModule = { descriptor, copyDirectory };
33
- Object.assign(exports, copyDirectoryModule);
@@ -1,11 +1,9 @@
1
1
  'use strict';
2
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');
3
+ const zero = require('../../zero');
7
4
 
8
- const descriptor = zero.descriptor.validate({
5
+
6
+ exports.descriptor = zero.descriptor.validate({
9
7
  name: 'zero.command.filesystem.copyFile',
10
8
  aliases: ['zero.command.copyFile'],
11
9
  description: 'Copy one file inside allowed roots.',
@@ -21,13 +19,10 @@ const descriptor = zero.descriptor.validate({
21
19
  }
22
20
  });
23
21
 
24
- const copyFile = (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.copyFileSync(source, destination);
22
+ exports.copyFile = (args = {}, context = {}) => {
23
+ const source = zero.pathPolicy.resolveAllowed(args.source, context);
24
+ const destination = zero.pathPolicy.resolveAllowed(args.destination, context);
25
+ zero.fs.mkdirSync(zero.path.dirname(destination), { recursive: true });
26
+ zero.fs.copyFileSync(source, destination);
29
27
  return { ok: true, source, destination };
30
28
  };
31
-
32
- const copyFileModule = { descriptor, copyFile };
33
- Object.assign(exports, copyFileModule);
@@ -1,10 +1,9 @@
1
1
  'use strict';
2
2
 
3
- const fs = require('node:fs');
4
- const zero = require('../../zero-runtime');
5
- const pathPolicy = require('./path-policy');
3
+ const zero = require('../../zero');
6
4
 
7
- const descriptor = zero.descriptor.validate({
5
+
6
+ exports.descriptor = zero.descriptor.validate({
8
7
  name: 'zero.command.filesystem.createDirectory',
9
8
  aliases: ['zero.command.createDirectory'],
10
9
  description: 'Create a directory recursively inside allowed roots.',
@@ -20,11 +19,8 @@ const descriptor = zero.descriptor.validate({
20
19
  }
21
20
  });
22
21
 
23
- const createDirectory = (args = {}, context = {}) => {
24
- const dir = pathPolicy.resolveAllowed(args.path, context);
25
- fs.mkdirSync(dir, { recursive: true });
26
- return { ok: true, path: dir, created: fs.existsSync(dir) && fs.statSync(dir).isDirectory() };
22
+ exports.createDirectory = (args = {}, context = {}) => {
23
+ const dir = zero.pathPolicy.resolveAllowed(args.path, context);
24
+ zero.fs.mkdirSync(dir, { recursive: true });
25
+ return { ok: true, path: dir, created: zero.fs.existsSync(dir) && zero.fs.statSync(dir).isDirectory() };
27
26
  };
28
-
29
- const createDirectoryModule = { descriptor, createDirectory };
30
- Object.assign(exports, createDirectoryModule);
@@ -1,11 +1,9 @@
1
1
  'use strict';
2
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');
3
+ const zero = require('../../zero');
7
4
 
8
- const descriptor = zero.descriptor.validate({
5
+
6
+ exports.descriptor = zero.descriptor.validate({
9
7
  name: 'zero.command.filesystem.createFile',
10
8
  aliases: ['zero.command.createFile'],
11
9
  description: 'Create a UTF-8 text file inside allowed roots.',
@@ -22,14 +20,11 @@ const descriptor = zero.descriptor.validate({
22
20
  });
23
21
 
24
22
  const fail = (code, message) => Object.assign(new Error(message), { code });
25
- const createFile = (args = {}, context = {}) => {
26
- const file = pathPolicy.resolveAllowed(args.path, context);
27
- if (fs.existsSync(file) && args.overwrite !== true) throw fail('FILE_EXISTS', 'file already exists');
23
+ exports.createFile = (args = {}, context = {}) => {
24
+ const file = zero.pathPolicy.resolveAllowed(args.path, context);
25
+ if (zero.fs.existsSync(file) && args.overwrite !== true) throw fail('FILE_EXISTS', 'file already exists');
28
26
  const data = String(args.content ?? '');
29
- fs.mkdirSync(path.dirname(file), { recursive: true });
30
- fs.writeFileSync(file, data, 'utf8');
27
+ zero.fs.mkdirSync(zero.path.dirname(file), { recursive: true });
28
+ zero.fs.writeFileSync(file, data, 'utf8');
31
29
  return { ok: true, path: file, created: true, bytes: Buffer.byteLength(data) };
32
30
  };
33
-
34
- const createFileModule = { descriptor, createFile };
35
- Object.assign(exports, createFileModule);
@@ -1,10 +1,9 @@
1
1
  'use strict';
2
2
 
3
- const fs = require('node:fs');
4
- const zero = require('../../zero-runtime');
5
- const pathPolicy = require('./path-policy');
3
+ const zero = require('../../zero');
6
4
 
7
- const descriptor = zero.descriptor.validate({
5
+
6
+ exports.descriptor = zero.descriptor.validate({
8
7
  name: 'zero.command.filesystem.deleteDirectory',
9
8
  aliases: ['zero.command.deleteDirectory'],
10
9
  description: 'Delete a directory inside allowed roots.',
@@ -16,12 +15,9 @@ const descriptor = zero.descriptor.validate({
16
15
  });
17
16
 
18
17
  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 });
18
+ exports.deleteDirectory = (args = {}, context = {}) => {
19
+ const dir = zero.pathPolicy.resolveAllowed(args.path, context);
20
+ if (!zero.fs.statSync(dir).isDirectory()) throw fail('NOT_A_DIRECTORY', 'path is not a directory');
21
+ zero.fs.rmSync(dir, { recursive: args.recursive === true, force: false });
23
22
  return { ok: true, path: dir, deleted: true };
24
23
  };
25
-
26
- const deleteDirectoryModule = { descriptor, deleteDirectory };
27
- Object.assign(exports, deleteDirectoryModule);
@@ -1,10 +1,9 @@
1
1
  'use strict';
2
2
 
3
- const fs = require('node:fs');
4
- const zero = require('../../zero-runtime');
5
- const pathPolicy = require('./path-policy');
3
+ const zero = require('../../zero');
6
4
 
7
- const descriptor = zero.descriptor.validate({
5
+
6
+ exports.descriptor = zero.descriptor.validate({
8
7
  name: 'zero.command.filesystem.deleteFile',
9
8
  aliases: ['zero.command.deleteFile'],
10
9
  description: 'Delete one file inside allowed roots. Directories are rejected.',
@@ -21,13 +20,10 @@ const descriptor = zero.descriptor.validate({
21
20
  });
22
21
 
23
22
  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);
23
+ exports.deleteFile = (args = {}, context = {}) => {
24
+ const file = zero.pathPolicy.resolveAllowed(args.path, context);
25
+ if (!zero.fs.existsSync(file)) throw fail('FILE_NOT_FOUND', 'file does not exist');
26
+ if (!zero.fs.statSync(file).isFile()) throw fail('NOT_A_FILE', 'deleteFile supports files only');
27
+ zero.fs.unlinkSync(file);
29
28
  return { ok: true, path: file, deleted: true };
30
29
  };
31
-
32
- const deleteFileModule = { descriptor, deleteFile };
33
- Object.assign(exports, deleteFileModule);
@@ -1,10 +1,9 @@
1
1
  'use strict';
2
2
 
3
- const fs = require('node:fs');
4
- const zero = require('../../zero-runtime');
5
- const pathPolicy = require('./path-policy');
3
+ const zero = require('../../zero');
6
4
 
7
- const descriptor = zero.descriptor.validate({
5
+
6
+ exports.descriptor = zero.descriptor.validate({
8
7
  name: 'zero.command.filesystem.exists',
9
8
  aliases: ['zero.command.exists'],
10
9
  description: 'Check whether a path exists inside allowed roots.',
@@ -20,10 +19,7 @@ const descriptor = zero.descriptor.validate({
20
19
  }
21
20
  });
22
21
 
23
- const exists = (args = {}, context = {}) => {
24
- const pathValue = pathPolicy.resolveAllowed(args.path, context);
25
- return { ok: true, path: pathValue, exists: fs.existsSync(pathValue) };
22
+ exports.exists = (args = {}, context = {}) => {
23
+ const pathValue = zero.pathPolicy.resolveAllowed(args.path, context);
24
+ return { ok: true, path: pathValue, exists: zero.fs.existsSync(pathValue) };
26
25
  };
27
-
28
- const existsModule = { descriptor, exists };
29
- Object.assign(exports, existsModule);