@miruzero/zero-agent 0.3.5 → 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 -158
  4. package/src/args.js +1 -3
  5. package/src/autostart.js +44 -68
  6. package/src/capabilities.js +98 -104
  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 +27 -0
  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/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # Zero Agent
2
2
 
3
- Local worker agent for Zero MCP multi-device execution.
3
+ `zero-agent` is the machine-local execution worker for Miruko Zero OS. It owns target-side execution, local identity/state, filesystem/path policy, capability reporting, and task completion back to Zero Core.
4
+
5
+ It does not own the public MCP endpoint or global device registry authority. The package includes only the machine-local ChatGPT auth/session runtime required by its login flow.
4
6
 
5
7
  ## Quick start
6
8
 
@@ -8,14 +10,11 @@ Local worker agent for Zero MCP multi-device execution.
8
10
  npx @miruzero/zero-agent@latest start --once
9
11
  ```
10
12
 
11
- On first run, `start` creates local config, a launcher, and a Windows user-logon autostart file automatically.
12
-
13
- ## Commands
13
+ Commands exposed by the package include:
14
14
 
15
- ```powershell
16
- zero-agent setup --name TON --code ZERO-XXXX
17
- zero-agent start --once
18
- zero-agent start
15
+ ```text
16
+ zero-agent setup
17
+ zero-agent start [--once]
19
18
  zero-agent status
20
19
  zero-agent doctor
21
20
  zero-agent providers
@@ -23,23 +22,33 @@ zero-agent tools
23
22
  zero-agent capabilities
24
23
  ```
25
24
 
26
- ## Autostart
27
-
28
- Default state path:
25
+ For source validation:
29
26
 
