@techsologic/unolock-agent 0.1.42 → 0.1.43
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
CHANGED
|
@@ -87,7 +87,7 @@ Agent-first onboarding site:
|
|
|
87
87
|
Recommended customer install source:
|
|
88
88
|
|
|
89
89
|
* UnoLock's built-in local runtime/CLI with a GitHub Release binary when available
|
|
90
|
-
* `
|
|
90
|
+
* `npm install -g @techsologic/unolock-agent` as the primary npm install path
|
|
91
91
|
* `pipx install` as the fallback source install path when no release binary is available yet
|
|
92
92
|
|
|
93
93
|
If you are new to UnoLock itself, start with these docs first:
|
|
@@ -144,7 +144,11 @@ For host configuration and implementation details, see:
|
|
|
144
144
|
* [OpenClaw plugin config example](examples/openclaw-plugin-config.json)
|
|
145
145
|
|
|
146
146
|
For skill-aware agents, start with the skill above.
|
|
147
|
-
For direct agent use,
|
|
147
|
+
For direct agent use, install the CLI once, then run `unolock-agent` directly.
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
npm install -g @techsologic/unolock-agent
|
|
151
|
+
```
|
|
148
152
|
|
|
149
153
|
```bash
|
|
150
154
|
unolock-agent register 'https://safe.example/#/agent-register/...' 1
|
|
@@ -153,15 +157,9 @@ unolock-agent list-notes
|
|
|
153
157
|
unolock-agent list-files
|
|
154
158
|
```
|
|
155
159
|
|
|
156
|
-
Only if the executable is not installed yet, use the same commands through:
|
|
157
|
-
|
|
158
|
-
```bash
|
|
159
|
-
npx -y @techsologic/unolock-agent@latest register 'https://safe.example/#/agent-register/...' 1
|
|
160
|
-
```
|
|
161
|
-
|
|
162
160
|
Only if a host needs the explicit host-command form, use:
|
|
163
161
|
|
|
164
|
-
* `
|
|
162
|
+
* `unolock-agent mcp`
|
|
165
163
|
* The host writes JSON-RPC to `stdin` and reads JSON-RPC from `stdout`.
|
|
166
164
|
* The `mcp` subcommand starts and uses UnoLock automatically.
|
|
167
165
|
* On a fresh host, the first start can take longer because local cryptographic code may need to be compiled or prepared.
|
|
@@ -214,7 +212,7 @@ For the best customer experience, prefer GitHub Release binaries over source ins
|
|
|
214
212
|
|
|
215
213
|
## Preferred Customer Install
|
|
216
214
|
|
|
217
|
-
When available, prefer the built-in UnoLock local runtime plus
|
|
215
|
+
When available, prefer the built-in UnoLock local runtime plus either a global npm install or a release binary.
|
|
218
216
|
|
|
219
217
|
For an agent-first public onboarding flow, send users or agents to:
|
|
220
218
|
|
|
@@ -224,16 +222,13 @@ When available, prefer the standalone GitHub Release binaries instead of install
|
|
|
224
222
|
|
|
225
223
|
That avoids most of the Python packaging and source-build overhead for customers.
|
|
226
224
|
|
|
227
|
-
If your host environment is already Node/npm-oriented,
|
|
225
|
+
If your host environment is already Node/npm-oriented, install the CLI globally:
|
|
228
226
|
|
|
229
227
|
```bash
|
|
230
|
-
|
|
228
|
+
npm install -g @techsologic/unolock-agent
|
|
229
|
+
unolock-agent --version
|
|
231
230
|
```
|
|
232
231
|
|
|
233
|
-
The wrapper downloads the correct GitHub Release binary for the current platform on first use and then reuses the cached copy.
|
|
234
|
-
|
|
235
|
-
On restart, the npm wrapper now checks GitHub Releases for a newer stable binary and will update its cached binary between tasks when a newer release is available.
|
|
236
|
-
|
|
237
232
|
The npm package is both:
|
|
238
233
|
|
|
239
234
|
* the normal UnoLock executable package
|
|
@@ -246,13 +241,13 @@ Project home:
|
|
|
246
241
|
Use it as a command that OpenClaw can launch, for example:
|
|
247
242
|
|
|
248
243
|
```bash
|
|
249
|
-
|
|
244
|
+
unolock-agent mcp
|
|
250
245
|
```
|
|
251
246
|
|
|
252
247
|
For hosts that require the command form, use the explicit `mcp` argument:
|
|
253
248
|
|
|
254
249
|
```bash
|
|
255
|
-
|
|
250
|
+
unolock-agent mcp
|
|
256
251
|
```
|
|
257
252
|
|
|
258
253
|
That is the preferred host-facing launch shape.
|
|
@@ -279,10 +274,9 @@ Or, through the MCP itself, call:
|
|
|
279
274
|
|
|
280
275
|
Preferred channel behavior:
|
|
281
276
|
|
|
282
|
-
*
|
|
283
|
-
* preferred low-friction path
|
|
284
|
-
*
|
|
285
|
-
* npm publishing is only needed when the wrapper itself changes
|
|
277
|
+
* global npm install
|
|
278
|
+
* preferred low-friction npm path
|
|
279
|
+
* update with `npm install -g @techsologic/unolock-agent@latest`, then restart UnoLock
|
|
286
280
|
* direct GitHub Release binary
|
|
287
281
|
* replace the binary manually, then restart the UnoLock MCP
|
|
288
282
|
* Python package install
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
PACKAGE_VERSION,
|
|
9
|
+
binaryUrl,
|
|
10
|
+
ensureDir,
|
|
11
|
+
ensureExecutable,
|
|
12
|
+
fetchToFile,
|
|
13
|
+
installedBinaryPath,
|
|
14
|
+
} = require("./unolock-agent-common");
|
|
15
|
+
|
|
16
|
+
async function main() {
|
|
17
|
+
const dest = installedBinaryPath();
|
|
18
|
+
if (fs.existsSync(dest)) {
|
|
19
|
+
ensureExecutable(dest);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
ensureDir(path.dirname(dest));
|
|
23
|
+
process.stderr.write(`Installing UnoLock agent ${PACKAGE_VERSION} for ${process.platform}/${process.arch}...\n`);
|
|
24
|
+
await fetchToFile(binaryUrl(PACKAGE_VERSION), dest);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (require.main === module) {
|
|
28
|
+
main().catch((error) => {
|
|
29
|
+
process.stderr.write(`${error.message}\n`);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const https = require("https");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
|
|
8
|
+
const packageJson = require("../package.json");
|
|
9
|
+
|
|
10
|
+
const PACKAGE_VERSION = packageJson.version;
|
|
11
|
+
const REPO = "TechSologic/unolock-agent";
|
|
12
|
+
|
|
13
|
+
function platformAssetInfo() {
|
|
14
|
+
const platform = process.platform;
|
|
15
|
+
const arch = process.arch;
|
|
16
|
+
if (platform === "linux" && arch === "x64") {
|
|
17
|
+
return { asset: "unolock-agent-linux-x86_64", executable: "unolock-agent-linux-x86_64" };
|
|
18
|
+
}
|
|
19
|
+
if (platform === "darwin" && arch === "arm64") {
|
|
20
|
+
return { asset: "unolock-agent-macos-arm64", executable: "unolock-agent-macos-arm64" };
|
|
21
|
+
}
|
|
22
|
+
if (platform === "darwin" && arch === "x64") {
|
|
23
|
+
return { asset: "unolock-agent-macos-x86_64", executable: "unolock-agent-macos-x86_64" };
|
|
24
|
+
}
|
|
25
|
+
if (platform === "win32" && arch === "x64") {
|
|
26
|
+
return { asset: "unolock-agent-windows-amd64.exe", executable: "unolock-agent-windows-amd64.exe" };
|
|
27
|
+
}
|
|
28
|
+
throw new Error(`Unsupported platform for UnoLock agent binary: ${platform}/${arch}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function installRoot() {
|
|
32
|
+
return path.join(__dirname, "..", "vendor");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function installedBinaryPath() {
|
|
36
|
+
const { executable } = platformAssetInfo();
|
|
37
|
+
return path.join(installRoot(), executable);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function binaryUrl(releaseVersion = PACKAGE_VERSION) {
|
|
41
|
+
if (process.env.UNOLOCK_AGENT_BINARY_URL) {
|
|
42
|
+
return process.env.UNOLOCK_AGENT_BINARY_URL;
|
|
43
|
+
}
|
|
44
|
+
const { asset } = platformAssetInfo();
|
|
45
|
+
return `https://github.com/${REPO}/releases/download/v${releaseVersion}/${asset}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function ensureDir(dir) {
|
|
49
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o755 });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function ensureExecutable(dest) {
|
|
53
|
+
if (process.platform !== "win32" && fs.existsSync(dest)) {
|
|
54
|
+
fs.chmodSync(dest, 0o755);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function fetchToFile(url, dest) {
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
const temp = `${dest}.download`;
|
|
61
|
+
const request = https.get(url, (response) => {
|
|
62
|
+
if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
|
63
|
+
response.resume();
|
|
64
|
+
fetchToFile(response.headers.location, dest).then(resolve, reject);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (response.statusCode !== 200) {
|
|
68
|
+
response.resume();
|
|
69
|
+
reject(new Error(`Failed to download UnoLock agent binary: HTTP ${response.statusCode}`));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const file = fs.createWriteStream(temp, { mode: 0o755 });
|
|
73
|
+
response.pipe(file);
|
|
74
|
+
file.on("finish", () => {
|
|
75
|
+
file.close((closeErr) => {
|
|
76
|
+
if (closeErr) {
|
|
77
|
+
reject(closeErr);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
fs.renameSync(temp, dest);
|
|
81
|
+
ensureExecutable(dest);
|
|
82
|
+
resolve();
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
file.on("error", (error) => {
|
|
86
|
+
file.close(() => {
|
|
87
|
+
try {
|
|
88
|
+
fs.unlinkSync(temp);
|
|
89
|
+
} catch {}
|
|
90
|
+
reject(error);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
request.on("error", reject);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = {
|
|
99
|
+
PACKAGE_VERSION,
|
|
100
|
+
binaryUrl,
|
|
101
|
+
ensureDir,
|
|
102
|
+
ensureExecutable,
|
|
103
|
+
fetchToFile,
|
|
104
|
+
installRoot,
|
|
105
|
+
installedBinaryPath,
|
|
106
|
+
platformAssetInfo,
|
|
107
|
+
};
|
package/bin/unolock-agent.js
CHANGED
|
@@ -2,17 +2,14 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
const fs = require("fs");
|
|
5
|
-
const os = require("os");
|
|
6
|
-
const path = require("path");
|
|
7
|
-
const https = require("https");
|
|
8
5
|
const { spawn } = require("child_process");
|
|
9
6
|
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
const {
|
|
8
|
+
PACKAGE_VERSION,
|
|
9
|
+
ensureExecutable,
|
|
10
|
+
installedBinaryPath,
|
|
11
|
+
} = require("./unolock-agent-common");
|
|
12
|
+
|
|
16
13
|
const TOP_LEVEL_USAGE = `usage: unolock-agent [-h] [--version] {register,set-agent-pin,list-spaces,get-current-space,set-current-space,list-records,list-notes,list-checklists,get-record,create-note,update-note,append-note,rename-record,create-checklist,set-checklist-item-done,add-checklist-item,remove-checklist-item,list-files,get-file,download-file,upload-file,rename-file,replace-file,delete-file,tpm-diagnose,tpm-check,self-test,mcp} ...
|
|
17
14
|
|
|
18
15
|
UnoLock Agent commands.
|
|
@@ -53,317 +50,11 @@ options:
|
|
|
53
50
|
--version show program's version number and exit
|
|
54
51
|
`;
|
|
55
52
|
|
|
56
|
-
function
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return { asset: "unolock-agent-linux-x86_64", executable: "unolock-agent-linux-x86_64" };
|
|
61
|
-
}
|
|
62
|
-
if (platform === "darwin" && arch === "arm64") {
|
|
63
|
-
return { asset: "unolock-agent-macos-arm64", executable: "unolock-agent-macos-arm64" };
|
|
64
|
-
}
|
|
65
|
-
if (platform === "darwin" && arch === "x64") {
|
|
66
|
-
return { asset: "unolock-agent-macos-x86_64", executable: "unolock-agent-macos-x86_64" };
|
|
67
|
-
}
|
|
68
|
-
if (platform === "win32" && arch === "x64") {
|
|
69
|
-
return { asset: "unolock-agent-windows-amd64.exe", executable: "unolock-agent-windows-amd64.exe" };
|
|
70
|
-
}
|
|
71
|
-
throw new Error(`Unsupported platform for UnoLock agent binary: ${platform}/${arch}`);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function cacheRoot() {
|
|
75
|
-
if (process.platform === "win32") {
|
|
76
|
-
return process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local");
|
|
77
|
-
}
|
|
78
|
-
return process.env.XDG_CACHE_HOME || path.join(os.homedir(), ".cache");
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function metadataPath() {
|
|
82
|
-
return path.join(cacheRoot(), "unolock-agent", "release.json");
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function installLockPath() {
|
|
86
|
-
return path.join(cacheRoot(), "unolock-agent", "install.lock");
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function binaryPath(releaseVersion) {
|
|
90
|
-
const { executable } = platformAssetInfo();
|
|
91
|
-
return path.join(cacheRoot(), "unolock-agent", releaseVersion, executable);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function binaryUrl(releaseVersion) {
|
|
95
|
-
if (process.env.UNOLOCK_AGENT_BINARY_URL) {
|
|
96
|
-
return process.env.UNOLOCK_AGENT_BINARY_URL;
|
|
97
|
-
}
|
|
98
|
-
const { asset } = platformAssetInfo();
|
|
99
|
-
return `https://github.com/${REPO}/releases/download/v${releaseVersion}/${asset}`;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function ensureDir(dir) {
|
|
103
|
-
fs.mkdirSync(dir, { recursive: true, mode: 0o755 });
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function sleep(ms) {
|
|
107
|
-
return new Promise((resolve) => {
|
|
108
|
-
setTimeout(resolve, ms);
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function fetchToFile(url, dest) {
|
|
113
|
-
return new Promise((resolve, reject) => {
|
|
114
|
-
const temp = `${dest}.download`;
|
|
115
|
-
const request = https.get(url, (response) => {
|
|
116
|
-
if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
|
117
|
-
response.resume();
|
|
118
|
-
fetchToFile(response.headers.location, dest).then(resolve, reject);
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
if (response.statusCode !== 200) {
|
|
122
|
-
response.resume();
|
|
123
|
-
reject(new Error(`Failed to download UnoLock agent binary: HTTP ${response.statusCode}`));
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
const file = fs.createWriteStream(temp, { mode: 0o755 });
|
|
127
|
-
response.pipe(file);
|
|
128
|
-
file.on("finish", () => {
|
|
129
|
-
file.close((closeErr) => {
|
|
130
|
-
if (closeErr) {
|
|
131
|
-
reject(closeErr);
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
fs.renameSync(temp, dest);
|
|
135
|
-
if (process.platform !== "win32") {
|
|
136
|
-
fs.chmodSync(dest, 0o755);
|
|
137
|
-
}
|
|
138
|
-
resolve();
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
file.on("error", (error) => {
|
|
142
|
-
file.close(() => {
|
|
143
|
-
try {
|
|
144
|
-
fs.unlinkSync(temp);
|
|
145
|
-
} catch {}
|
|
146
|
-
reject(error);
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
request.on("error", reject);
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function fetchJson(url) {
|
|
155
|
-
return new Promise((resolve, reject) => {
|
|
156
|
-
const request = https.get(
|
|
157
|
-
url,
|
|
158
|
-
{
|
|
159
|
-
headers: {
|
|
160
|
-
"Accept": "application/vnd.github+json",
|
|
161
|
-
"User-Agent": "unolock-agent-npm-wrapper"
|
|
162
|
-
}
|
|
163
|
-
},
|
|
164
|
-
(response) => {
|
|
165
|
-
if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
|
166
|
-
response.resume();
|
|
167
|
-
fetchJson(response.headers.location).then(resolve, reject);
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
if (response.statusCode !== 200) {
|
|
171
|
-
response.resume();
|
|
172
|
-
reject(new Error(`Failed to query UnoLock agent latest release: HTTP ${response.statusCode}`));
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
let body = "";
|
|
176
|
-
response.setEncoding("utf8");
|
|
177
|
-
response.on("data", (chunk) => {
|
|
178
|
-
body += chunk;
|
|
179
|
-
});
|
|
180
|
-
response.on("end", () => {
|
|
181
|
-
try {
|
|
182
|
-
resolve(JSON.parse(body));
|
|
183
|
-
} catch (error) {
|
|
184
|
-
reject(error);
|
|
185
|
-
}
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
);
|
|
189
|
-
request.on("error", reject);
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function normalizeVersion(value) {
|
|
194
|
-
if (!value || typeof value !== "string") {
|
|
195
|
-
return null;
|
|
196
|
-
}
|
|
197
|
-
const trimmed = value.trim();
|
|
198
|
-
if (!trimmed) {
|
|
199
|
-
return null;
|
|
200
|
-
}
|
|
201
|
-
return trimmed.startsWith("v") ? trimmed.slice(1) : trimmed;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function compareVersions(left, right) {
|
|
205
|
-
const leftParts = String(left || "").split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
206
|
-
const rightParts = String(right || "").split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
207
|
-
const length = Math.max(leftParts.length, rightParts.length);
|
|
208
|
-
for (let index = 0; index < length; index += 1) {
|
|
209
|
-
const leftValue = leftParts[index] || 0;
|
|
210
|
-
const rightValue = rightParts[index] || 0;
|
|
211
|
-
if (leftValue > rightValue) {
|
|
212
|
-
return 1;
|
|
213
|
-
}
|
|
214
|
-
if (leftValue < rightValue) {
|
|
215
|
-
return -1;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
return 0;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function readReleaseMetadata() {
|
|
222
|
-
try {
|
|
223
|
-
return JSON.parse(fs.readFileSync(metadataPath(), "utf8"));
|
|
224
|
-
} catch {
|
|
225
|
-
return null;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function writeReleaseMetadata(releaseVersion) {
|
|
230
|
-
ensureDir(path.dirname(metadataPath()));
|
|
231
|
-
const dest = metadataPath();
|
|
232
|
-
const temp = `${dest}.tmp-${process.pid}`;
|
|
233
|
-
fs.writeFileSync(
|
|
234
|
-
temp,
|
|
235
|
-
JSON.stringify(
|
|
236
|
-
{
|
|
237
|
-
releaseVersion,
|
|
238
|
-
checkedAt: Date.now()
|
|
239
|
-
},
|
|
240
|
-
null,
|
|
241
|
-
2
|
|
242
|
-
),
|
|
243
|
-
"utf8"
|
|
53
|
+
function installedBinaryError() {
|
|
54
|
+
return (
|
|
55
|
+
"UnoLock Agent is not installed correctly. Reinstall it with `npm install -g @techsologic/unolock-agent` " +
|
|
56
|
+
"or use a GitHub release binary."
|
|
244
57
|
);
|
|
245
|
-
fs.renameSync(temp, dest);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
function cachedReleaseVersion() {
|
|
249
|
-
const override = normalizeVersion(process.env.UNOLOCK_AGENT_BINARY_VERSION);
|
|
250
|
-
if (override) {
|
|
251
|
-
return override;
|
|
252
|
-
}
|
|
253
|
-
const metadata = readReleaseMetadata();
|
|
254
|
-
const cached = metadata && typeof metadata.releaseVersion === "string" ? normalizeVersion(metadata.releaseVersion) : null;
|
|
255
|
-
if (cached && fs.existsSync(binaryPath(cached))) {
|
|
256
|
-
if (compareVersions(cached, FALLBACK_BINARY_VERSION) >= 0) {
|
|
257
|
-
return cached;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
if (fs.existsSync(binaryPath(FALLBACK_BINARY_VERSION))) {
|
|
261
|
-
return FALLBACK_BINARY_VERSION;
|
|
262
|
-
}
|
|
263
|
-
return null;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
async function resolveReleaseVersion() {
|
|
267
|
-
const cached = cachedReleaseVersion();
|
|
268
|
-
if (cached) {
|
|
269
|
-
return cached;
|
|
270
|
-
}
|
|
271
|
-
try {
|
|
272
|
-
const payload = await fetchJson(`https://api.github.com/repos/${REPO}/releases/latest`);
|
|
273
|
-
const latest = normalizeVersion(payload && payload.tag_name);
|
|
274
|
-
if (latest) {
|
|
275
|
-
writeReleaseMetadata(latest);
|
|
276
|
-
return latest;
|
|
277
|
-
}
|
|
278
|
-
} catch (error) {
|
|
279
|
-
process.stderr.write(`Warning: ${error.message}. Falling back to bundled release ${FALLBACK_BINARY_VERSION}.\n`);
|
|
280
|
-
}
|
|
281
|
-
writeReleaseMetadata(FALLBACK_BINARY_VERSION);
|
|
282
|
-
return FALLBACK_BINARY_VERSION;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
function removeIfStale(lockPath) {
|
|
286
|
-
let stats;
|
|
287
|
-
try {
|
|
288
|
-
stats = fs.statSync(lockPath);
|
|
289
|
-
} catch (error) {
|
|
290
|
-
if (error && error.code === "ENOENT") {
|
|
291
|
-
return false;
|
|
292
|
-
}
|
|
293
|
-
throw error;
|
|
294
|
-
}
|
|
295
|
-
if (Date.now() - stats.mtimeMs < INSTALL_LOCK_STALE_MS) {
|
|
296
|
-
return false;
|
|
297
|
-
}
|
|
298
|
-
try {
|
|
299
|
-
fs.unlinkSync(lockPath);
|
|
300
|
-
return true;
|
|
301
|
-
} catch (error) {
|
|
302
|
-
if (error && error.code === "ENOENT") {
|
|
303
|
-
return true;
|
|
304
|
-
}
|
|
305
|
-
if (error && error.code === "EPERM") {
|
|
306
|
-
return false;
|
|
307
|
-
}
|
|
308
|
-
throw error;
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
async function acquireInstallLock() {
|
|
313
|
-
const lockPath = installLockPath();
|
|
314
|
-
ensureDir(path.dirname(lockPath));
|
|
315
|
-
const deadline = Date.now() + INSTALL_LOCK_TIMEOUT_MS;
|
|
316
|
-
while (true) {
|
|
317
|
-
try {
|
|
318
|
-
const fd = fs.openSync(lockPath, "wx", 0o600);
|
|
319
|
-
fs.writeFileSync(
|
|
320
|
-
fd,
|
|
321
|
-
JSON.stringify({ pid: process.pid, createdAt: Date.now() }),
|
|
322
|
-
"utf8"
|
|
323
|
-
);
|
|
324
|
-
return () => {
|
|
325
|
-
try {
|
|
326
|
-
fs.closeSync(fd);
|
|
327
|
-
} catch {}
|
|
328
|
-
try {
|
|
329
|
-
fs.unlinkSync(lockPath);
|
|
330
|
-
} catch {}
|
|
331
|
-
};
|
|
332
|
-
} catch (error) {
|
|
333
|
-
if (!error || error.code !== "EEXIST") {
|
|
334
|
-
throw error;
|
|
335
|
-
}
|
|
336
|
-
removeIfStale(lockPath);
|
|
337
|
-
if (Date.now() >= deadline) {
|
|
338
|
-
throw new Error("Timed out waiting for the UnoLock agent install lock");
|
|
339
|
-
}
|
|
340
|
-
await sleep(INSTALL_LOCK_POLL_MS);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
async function ensureBinary() {
|
|
346
|
-
const cached = cachedReleaseVersion();
|
|
347
|
-
if (cached) {
|
|
348
|
-
const dest = binaryPath(cached);
|
|
349
|
-
if (fs.existsSync(dest)) {
|
|
350
|
-
return { dest, releaseVersion: cached };
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
const releaseLock = await acquireInstallLock();
|
|
354
|
-
try {
|
|
355
|
-
const releaseVersion = await resolveReleaseVersion();
|
|
356
|
-
const dest = binaryPath(releaseVersion);
|
|
357
|
-
if (fs.existsSync(dest)) {
|
|
358
|
-
return { dest, releaseVersion };
|
|
359
|
-
}
|
|
360
|
-
ensureDir(path.dirname(dest));
|
|
361
|
-
process.stderr.write(`Downloading UnoLock agent ${releaseVersion} for ${process.platform}/${process.arch}...\n`);
|
|
362
|
-
await fetchToFile(binaryUrl(releaseVersion), dest);
|
|
363
|
-
return { dest, releaseVersion };
|
|
364
|
-
} finally {
|
|
365
|
-
releaseLock();
|
|
366
|
-
}
|
|
367
58
|
}
|
|
368
59
|
|
|
369
60
|
async function main() {
|
|
@@ -376,14 +67,19 @@ async function main() {
|
|
|
376
67
|
process.stdout.write(`${PACKAGE_VERSION}\n`);
|
|
377
68
|
return;
|
|
378
69
|
}
|
|
379
|
-
|
|
70
|
+
|
|
71
|
+
const dest = installedBinaryPath();
|
|
72
|
+
if (!fs.existsSync(dest)) {
|
|
73
|
+
throw new Error(installedBinaryError());
|
|
74
|
+
}
|
|
75
|
+
ensureExecutable(dest);
|
|
76
|
+
|
|
380
77
|
const child = spawn(dest, forwardedArgs, {
|
|
381
78
|
stdio: "inherit",
|
|
382
79
|
env: {
|
|
383
80
|
...process.env,
|
|
384
|
-
UNOLOCK_AGENT_INSTALL_CHANNEL: "npm-
|
|
385
|
-
|
|
386
|
-
UNOLOCK_AGENT_BINARY_VERSION: releaseVersion
|
|
81
|
+
UNOLOCK_AGENT_INSTALL_CHANNEL: "npm-install",
|
|
82
|
+
UNOLOCK_AGENT_BINARY_VERSION: PACKAGE_VERSION,
|
|
387
83
|
}
|
|
388
84
|
});
|
|
389
85
|
child.on("exit", (code, signal) => {
|
|
@@ -406,12 +102,10 @@ if (require.main === module) {
|
|
|
406
102
|
});
|
|
407
103
|
} else {
|
|
408
104
|
module.exports = {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
sleep,
|
|
415
|
-
writeReleaseMetadata,
|
|
105
|
+
PACKAGE_VERSION,
|
|
106
|
+
TOP_LEVEL_USAGE,
|
|
107
|
+
installedBinaryError,
|
|
108
|
+
installedBinaryPath,
|
|
109
|
+
main,
|
|
416
110
|
};
|
|
417
111
|
}
|
|
@@ -44,7 +44,7 @@ Choose the object that matches the work:
|
|
|
44
44
|
|
|
45
45
|
## Preferred Workflow
|
|
46
46
|
|
|
47
|
-
1.
|
|
47
|
+
1. Install the UnoLock Agent CLI with `npm install -g @techsologic/unolock-agent`, then run `unolock-agent` directly.
|
|
48
48
|
1.5. Run `unolock-agent` without arguments to get a usage statement when needed.
|
|
49
49
|
2. Run the `unolock-agent` command you need directly.
|
|
50
50
|
3. On a fresh host, allow extra time for the first start.
|
|
@@ -82,7 +82,7 @@ Choose the object that matches the work:
|
|
|
82
82
|
- if a command reports that the PIN is needed, run `unolock-agent set-agent-pin '<pin>'` and retry the original command
|
|
83
83
|
- if unsure which command to use next, run `unolock-agent --help`
|
|
84
84
|
- keep the PIN in UnoLock process memory only
|
|
85
|
-
-
|
|
85
|
+
- install the UnoLock Agent CLI with `npm install -g @techsologic/unolock-agent`
|
|
86
86
|
|
|
87
87
|
## User-Facing Model
|
|
88
88
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@techsologic/unolock-agent",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.43",
|
|
4
|
+
"description": "UnoLock Agent CLI and local runtime",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"homepage": "https://unolock.ai",
|
|
7
7
|
"repository": {
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
"bin": {
|
|
15
15
|
"unolock-agent": "bin/unolock-agent.js"
|
|
16
16
|
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"postinstall": "node bin/install-binary.js"
|
|
19
|
+
},
|
|
17
20
|
"openclaw": {
|
|
18
21
|
"extensions": [
|
|
19
22
|
"./openclaw-plugin/index.js"
|
|
@@ -45,7 +45,7 @@ Choose the object that matches the work:
|
|
|
45
45
|
## Preferred Workflow
|
|
46
46
|
|
|
47
47
|
1. Load this skill if the host supports skills.
|
|
48
|
-
2.
|
|
48
|
+
2. Install the UnoLock Agent CLI with `npm install -g @techsologic/unolock-agent`, then run `unolock-agent` directly.
|
|
49
49
|
3. Run `unolock-agent` without arguments to get a usage statement when needed.
|
|
50
50
|
4. Run the `unolock-agent` command you need directly.
|
|
51
51
|
5. On a fresh host, allow extra time for the first start.
|
|
@@ -83,7 +83,7 @@ Choose the object that matches the work:
|
|
|
83
83
|
- if a command reports that the PIN is needed, run `unolock-agent set-agent-pin '<pin>'` and retry the original command
|
|
84
84
|
- if unsure which command to use next, run `unolock-agent --help`
|
|
85
85
|
- keep the PIN in UnoLock process memory only
|
|
86
|
-
-
|
|
86
|
+
- install the UnoLock Agent CLI with `npm install -g @techsologic/unolock-agent`
|
|
87
87
|
|
|
88
88
|
## User-Facing Model
|
|
89
89
|
|