@techsologic/unolock-agent 0.1.41 → 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
- * `npx -y @techsologic/unolock-agent@latest` as the easiest Node/npm CLI path
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, prefer the CLI. If `unolock-agent` is already installed, use it directly. Only use `npx -y @techsologic/unolock-agent@latest ...` when the executable is not installed yet. Run the command you need directly.
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
- * `npx -y @techsologic/unolock-agent@latest mcp`
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 the npm wrapper or release binary.
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, you can also use the npm wrapper:
225
+ If your host environment is already Node/npm-oriented, install the CLI globally:
228
226
 
229
227
  ```bash
230
- npx -y @techsologic/unolock-agent@latest --version
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
- npx -y @techsologic/unolock-agent@latest mcp
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
- npx -y @techsologic/unolock-agent@latest mcp
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
- * built-in daemon + `npx -y @techsologic/unolock-agent@latest`
283
- * preferred low-friction path
284
- * on restart, the npm wrapper checks GitHub Releases and can fetch the latest stable binary
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
+ };
@@ -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 PACKAGE_VERSION = "0.1.41";
11
- const FALLBACK_BINARY_VERSION = "0.1.41";
12
- const REPO = "TechSologic/unolock-agent";
13
- const INSTALL_LOCK_TIMEOUT_MS = 120000;
14
- const INSTALL_LOCK_STALE_MS = 300000;
15
- const INSTALL_LOCK_POLL_MS = 100;
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 platformAssetInfo() {
57
- const platform = process.platform;
58
- const arch = process.arch;
59
- if (platform === "linux" && arch === "x64") {
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
- const { dest, releaseVersion } = await ensureBinary();
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-wrapper",
385
- UNOLOCK_AGENT_WRAPPER_VERSION: PACKAGE_VERSION,
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
- acquireInstallLock,
410
- compareVersions,
411
- ensureBinary,
412
- installLockPath,
413
- metadataPath,
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. If `unolock-agent` is not installed yet, install the UnoLock Agent CLI with `npx -y @techsologic/unolock-agent@latest`. If it is already installed, run `unolock-agent` directly.
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
- - if `unolock-agent` is not installed yet, install the UnoLock Agent CLI with `npx -y @techsologic/unolock-agent@latest`
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.41",
4
- "description": "npx wrapper for the official UnoLock Agent release binaries",
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. If `unolock-agent` is not installed yet, install the UnoLock Agent CLI with `npx -y @techsologic/unolock-agent@latest`. If it is already installed, run `unolock-agent` directly.
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
- - if `unolock-agent` is not installed yet, install the UnoLock Agent CLI with `npx -y @techsologic/unolock-agent@latest`
86
+ - install the UnoLock Agent CLI with `npm install -g @techsologic/unolock-agent`
87
87
 
88
88
  ## User-Facing Model
89
89