30
- ```text
31
- %USERPROFILE%\.zero-agent\device.json
32
- %USERPROFILE%\.zero-agent\start-zero-agent.cmd
33
- %APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\MiruZeroAgent.vbs
27
+ ```powershell
28
+ Set-Location M:\Zero_HOME\source\agent\zero-agent
29
+ npm test
30
+ npm run doctor
31
+ npm pack --dry-run
34
32
  ```
33
+ ## Runtime ownership
35
34
 
36
- `setup` installs a stable global Zero Agent package by default. The generated launcher records the resolved Node executable and installed Zero Agent entrypoint, while the long-running agent retries transient connection failures instead of exiting.
35
+ Zero Agent owns the machine-local capability boundary. It reports capabilities to Core and executes work only inside local policy/identity constraints.
37
36
 
38
- Device tokens are local secrets and must not be printed or committed.
37
+ Shared Zero capabilities should be reached through the module’s Zero root rather than rebuilding dependency ownership in every file. Public capability paths stay stable while implementations may change behind them.
39
38
 
40
- ## Package checks
39
+ ## ChatGPT boundary
41
40
 
42
- ```powershell
43
- npm test
44
- npm pack --dry-run
45
- ```
41
+ Zero Agent owns the ChatGPT auth/session capability required by HOME under `src/chatgpt/`. `src/chatgpt-runtime.js` resolves this package-local implementation only; sibling ChatGPT projects are Supplier / R&D references, never runtime dependencies.
42
+
43
+ The owner can reuse a valid HOME auth context or acquire one from the current machine's local ChatGPT session state. The machine-local handoff reads Chrome Local Storage as input, selects the active personal workspace when available, performs the HOME-owned direct session exchange, persists canonical state under the Agent home, and returns only redacted readiness to Core.
44
+
45
+ There is no Puppeteer/Playwright/CDP browser launcher and no runtime require/import of `M:\Zero_ChatGPT`.
46
+
47
+ ## Local instance ownership
48
+
49
+ A current Agent instance acquires local ownership before polling. Replacement/takeover behavior exists to prevent duplicate pollers for the same paired identity. Inspect current tests/source before relying on old PID/process notes.
50
+
51
+ ## Secrets
52
+
53
+ Device tokens, machine auth material, ChatGPT session data, cookies and setup tickets are local secrets. Never print, commit, or return them in ordinary diagnostics. Report only redacted status/evidence.
54
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miruzero/zero-agent",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "MiruZero local worker agent for multi-device tool execution.",
5
5
  "type": "commonjs",
6
6
  "main": "src/index.js",
@@ -11,7 +11,7 @@
11
11
  "zero-agent": "bin/zero-agent.js"
12
12
  },
13
13
  "scripts": {
14
- "test": "node --test",
14
+ "test": "node --test && node ../../ownership/verify.js",
15
15
  "start": "node bin/zero-agent.js start",
16
16
  "doctor": "node bin/zero-agent.js doctor"
17
17
  },
@@ -25,6 +25,9 @@
25
25
  },
26
26
  "license": "UNLICENSED",
27
27
  "dependencies": {
28
- "@miruzero/zero": "0.1.0"
28
+ "classic-level": "^3.0.0"
29
+ },
30
+ "allowScripts": {
31
+ "classic-level@3.0.0": true
29
32
  }
30
33
  }
package/src/agent.js CHANGED
@@ -1,158 +1,193 @@
1
- 'use strict';
2
-
3
- const os = require('node:os');
4
- const config = require('./config');
5
- const caps = require('./capabilities');
6
- const http = require('./http');
7
- const localTools = require('./local-tools');
8
- const command = require('./command');
9
- const zero = require('./zero-runtime');
10
-
11
- const nowIso = () => new Date().toISOString();
12
-
13
- const serverBase = (server) => String(server || 'https://zero.miru.work').replace(/\/$/, '');
14
- const trace = (env, event, payload = {}) => {
15
- if (env.ZERO_TRACE !== '1') return;
16
- console.log(JSON.stringify({ at: nowIso(), event, ...payload }));
17
- };
18
-
19
- const initDevice = ({ name, server }, env = process.env) => {
20
- const host = os.hostname();
21
- const device = {
22
- name: name || host,
23
- host,
24
- server: server || 'https://zero.miru.work',
25
- device_id: null,
26
- status: 'initialized',
27
- created_at: nowIso(),
28
- updated_at: nowIso()
29
- };
30
- const file = config.saveDevice(device, env);
31
- return { file, device: config.redactedDevice(device) };
32
- };
33
-
34
- const pairDevice = async ({ name, server, code }, env = process.env) => {
35
- if (!server) throw Object.assign(new Error('server is required'), { exitCode: 64 });
36
- if (!code) throw Object.assign(new Error('pairing code is required'), { exitCode: 64 });
37
- const host = os.hostname();
38
- const payload = { name: name || host, pairing_code: code, host };
39
- const result = await http.requestJson(`${serverBase(server)}/devices/register`, {
40
- method: 'POST',
41
- body: payload
42
- });
43
- if (!result.ok) {
44
- const error = new Error(`pair failed status=${result.status}`);
45
- error.response = result.json;
46
- throw error;
47
- }
48
- const device = {
49
- name: payload.name,
50
- host,
51
- server,
52
- device_id: result.json.device_id,
53
- device_token: result.json.device_token,
54
- status: 'paired',
55
- paired_at: nowIso(),
56
- updated_at: nowIso()
57
- };
58
- const file = config.saveDevice(device, env);
59
- return { file, device: config.redactedDevice(device) };
60
- };
61
-
62
- const getPairedDevice = (env = process.env) => {
63
- const device = config.loadDevice(env);
64
- if (!device || !device.device_id || !device.device_token || !device.server) {
65
- return null;
66
- }
67
- return device;
68
- };
69
-
70
- const heartbeatPayload = (device, env = process.env) => {
71
- const capability = caps.buildCapabilities(env);
72
- return {
73
- name: device.name,
74
- status: 'online',
75
- last_seen: nowIso(),
76
- capability_hash: capability.capability_hash,
77
- provider_count: capability.providers.length
78
- };
79
- };
80
-
81
- const heartbeatOnce = async (env = process.env) => {
82
- const device = getPairedDevice(env);
83
- if (!device) return { ok: false, code: 'NOT_PAIRED', message: 'run zero-agent pair first' };
84
- const url = `${serverBase(device.server)}/devices/${encodeURIComponent(device.device_id)}/heartbeat`;
85
- const result = await http.requestJson(url, {
86
- method: 'POST',
87
- token: device.device_token,
88
- body: heartbeatPayload(device, env)
89
- });
90
- return { ok: result.ok, status: result.status, body: result.json };
91
- };
92
-
93
- const syncCapabilitiesOnce = async (env = process.env) => {
94
- const device = getPairedDevice(env);
95
- if (!device) return { ok: false, code: 'NOT_PAIRED' };
96
- const url = `${serverBase(device.server)}/devices/${encodeURIComponent(device.device_id)}/capabilities`;
97
- const result = await http.requestJson(url, {
98
- method: 'POST',
99
- token: device.device_token,
100
- body: caps.buildCapabilities(env)
101
- });
102
- return { ok: result.ok, status: result.status, body: result.json };
103
- };
104
-
105
-
106
- const executeTask = (task = {}, env = process.env) => {
107
- if (task.contractVersion) zero.contract.assertCompatible(task.contractVersion);
108
- if (task.deadline && Date.parse(task.deadline) <= Date.now()) {
109
- throw Object.assign(new Error('task deadline exceeded'), { code: 'TASK_DEADLINE_EXCEEDED', retryable: false });
110
- }
111
- const tool = String(task.tool || '');
112
- if (tool.startsWith('zero.command.') || tool === 'readFile' || tool === 'command.readFile') {
113
- try { return command.callTool(tool, task.arguments || {}, { env }); }
114
- catch (error) {
115
- if (error.code !== 'UNKNOWN_TOOL') throw error;
116
- }
117
- }
118
- return localTools.execute(tool, task.arguments || {}, env);
119
- };
120
-
121
- const pollTaskOnce = async (env = process.env) => {
122
- const device = getPairedDevice(env);
123
- if (!device) return { ok: false, code: 'NOT_PAIRED' };
124
- const nextUrl = serverBase(device.server) + '/devices/' + encodeURIComponent(device.device_id) + '/tasks/next';
125
- const next = await http.requestJson(nextUrl, { method: 'GET', token: device.device_token });
126
- if (!next.ok) return { ok: false, status: next.status, body: next.json };
127
- const task = next.json?.task || null;
128
- if (!task) return { ok: true, task: null };
129
- const started = Date.now();
130
- trace(env, 'agent.task.received', { task_id: task.task_id, tool: task.tool, args: task.arguments || {} });
131
- const resultUrl = serverBase(device.server) + '/devices/' + encodeURIComponent(device.device_id)
132
- + '/tasks/' + encodeURIComponent(task.task_id) + '/result';
133
- try {
134
- const result = executeTask(task, env);
135
- trace(env, 'agent.task.done', { task_id: task.task_id, ok: true, duration_ms: Date.now() - started });
136
- const posted = await http.requestJson(resultUrl, { method: 'POST', token: device.device_token, body: { ok: true, result } });
137
- const finalTask = posted.json?.task || task;
138
- return { ok: posted.ok, task: finalTask, result, posted: { status: posted.status, body: posted.json } };
139
- } catch (error) {
140
- const payload = { ok: false, error: { code: error.code || 'AGENT_EXEC_ERROR', message: error.message || String(error) } };
141
- trace(env, 'agent.task.done', { task_id: task.task_id, ok: false, error: payload.error, duration_ms: Date.now() - started });
142
- const posted = await http.requestJson(resultUrl, { method: 'POST', token: device.device_token, body: payload });
143
- const finalTask = posted.json?.task || task;
144
- return { ok: false, task: finalTask, error: payload.error, posted: { status: posted.status, body: posted.json } };
145
- }
146
- };
147
-
148
- const listDevices = async (env = process.env) => {
149
- const device = config.loadDevice(env);
150
- const result = await http.requestJson(`${serverBase(device?.server)}/devices`);
151
- return { ok: result.ok, status: result.status, body: result.json };
152
- };
153
- const agent = {
154
- initDevice, pairDevice, heartbeatPayload, heartbeatOnce, syncCapabilitiesOnce,
155
- executeTask, pollTaskOnce, listDevices, getPairedDevice
156
- };
157
-
158
- Object.assign(exports, agent);
1
+ 'use strict';
2
+
3
+ const zero = require('./zero');
4
+
5
+ const nowIso = () => new Date().toISOString();
6
+
7
+ const serverBase = (server) => String(server || 'https://zero.miru.work').replace(/\/$/, '');
8
+ const trace = (env, event, payload = {}) => {
9
+ if (env.ZERO_TRACE !== '1') return;
10
+ console.log(JSON.stringify({ at: nowIso(), event, ...payload }));
11
+ };
12
+
13
+ exports.initDevice = ({ name, server }, env = process.env) => {
14
+ const host = zero.os.hostname();
15
+ const device = {
16
+ name: name || host,
17
+ host,
18
+ server: server || 'https://zero.miru.work',
19
+ device_id: null,
20
+ status: 'initialized',
21
+ created_at: nowIso(),
22
+ updated_at: nowIso()
23
+ };
24
+ const file = zero.config.saveDevice(device, env);
25
+ return { file, device: zero.config.redactedDevice(device) };
26
+ };
27
+
28
+ exports.pairDevice = async ({ name, server, code }, env = process.env) => {
29
+ if (!server) throw Object.assign(new Error('server is required'), { exitCode: 64 });
30
+ if (!code) throw Object.assign(new Error('pairing code is required'), { exitCode: 64 });
31
+ const host = zero.os.hostname();
32
+ const payload = { name: name || host, pairing_code: code, host };
33
+ const result = await zero.http.requestJson(`${serverBase(server)}/devices/register`, {
34
+ method: 'POST',
35
+ body: payload
36
+ });
37
+ if (!result.ok) {
38
+ const error = new Error(`pair failed status=${result.status}`);
39
+ error.response = result.json;
40
+ throw error;
41
+ }
42
+ const device = {
43
+ name: payload.name,
44
+ host,
45
+ server,
46
+ device_id: result.json.device_id,
47
+ device_token: result.json.device_token,
48
+ status: 'paired',
49
+ paired_at: nowIso(),
50
+ updated_at: nowIso()
51
+ };
52
+ const file = zero.config.saveDevice(device, env);
53
+ return { file, device: zero.config.redactedDevice(device) };
54
+ };
55
+
56
+ exports.getPairedDevice = (env = process.env) => {
57
+ const device = zero.config.loadDevice(env);
58
+ if (!device || !device.device_id || !device.device_token || !device.server) {
59
+ return null;
60
+ }
61
+ return device;
62
+ };
63
+
64
+ exports.heartbeatPayload = (device, env = process.env) => {
65
+ const capability = zero.capabilities.buildCapabilities(env);
66
+ return {
67
+ name: device.name,
68
+ status: 'online',
69
+ last_seen: nowIso(),
70
+ agentVersion: zero.pkg.version,
71
+ contractVersion: zero.contract.version,
72
+ platform: process.platform,
73
+ architecture: process.arch,
74
+ capability_hash: capability.capability_hash,
75
+ provider_count: capability.providers.length
76
+ };
77
+ };
78
+
79
+ exports.heartbeatOnce = async (env = process.env) => {
80
+ const device = exports.getPairedDevice(env);
81
+ if (!device) return { ok: false, code: 'NOT_PAIRED', message: 'run zero-agent pair first' };
82
+ const url = `${serverBase(device.server)}/devices/${encodeURIComponent(device.device_id)}/heartbeat`;
83
+ const result = await zero.http.requestJson(url, {
84
+ method: 'POST',
85
+ token: device.device_token,
86
+ body: exports.heartbeatPayload(device, env)
87
+ });
88
+ return { ok: result.ok, status: result.status, body: result.json };
89
+ };
90
+
91
+ exports.syncCapabilitiesOnce = async (env = process.env) => {
92
+ const device = exports.getPairedDevice(env);
93
+ if (!device) return { ok: false, code: 'NOT_PAIRED' };
94
+ const url = `${serverBase(device.server)}/devices/${encodeURIComponent(device.device_id)}/capabilities`;
95
+ const result = await zero.http.requestJson(url, {
96
+ method: 'POST',
97
+ token: device.device_token,
98
+ body: zero.capabilities.buildCapabilities(env)
99
+ });
100
+ return { ok: result.ok, status: result.status, body: result.json };
101
+ };
102
+
103
+
104
+ exports.executeTask = (task = {}, env = process.env) => {
105
+ if (task.contractVersion) zero.contract.assertCompatible(task.contractVersion);
106
+ if (task.deadline && Date.parse(task.deadline) <= Date.now()) {
107
+ throw Object.assign(new Error('task deadline exceeded'), { code: 'TASK_DEADLINE_EXCEEDED', retryable: false });
108
+ }
109
+ const tool = String(task.tool || '');
110
+ if (tool === 'zero.chatgpt.auth.check') {
111
+ const status = zero.chatgptAuth.status({ env }).auth || {};
112
+ const auth = ['valid', 'expired', 'missing', 'invalid'].includes(status.status) ? status.status : 'invalid';
113
+ return {
114
+ state: auth === 'valid' ? 'auth_valid' : 'auth_required',
115
+ runtime: 'ready',
116
+ auth,
117
+ redacted: true
118
+ };
119
+ }
120
+ if (tool === 'zero.device.paths.setAllowedRoots') {
121
+ return zero.devicePaths.saveAllowedRoots(task.arguments?.roots || [], env);
122
+ }
123
+ if (tool.startsWith('zero.command.') || tool === 'readFile' || tool === 'command.readFile') {
124
+ try { return zero.command.callTool(tool, task.arguments || {}, { env }); }
125
+ catch (error) {
126
+ if (error.code !== 'UNKNOWN_TOOL') throw error;
127
+ }
128
+ }
129
+ return zero.localTools.execute(tool, task.arguments || {}, env);
130
+ };
131
+
132
+ exports.executeTaskAsync = async (task = {}, env = process.env) => {
133
+ if (String(task.tool || '') !== 'zero.chatgpt.auth.ensure') return exports.executeTask(task, env);
134
+ if (task.contractVersion) zero.contract.assertCompatible(task.contractVersion);
135
+ if (task.deadline && Date.parse(task.deadline) <= Date.now()) {
136
+ throw Object.assign(new Error('task deadline exceeded'), { code: 'TASK_DEADLINE_EXCEEDED', retryable: false });
137
+ }
138
+ try {
139
+ await zero.chatgptAuth.ensure({ env });
140
+ return { state: 'auth_valid', runtime: 'ready', auth: 'valid', redacted: true };
141
+ } catch (error) {
142
+ const recoverable = new Set([
143
+ 'AUTH_REQUIRED', 'CHATGPT_LOCAL_SESSION_NOT_FOUND', 'CHATGPT_LOCAL_SESSION_AMBIGUOUS',
144
+ 'CHATGPT_SESSION_MARKER_MISSING', 'CHATGPT_SESSION_NOT_AUTHENTICATED',
145
+ 'CHATGPT_SESSION_FETCH_FAILED', 'CHATGPT_SESSION_BOOTSTRAP_FAILED',
146
+ 'CHATGPT_SESSION_BOOTSTRAP_COOKIE_MISSING', 'CHATGPT_SESSION_EXCHANGE_FAILED'
147
+ ]);
148
+ if (!recoverable.has(error?.code)) throw error;
149
+ const status = zero.chatgptAuth.status({ env }).auth || {};
150
+ const auth = ['expired', 'missing', 'invalid'].includes(status.status) ? status.status : 'missing';
151
+ return { state: 'auth_required', runtime: 'ready', auth, redacted: true };
152
+ }
153
+ };
154
+ exports.executeAndPostTask = async (task, device, env = process.env) => {
155
+ const started = Date.now();
156
+ trace(env, 'agent.task.received', { task_id: task.task_id, tool: task.tool, args: task.arguments || {} });
157
+ const resultUrl = serverBase(device.server) + '/devices/' + encodeURIComponent(device.device_id)
158
+ + '/tasks/' + encodeURIComponent(task.task_id) + '/result';
159
+ try {
160
+ const result = await exports.executeTaskAsync(task, env);
161
+ trace(env, 'agent.task.done', { task_id: task.task_id, ok: true, duration_ms: Date.now() - started });
162
+ const posted = await zero.http.requestJson(resultUrl, { method: 'POST', token: device.device_token, body: { ok: true, result } });
163
+ return { ok: posted.ok, task: posted.json?.task || task, result, posted: { status: posted.status, body: posted.json } };
164
+ } catch (error) {
165
+ const payload = { ok: false, error: { code: error.code || 'AGENT_EXEC_ERROR', message: error.message || String(error) } };
166
+ trace(env, 'agent.task.done', { task_id: task.task_id, ok: false, error: payload.error, duration_ms: Date.now() - started });
167
+ const posted = await zero.http.requestJson(resultUrl, { method: 'POST', token: device.device_token, body: payload });
168
+ return { ok: false, task: posted.json?.task || task, error: payload.error, posted: { status: posted.status, body: posted.json } };
169
+ }
170
+ };
171
+
172
+ exports.listenTaskStreamOnce = async (env = process.env, { signal } = {}) => {
173
+ const device = exports.getPairedDevice(env);
174
+ if (!device) return { ok: false, code: 'NOT_PAIRED' };
175
+ const streamUrl = serverBase(device.server) + '/devices/' + encodeURIComponent(device.device_id) + '/tasks/stream';
176
+ let lastTask = null;
177
+ const stream = await zero.http.consumeEventStream(streamUrl, {
178
+ token: device.device_token,
179
+ signal,
180
+ onEvent: async ({ event, data }) => {
181
+ if (event !== 'task') return;
182
+ const task = JSON.parse(data);
183
+ lastTask = await exports.executeAndPostTask(task, device, env);
184
+ }
185
+ });
186
+ return { ...stream, task: lastTask?.task || null, result: lastTask?.result || null };
187
+ };
188
+
189
+ exports.listDevices = async (env = process.env) => {
190
+ const device = zero.config.loadDevice(env);
191
+ const result = await zero.http.requestJson(`${serverBase(device?.server)}/devices`);
192
+ return { ok: result.ok, status: result.status, body: result.json };
193
+ };
package/src/args.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const parseArgs = (argv = []) => {
3
+ exports.parseArgs = (argv = []) => {
4
4
  const out = { _: [] };
5
5
  for (let index = 0; index < argv.length; index += 1) {
6
6
  const item = argv[index];
@@ -24,5 +24,3 @@ const parseArgs = (argv = []) => {
24
24
  }
25
25
  return out;
26
26
  };
27
-
28
- module.exports = { parseArgs };