@miruzero/zero-agent 0.3.0 → 0.3.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/README.md +13 -4
- package/package.json +1 -1
- package/src/autostart.js +84 -0
- package/src/cli.js +43 -3
package/README.md
CHANGED
|
@@ -2,11 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
Local worker agent for Zero MCP multi-device execution.
|
|
4
4
|
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
```powershell
|
|
8
|
+
npx @miruzero/zero-agent@latest start --once
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
On first run, `start` creates local config, a launcher, and a Windows user-logon autostart file automatically.
|
|
12
|
+
|
|
5
13
|
## Commands
|
|
6
14
|
|
|
7
15
|
```powershell
|
|
8
|
-
zero-agent
|
|
9
|
-
zero-agent pair --server https://zero.miru.work --code ZERO-XXXX
|
|
16
|
+
zero-agent setup --name TON --code ZERO-XXXX
|
|
10
17
|
zero-agent start --once
|
|
11
18
|
zero-agent start
|
|
12
19
|
zero-agent status
|
|
@@ -16,15 +23,17 @@ zero-agent tools
|
|
|
16
23
|
zero-agent capabilities
|
|
17
24
|
```
|
|
18
25
|
|
|
19
|
-
##
|
|
26
|
+
## Autostart
|
|
20
27
|
|
|
21
28
|
Default state path:
|
|
22
29
|
|
|
23
30
|
```text
|
|
24
31
|
%USERPROFILE%\.zero-agent\device.json
|
|
32
|
+
%USERPROFILE%\.zero-agent\start-zero-agent.cmd
|
|
33
|
+
%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\MiruZeroAgent.vbs
|
|
25
34
|
```
|
|
26
35
|
|
|
27
|
-
|
|
36
|
+
The launcher uses `npx -y @miruzero/zero-agent@<current-version> start` so the user-logon agent does not depend on a stale global install.
|
|
28
37
|
|
|
29
38
|
Device tokens are local secrets and must not be printed or committed.
|
|
30
39
|
|
package/package.json
CHANGED
package/src/autostart.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const os = require('node:os');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const config = require('./config');
|
|
7
|
+
const pkg = require('../package.json');
|
|
8
|
+
|
|
9
|
+
const DEFAULT_INTERVAL = 30000;
|
|
10
|
+
|
|
11
|
+
const q = (value) => '"' + String(value).replace(/"/g, '""') + '"';
|
|
12
|
+
const winPath = (value) => String(value).replace(/\//g, '\\');
|
|
13
|
+
|
|
14
|
+
const defaultRoots = (env = process.env) => {
|
|
15
|
+
if (env.ZERO_AGENT_ROOTS) return env.ZERO_AGENT_ROOTS;
|
|
16
|
+
const home = env.USERPROFILE || os.homedir();
|
|
17
|
+
return [
|
|
18
|
+
path.join(home, 'Desktop', 'zero'),
|
|
19
|
+
path.join(home, 'Downloads')
|
|
20
|
+
].join(path.delimiter);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const startupDir = (env = process.env) => {
|
|
24
|
+
const appData = env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
|
|
25
|
+
return path.join(appData, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup');
|
|
26
|
+
};
|
|
27
|
+
const paths = (env = process.env) => {
|
|
28
|
+
const home = config.agentHome(env);
|
|
29
|
+
return {
|
|
30
|
+
home,
|
|
31
|
+
launcherFile: path.join(home, 'start-zero-agent.cmd'),
|
|
32
|
+
startupFile: path.join(startupDir(env), 'MiruZeroAgent.vbs'),
|
|
33
|
+
logFile: path.join(config.logDir(env), 'zero-agent.log')
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const packageSpec = () => pkg.name + '@' + pkg.version;
|
|
38
|
+
|
|
39
|
+
const launcherContent = ({ interval = DEFAULT_INTERVAL, roots, logFile }, env = process.env) => {
|
|
40
|
+
const home = config.agentHome(env);
|
|
41
|
+
const spec = packageSpec();
|
|
42
|
+
return [
|
|
43
|
+
'@echo off',
|
|
44
|
+
'setlocal',
|
|
45
|
+
'set "ZERO_TRACE=1"',
|
|
46
|
+
'set "ZERO_AGENT_HOME=' + winPath(home) + '"',
|
|
47
|
+
'set "ZERO_AGENT_ROOTS=' + winPath(roots) + '"',
|
|
48
|
+
'set "ZERO_AGENT_INTERVAL=' + Number(interval || DEFAULT_INTERVAL) + '"',
|
|
49
|
+
'rem interval=' + Number(interval || DEFAULT_INTERVAL) + 'ms',
|
|
50
|
+
'if not exist ' + q(winPath(path.dirname(logFile))) + ' mkdir ' + q(winPath(path.dirname(logFile))),
|
|
51
|
+
'echo [%date% %time%] starting ' + spec + ' >> ' + q(winPath(logFile)),
|
|
52
|
+
'npx -y ' + spec + ' start --interval %ZERO_AGENT_INTERVAL% >> ' + q(winPath(logFile)) + ' 2>&1',
|
|
53
|
+
''
|
|
54
|
+
].join('\r\n');
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const vbsContent = (launcherFile) => [
|
|
58
|
+
'Set WshShell = CreateObject("WScript.Shell")',
|
|
59
|
+
'WshShell.Run Chr(34) & "' + winPath(launcherFile).replace(/"/g, '""') + '" & Chr(34), 0, False',
|
|
60
|
+
''
|
|
61
|
+
].join('\r\n');
|
|
62
|
+
|
|
63
|
+
const prepare = (options = {}, env = process.env) => {
|
|
64
|
+
config.ensureDirs(env);
|
|
65
|
+
const p = paths(env);
|
|
66
|
+
const interval = Number(options.interval || env.ZERO_AGENT_INTERVAL || DEFAULT_INTERVAL);
|
|
67
|
+
const roots = options.roots || defaultRoots(env);
|
|
68
|
+
fs.mkdirSync(path.dirname(p.launcherFile), { recursive: true });
|
|
69
|
+
fs.mkdirSync(path.dirname(p.startupFile), { recursive: true });
|
|
70
|
+
fs.mkdirSync(path.dirname(p.logFile), { recursive: true });
|
|
71
|
+
fs.writeFileSync(p.launcherFile, launcherContent({ interval, roots, logFile: p.logFile }, env), 'utf8');
|
|
72
|
+
fs.writeFileSync(p.startupFile, vbsContent(p.launcherFile), 'utf8');
|
|
73
|
+
return { ok: true, interval, roots, ...p };
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const autostart = {
|
|
77
|
+
DEFAULT_INTERVAL,
|
|
78
|
+
defaultRoots,
|
|
79
|
+
paths,
|
|
80
|
+
prepare,
|
|
81
|
+
launcherContent
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
Object.assign(exports, autostart);
|
package/src/cli.js
CHANGED
|
@@ -4,6 +4,7 @@ const childProcess = require('node:child_process');
|
|
|
4
4
|
const os = require('node:os');
|
|
5
5
|
const pkg = require('../package.json');
|
|
6
6
|
const config = require('./config');
|
|
7
|
+
const autostart = require('./autostart');
|
|
7
8
|
const caps = require('./capabilities');
|
|
8
9
|
const agent = require('./agent');
|
|
9
10
|
const { parseArgs } = require('./args');
|
|
@@ -14,7 +15,7 @@ Usage:
|
|
|
14
15
|
zero-agent setup [--server <url>] [--name <device>] [--code <pairing-code>] [--no-global]
|
|
15
16
|
zero-agent init --server <url> [--name <device>]
|
|
16
17
|
zero-agent pair --server <url> --code <pairing-code> [--name <device>]
|
|
17
|
-
zero-agent start [--once] [--interval <ms>]
|
|
18
|
+
zero-agent start [--once] [--interval <ms>] [--code <pairing-code>] [--name <device>]
|
|
18
19
|
zero-agent status
|
|
19
20
|
zero-agent devices
|
|
20
21
|
zero-agent doctor
|
|
@@ -27,6 +28,42 @@ const printJson = (value) => console.log(JSON.stringify(value, null, 2));
|
|
|
27
28
|
const okMark = (ok) => ok ? 'OK' : 'FAIL';
|
|
28
29
|
const DEFAULT_SERVER = 'https://zero.miru.work';
|
|
29
30
|
|
|
31
|
+
const autostartInterval = (parsed) => Number(parsed.interval || autostart.DEFAULT_INTERVAL);
|
|
32
|
+
|
|
33
|
+
const printAutostartInfo = (prepared) => {
|
|
34
|
+
console.log('Autostart file');
|
|
35
|
+
console.log(prepared.startupFile);
|
|
36
|
+
console.log('ตัวนี้จะรันตอน user logon และเปิด:');
|
|
37
|
+
console.log(prepared.launcherFile);
|
|
38
|
+
console.log('Launcher');
|
|
39
|
+
console.log(prepared.launcherFile);
|
|
40
|
+
console.log('ตั้งค่า:');
|
|
41
|
+
console.log('ZERO_TRACE=1');
|
|
42
|
+
console.log('ZERO_AGENT_ROOTS=' + prepared.roots);
|
|
43
|
+
console.log('interval=' + prepared.interval + 'ms');
|
|
44
|
+
console.log('log=' + prepared.logFile);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const ensureLocalBootstrap = async (parsed, env) => {
|
|
48
|
+
let device = config.loadDevice(env);
|
|
49
|
+
if (!device) {
|
|
50
|
+
const init = agent.initDevice({ name: parsed.name, server: parsed.server || DEFAULT_SERVER }, env);
|
|
51
|
+
console.log('autoInit=OK file=' + init.file);
|
|
52
|
+
device = config.loadDevice(env);
|
|
53
|
+
}
|
|
54
|
+
const prepared = autostart.prepare({ interval: autostartInterval(parsed) }, env);
|
|
55
|
+
console.log('autoSetup=OK launcher=' + prepared.launcherFile);
|
|
56
|
+
if (parsed.code && (!device || !device.device_id)) {
|
|
57
|
+
const paired = await agent.pairDevice({
|
|
58
|
+
name: parsed.name || device?.name,
|
|
59
|
+
server: parsed.server || device?.server || DEFAULT_SERVER,
|
|
60
|
+
code: parsed.code
|
|
61
|
+
}, env);
|
|
62
|
+
console.log('autoPair=OK file=' + paired.file);
|
|
63
|
+
}
|
|
64
|
+
return prepared;
|
|
65
|
+
};
|
|
66
|
+
|
|
30
67
|
const runGlobalInstall = (parsed, env) => {
|
|
31
68
|
if (parsed['no-global'] || env.ZERO_AGENT_SKIP_GLOBAL_INSTALL === '1') {
|
|
32
69
|
console.log('globalInstall=SKIPPED');
|
|
@@ -53,6 +90,8 @@ const runSetup = async (parsed, env) => {
|
|
|
53
90
|
runGlobalInstall(parsed, env);
|
|
54
91
|
const init = agent.initDevice({ name, server }, env);
|
|
55
92
|
console.log('init=OK file=' + init.file);
|
|
93
|
+
const prepared = autostart.prepare({ interval: autostartInterval(parsed) }, env);
|
|
94
|
+
printAutostartInfo(prepared);
|
|
56
95
|
if (!parsed.code) {
|
|
57
96
|
console.log('pairing=NEEDS_CODE');
|
|
58
97
|
console.log('Next: zero-agent pair --server ' + server + ' --code ZERO-XXXXXX --name ' + init.device.name);
|
|
@@ -122,7 +161,7 @@ const printLoopResult = (result) => {
|
|
|
122
161
|
status: device?.status || null,
|
|
123
162
|
name: device?.name || null,
|
|
124
163
|
host: device?.host || null,
|
|
125
|
-
providers: device?.provider_count
|
|
164
|
+
providers: device?.provider_count ?? null,
|
|
126
165
|
last_seen: device?.last_seen || null,
|
|
127
166
|
task: taskState
|
|
128
167
|
}));
|
|
@@ -176,11 +215,12 @@ async function run(argv = [], env = process.env) {
|
|
|
176
215
|
return 0;
|
|
177
216
|
}
|
|
178
217
|
if (command === 'start') {
|
|
218
|
+
await ensureLocalBootstrap(parsed, env);
|
|
179
219
|
if (parsed.once) {
|
|
180
220
|
printJson(await runStartOnce(env));
|
|
181
221
|
return 0;
|
|
182
222
|
}
|
|
183
|
-
const interval = Number(parsed.interval ||
|
|
223
|
+
const interval = Number(parsed.interval || autostart.DEFAULT_INTERVAL);
|
|
184
224
|
printStartBanner(env);
|
|
185
225
|
console.log('⏱️ Heartbeat interval: ' + interval + 'ms');
|
|
186
226
|
console.log('🟢 Device ready; press Ctrl+C to stop');
|