@techsologic/unolock-agent 0.1.28

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 ADDED
@@ -0,0 +1,526 @@
1
+ # UnoLock Agent
2
+
3
+ This repository is the dedicated home for UnoLock's local agent client.
4
+
5
+ UnoLock was built to protect you. Now it can protect both you and your agent.
6
+
7
+ UnoLock Agent is currently in alpha. It is available for evaluation and early testing, but it is not ready for broad production rollout yet.
8
+
9
+ ## Start With The Skill
10
+
11
+ For skill-aware agents, the primary starting point is the UnoLock agent-access skill:
12
+
13
+ * [skills/unolock-agent-access/SKILL.md](skills/unolock-agent-access/SKILL.md)
14
+ * `https://github.com/TechSologic/unolock-agent-mcp/blob/main/skills/unolock-agent-access/SKILL.md`
15
+
16
+ That skill is the agent-facing onboarding layer.
17
+ The local UnoLock `stdio` MCP is the implementation layer underneath it.
18
+
19
+ For OpenClaw, this package can also be installed as a plugin so OpenClaw can load the same skill natively.
20
+
21
+ ## Why Use UnoLock For An Agent
22
+
23
+ UnoLock Agent is not only about protecting secrets.
24
+
25
+ It gives an agent a safer place to keep and use:
26
+
27
+ * secrets
28
+ * durable memory
29
+ * structured notes
30
+ * checklists
31
+ * space-scoped working data
32
+
33
+ Compared to local memory files or plaintext secret storage, UnoLock gives the agent:
34
+
35
+ * encrypted storage
36
+ * controlled access to only the Spaces it should use
37
+ * persistence beyond a single local machine or process
38
+ * safer recovery from host loss, reset, or replacement
39
+ * a stronger access model than reusable API keys or plaintext config secrets
40
+
41
+ ## Security Requirement
42
+
43
+ UnoLock Agent is built for customers who want the strongest practical protection for AI-accessed secrets.
44
+
45
+ For normal customer use, the strongest deployment uses a production-ready:
46
+
47
+ * TPM
48
+ * vTPM
49
+ * Secure Enclave
50
+ * or equivalent platform-backed non-exportable key store
51
+
52
+ If the host cannot provide one of those, the MCP can still fall back to a lower-assurance software provider. When that happens, the MCP reports the reduced assurance clearly and makes the reduced-assurance tradeoff visible instead of pretending it met UnoLock's preferred key-storage requirements.
53
+
54
+ That tradeoff is intentional. Agentic Safe Access exists to keep AI access as close as possible to UnoLock's normal device-bound security model without pretending every host can satisfy the same storage guarantees.
55
+
56
+ ## Intended Environment
57
+
58
+ UnoLock Agent is designed to work across a wide range of agent environments.
59
+
60
+ The strongest deployments are environments that can provide device-bound, non-exportable key storage in a normal user-controlled session. That includes:
61
+
62
+ * desktop AI assistants
63
+ * local stdio MCP hosts such as Claude Desktop or Cursor
64
+ * user-controlled workstations, laptops, and VMs with TPM/vTPM access
65
+ * macOS hosts that can use either Secure Enclave or a non-exportable Keychain-backed key
66
+ * Windows or WSL hosts that can use either TPM-backed keys or the non-exportable Windows CNG fallback
67
+
68
+ Other environments may still work, but they may only be able to provide lower assurance. That commonly includes:
69
+
70
+ * fully headless background agents
71
+ * remote sandboxes
72
+ * plain containers without hardware-backed key access
73
+
74
+ These environments are harder to support because they often cannot satisfy UnoLock's preferred requirement for device-bound, non-exportable key storage in a normal user-controlled session.
75
+
76
+ Official GitHub repository:
77
+
78
+ * `https://github.com/TechSologic/unolock-agent-mcp`
79
+ * Releases: `https://github.com/TechSologic/unolock-agent-mcp/releases`
80
+
81
+ Agent-first onboarding site:
82
+
83
+ * `https://unolock.ai/index.html`
84
+ * `https://unolock.ai/install-mcp.html`
85
+ * `https://unolock.ai/connect-agent.html`
86
+ * `https://unolock.ai/agent-explanation-kit.html`
87
+
88
+ Recommended customer install source:
89
+
90
+ * UnoLock's built-in local daemon/CLI with a GitHub Release binary when available
91
+ * `npx -y @techsologic/unolock-agent@latest` as the easiest Node/npm CLI path
92
+ * `pipx install` as the fallback source install path when no release binary is available yet
93
+
94
+ If you are new to UnoLock itself, start with these docs first:
95
+
96
+ * UnoLock Knowledge Base: `https://docs.unolock.com/index.html`
97
+ * Agentic Safe Access: `https://docs.unolock.com/features/agentic-safe-access.html`
98
+ * Access Keys & Safe Access: `https://docs.unolock.com/features/multi-device-access.html`
99
+ * Spaces: `https://docs.unolock.com/features/spaces.html`
100
+ * Connect an AI Agent to a Safe: `https://docs.unolock.com/howto/connecting-an-ai-agent.html`
101
+
102
+ Prerequisite:
103
+
104
+ * Free and Inheritance can share their single included Safe space with one extra Agent Key.
105
+ * Sovereign and HighRisk are still the right tiers for broader multi-Space and collaboration-heavy agent workflows.
106
+
107
+ The current MCP layer proves the hardest integration seam first:
108
+
109
+ * live local `/start` flow compatibility
110
+ * ML-DSA signature verification
111
+ * ML-KEM encapsulation
112
+ * AES-GCM callback decryption
113
+
114
+ The agent client does not create Safes.
115
+
116
+ Safe creation remains a human/browser responsibility, matching the product model:
117
+
118
+ * human admin creates a Safe
119
+ * human admin creates an agent access key for that Safe
120
+ * MCP registers to the existing Safe
121
+ * MCP later authenticates and uses the shared Safe API surface for agent memory, notes, checklists, and secrets
122
+
123
+ ## Quick start
124
+
125
+ Run this from the repo root after the local server is up on `http://127.0.0.1:3000`:
126
+
127
+ ```bash
128
+ ./scripts/bootstrap.sh
129
+ ./scripts/run_local_probe.sh
130
+ ./scripts/run_stdio_mcp.sh
131
+ ./scripts/run_local_e2e_readonly.sh
132
+ ```
133
+
134
+ For real MCP hosts, see:
135
+
136
+ * [Install Guide](docs/install.md)
137
+ * [macOS Quick Start](docs/macos.md)
138
+ * [Supported Environments](docs/supported-environments.md)
139
+ * [MCP Host Config](docs/host-config.md)
140
+ * [Support Matrix](docs/support-matrix.md)
141
+ * [Tool Catalog](docs/tool-catalog.md)
142
+ * [Claude Desktop example](examples/claude-desktop-config.json)
143
+ * [Cursor example](examples/cursor-mcp.json)
144
+ * [OpenClaw MCP example](examples/openclaw-mcp.json)
145
+ * [OpenClaw plugin config example](examples/openclaw-plugin-config.json)
146
+
147
+ For skill-aware agents, start with the skill above.
148
+ For direct agent use, prefer the CLI:
149
+
150
+ ```bash
151
+ npx -y @techsologic/unolock-agent@latest link-agent-key 'https://safe.example/#/agent-register/...' 1
152
+ npx -y @techsologic/unolock-agent@latest list-spaces
153
+ npx -y @techsologic/unolock-agent@latest list-notes
154
+ npx -y @techsologic/unolock-agent@latest list-files
155
+ ```
156
+
157
+ For hosts that need MCP configuration, use the same executable in explicit MCP mode:
158
+
159
+ * MCP hosts launch `npx -y @techsologic/unolock-agent@latest mcp`.
160
+ * The host writes JSON-RPC to `stdin` and reads JSON-RPC from `stdout`.
161
+ * The `mcp` subcommand uses the local UnoLock daemon and auto-starts it if needed.
162
+ * On a fresh host, the first start can take longer because local cryptographic code may need to be compiled or prepared.
163
+
164
+ That keeps the user PIN in process memory, keeps the current Space selected, and hides local process details from the agent.
165
+
166
+ The same executable also supports explicit CLI commands, for example:
167
+
168
+ ```bash
169
+ unolock-agent link-agent-key 'https://safe.example/#/agent-register/...' 1
170
+ unolock-agent list-spaces
171
+ unolock-agent list-notes
172
+ unolock-agent create-note "Todo" "Buy milk"
173
+ unolock-agent list-files
174
+ ```
175
+
176
+ Use the explicit `mcp` subcommand for daemon-backed stdio MCP mode. All other subcommands are daemon-backed CLI mode. Running `unolock-agent` with no arguments prints usage.
177
+
178
+ Once the local stdio MCP is running, the normal flow is:
179
+
180
+ * call normal UnoLock tools
181
+ * provide the one-time Agent Key URL and PIN together when the MCP asks for setup
182
+ * let the MCP keep and use the current Space by default for normal work
183
+
184
+ If you prefer manual install from source:
185
+
186
+ ```bash
187
+ git clone https://github.com/TechSologic/unolock-agent-mcp.git
188
+ cd unolock-agent-mcp
189
+ python3 -m pip install --user -e .
190
+ unolock-agent-probe probe
191
+ unolock-agent --help
192
+ unolock-agent mcp
193
+ python3 -m unolock_mcp tpm-diagnose
194
+ unolock-agent-tpm-check
195
+ unolock-agent-self-test
196
+ python3 -m unolock_mcp config-check
197
+ ```
198
+
199
+ For normal customer and agent onboarding, do not drive the CLI `bootstrap` command directly.
200
+ Let the MCP guide the normal flow.
201
+
202
+ macOS support is still alpha. The MCP now prefers Secure Enclave when it works cleanly and otherwise falls back to a non-exportable macOS Keychain key for broader compatibility. If you are evaluating it on Apple Silicon, start with:
203
+
204
+ * [macOS Quick Start](docs/macos.md)
205
+
206
+ The first `liboqs-python` run may build or locate `liboqs` under your home directory. That can take a few minutes, so agents should not assume the first startup is hung just because it is slower than later launches.
207
+
208
+ For the best customer experience, prefer GitHub Release binaries over source installs. Source installs still depend on the local `liboqs-python` / `liboqs` environment.
209
+
210
+ ## Preferred Customer Install
211
+
212
+ When available, prefer the built-in UnoLock local daemon plus the npm wrapper or release binary.
213
+
214
+ For an agent-first public onboarding flow, send users or agents to:
215
+
216
+ * `https://unolock.ai/index.html`
217
+
218
+ When available, prefer the standalone GitHub Release binaries instead of installing from Git.
219
+
220
+ That avoids most of the Python packaging and source-build overhead for customers.
221
+
222
+ If your host environment is already Node/npm-oriented, you can also use the npm wrapper:
223
+
224
+ ```bash
225
+ npx -y @techsologic/unolock-agent@latest --version
226
+ ```
227
+
228
+ The wrapper downloads the correct GitHub Release binary for the current platform on first use and then reuses the cached copy.
229
+
230
+ 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.
231
+
232
+ The npm package is both:
233
+
234
+ * the normal MCP command package
235
+ * an OpenClaw plugin package that ships the UnoLock skill
236
+
237
+ Project home:
238
+
239
+ * `https://github.com/TechSologic/unolock-agent-mcp`
240
+
241
+ Use it as a command that OpenClaw can launch, for example:
242
+
243
+ ```bash
244
+ npx -y @techsologic/unolock-agent@latest mcp
245
+ ```
246
+
247
+ For MCP hosts, use the explicit `mcp` argument:
248
+
249
+ ```bash
250
+ npx -y @techsologic/unolock-agent@latest mcp
251
+ ```
252
+
253
+ That is the preferred host-facing launch shape.
254
+
255
+ ## Update Policy
256
+
257
+ UnoLock Agent should not replace itself in the middle of an active session or write flow.
258
+
259
+ The intended update model is:
260
+
261
+ * the MCP reports update status
262
+ * the install channel applies updates
263
+ * the UnoLock process restarts between tasks so in-memory PINs and sessions can be re-established cleanly
264
+
265
+ Check update status with:
266
+
267
+ ```bash
268
+ unolock-agent check-update --json
269
+ ```
270
+
271
+ Or, through the MCP itself, call:
272
+
273
+ * `unolock_get_update_status`
274
+
275
+ Preferred channel behavior:
276
+
277
+ * built-in daemon + `npx -y @techsologic/unolock-agent@latest`
278
+ * preferred low-friction path
279
+ * on restart, the npm wrapper checks GitHub Releases and can fetch the latest stable binary
280
+ * npm publishing is only needed when the wrapper itself changes
281
+ * direct GitHub Release binary
282
+ * replace the binary manually, then restart the UnoLock MCP
283
+ * Python package install
284
+ * upgrade the package in that environment, then restart the UnoLock MCP
285
+
286
+ For the best user experience, do updates between tasks, not while a setup flow, authentication flow, or sensitive write flow is active.
287
+
288
+ ## Standalone config
289
+
290
+ Normal setup should not require this section. When the MCP runs outside the main UnoLock monorepo, it can usually derive its UnoLock runtime config from the UnoLock Agent Key URL. Environment variables and config files are advanced overrides for custom deployments or broken metadata, not part of the normal agent flow.
291
+
292
+ Advanced override example:
293
+
294
+ ```json
295
+ {
296
+ "base_url": "https://api.unolock.example",
297
+ "transparency_origin": "https://safe.unolock.example",
298
+ "signing_public_key_b64": "BASE64_SERVER_PQ_SIGNING_PUBLIC_KEY"
299
+ }
300
+ ```
301
+
302
+ For normal UnoLock cloud-service use, the MCP can derive the API origin and PQ validation key from the user-provided Agent Key URL automatically. UnoLock remains client-side encrypted, no identity is linked to a Safe, and the design tries to minimize unnecessary metadata and correlation exposure. If you want to force the same normal cloud deployment without waiting for an Agent Key URL, this also works:
303
+
304
+ ```json
305
+ {
306
+ "base_url": "https://api.safe.unolock.com"
307
+ }
308
+ ```
309
+
310
+ the MCP will derive `https://safe.unolock.com`, fetch `/unolock-client.json`, and read the published `serverPQValidationKey`. If that deployment metadata file is unavailable, it falls back to the transparency bundle.
311
+
312
+ Use this command to verify what the MCP resolved:
313
+
314
+ ```bash
315
+ python3 -m unolock_mcp config-check
316
+ ```
317
+
318
+ TPM provider selection:
319
+
320
+ * default: `UNOLOCK_TPM_PROVIDER=auto`
321
+ * force software provider: `UNOLOCK_TPM_PROVIDER=software`
322
+ * force Linux TPM/vTPM provider: `UNOLOCK_TPM_PROVIDER=linux`
323
+ * force best macOS provider: `UNOLOCK_TPM_PROVIDER=mac`
324
+ * force best Windows provider: `UNOLOCK_TPM_PROVIDER=windows`
325
+
326
+ On WSL2, `auto` now prefers the Windows TPM helper provider when `powershell.exe` can create TPM-backed keys on the Windows host, and falls back to a non-exportable Windows CNG key when TPM-backed creation is unavailable. This has been validated locally with live registration and authentication. If neither Windows path works, `auto` falls back to the software provider with loud reduced-assurance warnings.
327
+
328
+ On macOS, `auto` now tries the Secure Enclave provider first and then falls back to a non-exportable Keychain-backed provider. Secure Enclave remains the higher-assurance path, but the Keychain path is there to reduce launch-context friction on real Macs.
329
+
330
+ ## Current capabilities
331
+
332
+ The working path today is the local probe:
333
+
334
+ * GET `/start?type=access`
335
+ * verify `PQ_KEY_EXCHANGE`
336
+ * verify the server ML-DSA signature
337
+ * encapsulate to the server ML-KEM public key
338
+ * POST the PQ callback response
339
+ * decrypt the next encrypted callback
340
+
341
+ On the current local stack this is already returning the next callback successfully.
342
+
343
+ The package now also exposes a real stdio MCP server with:
344
+
345
+ * single active UnoLock auth-flow state machine
346
+ * generic `/start` flow bootstrap after PQ negotiation
347
+ * generic flow continuation
348
+ * generic authenticated `/api` action calls
349
+ * convenience wrappers for `GetSpaces` and `GetArchives`
350
+ * note and checklist projection from UnoLock `Records` archives
351
+ * write-support MVP for notes and checklists with version-aware conflict handling
352
+
353
+ Installed commands:
354
+
355
+ * `unolock-agent-probe`
356
+ * run the packaged local probe
357
+ * `unolock-agent`
358
+ * run the CLI and print usage with no arguments
359
+ * use `unolock-agent mcp` for stdio MCP mode
360
+ * `unolock-agent-tpm-check`
361
+ * run the fail-fast production-readiness TPM check
362
+
363
+ Current MCP tools:
364
+
365
+ * `unolock_set_agent_pin`
366
+ * `unolock_link_agent_key`
367
+ * `unolock_list_spaces`
368
+ * `unolock_get_current_space`
369
+ * `unolock_set_current_space`
370
+ * `unolock_list_records`
371
+ * `unolock_list_files`
372
+ * `unolock_list_notes`
373
+ * `unolock_list_checklists`
374
+ * `unolock_get_file`
375
+ * `unolock_get_record`
376
+ * `unolock_download_file`
377
+ * `unolock_rename_file`
378
+ * `unolock_replace_file`
379
+ * `unolock_delete_file`
380
+ * `unolock_create_note`
381
+ * `unolock_update_note`
382
+ * `unolock_append_note`
383
+ * `unolock_upload_file`
384
+ * `unolock_rename_record`
385
+ * `unolock_create_checklist`
386
+ * `unolock_set_checklist_item_done`
387
+ * `unolock_add_checklist_item`
388
+ * `unolock_remove_checklist_item`
389
+
390
+ Low-level flow and raw API debug tools are hidden by default. Enable them only for debugging with `UNOLOCK_MCP_ENABLE_ADVANCED_TOOLS=1`.
391
+ * the software provider is the final fallback when the host cannot provide a production-grade provider, and the MCP surfaces that reduced assurance clearly
392
+ * once authenticated, the MCP can read UnoLock notes/checklists and project them into plain-text agent-friendly DTOs while keeping the stored Quill/checklist formats unchanged
393
+ * the MCP can now create notes and checklists and perform version-aware note/checklist updates, note appends, and checklist updates within the agent's allowed Spaces
394
+ * the MCP now keeps one current Space and uses it as the default for normal read, write, and Cloud file operations
395
+ * registration status now reports a `recommended_next_action` and `guidance` field so an agent can tell whether it should ask for an agent key URL, ask for a PIN, start registration, or authenticate
396
+ * after the MCP process restarts, the agent stays registered but must ask the user for the PIN again before re-authenticating
397
+ * registration state now remembers which TPM provider created the agent key and will tell the host to re-register or force the old provider if there is a provider mismatch
398
+ * the MCP can diagnose the active TPM/vTPM provider and give host advice when no working TPM/vTPM is detected
399
+
400
+ Read and write support:
401
+
402
+ * `unolock_list_records` accepts `kind`, `pinned`, and `label`
403
+ * `unolock_list_notes` and `unolock_list_checklists` are convenience wrappers
404
+ * `unolock_list_spaces` marks the current Space, and `unolock_get_current_space` / `unolock_set_current_space` manage that default
405
+ * `unolock_list_files` exposes only `Cloud` archives; `Local` and `Msg` archives are intentionally excluded
406
+ * `unolock_list_spaces` returns space metadata plus record counts and Cloud file counts
407
+ * normal read and write tools use the current Space automatically and include the `space_id` they actually used in their responses
408
+ * read/list/get responses include `writable`, `allowed_operations`, `version`, `read_only`, and `locked`
409
+ * write tools use cache-first optimistic writes with 5-minute in-memory archive TTLs
410
+ * archive rereads happen only on cache miss, cache expiry, or upload conflict
411
+ * write conflicts return stable structured reasons such as `write_conflict_requires_reread`
412
+ * `unolock_upload_file` creates a `Cloud` archive and uploads encrypted multipart chunks like the web client path
413
+ * `unolock_download_file` reconstructs multipart Cloud archives part by part before writing plaintext to the local filesystem
414
+ * `unolock_rename_file` updates only Cloud file metadata and keeps the archive in place
415
+ * `unolock_replace_file` reuses the existing Cloud archive ID while replacing file contents
416
+ * `unolock_delete_file` removes the Cloud archive when the Agent Key is writable
417
+
418
+ Current bootstrap limitation:
419
+
420
+ * to finish `DecodeKey` and `ClientDataKey`, the MCP still needs the bootstrap AIDK material for the access
421
+ * if the Agent Key URL does not include that bootstrap secret, the MCP will stop with a clear blocker instead of faking progress
422
+ * that keeps the implementation aligned with UnoLock's current AIDK/CDMK hierarchy instead of bypassing it
423
+
424
+ ## Testing with a local Safe
425
+
426
+ When you need a real Safe for local testing, use the UnoLock browser Playwright harness from a full UnoLock checkout under `client/e2e-playwright`.
427
+
428
+ That harness already covers:
429
+
430
+ * Safe creation
431
+ * virtual WebAuthn registration in Chromium
432
+ * Safe open/lifecycle flows
433
+
434
+ This keeps the boundary clean:
435
+
436
+ * browser tests create and manage test Safes
437
+ * `agent-mcp` only probes or authenticates against an existing Safe
438
+
439
+ For local agent bootstrap, the create-safe harness can now emit a registration artifact:
440
+
441
+ ```bash
442
+ E2E_AGENT_BOOTSTRAP_OUTPUT_FILE=/tmp/unolock-agent-bootstrap.json \
443
+ npm --prefix client/e2e-playwright run test:create-safe
444
+ cat /tmp/unolock-agent-bootstrap.json
445
+ ```
446
+
447
+ That artifact includes:
448
+
449
+ * the generated UnoLock Agent Key URL
450
+ * the access ID used for the agent registration
451
+ * the bootstrap secret encoding needed by the MCP
452
+ * whether the browser had to fall back to the current access because the Safe tier could not create another device access
453
+ * with the default Playwright settings, the local test PIN is `0123`
454
+
455
+ Current local fallback behavior:
456
+
457
+ * if the Safe tier permits another device access, the harness creates a dedicated AI-marked access
458
+ * if the tier blocks new device accesses, the harness issues an agent registration URL for the current authenticated access instead
459
+ * the current-access fallback exists for local/dev testing and is not the preferred long-term product shape
460
+
461
+ For a full local regression run:
462
+
463
+ ```bash
464
+ ./scripts/run_local_e2e_readonly.sh
465
+ ```
466
+
467
+ That script:
468
+
469
+ * creates a fresh local Safe and agent bootstrap artifact with Playwright
470
+ * registers the MCP against that new agent key
471
+ * authenticates and reads spaces/records
472
+ * simulates an MCP restart
473
+ * re-authenticates with the PIN and verifies access again
474
+
475
+ ## Package layout
476
+
477
+ ```text
478
+ unolock-agent/
479
+ docs/
480
+ scripts/
481
+ src/
482
+ unolock_mcp/
483
+ api/
484
+ auth/
485
+ crypto/
486
+ domain/
487
+ mcp/
488
+ tpm/
489
+ transport/
490
+ tests/
491
+ ```
492
+
493
+ ## Separation of concerns
494
+
495
+ * `src/unolock_mcp/tpm/`
496
+ * TPM DAO and provider implementations
497
+ * `src/unolock_mcp/crypto/`
498
+ * PQ session negotiation
499
+ * callback AES-GCM helpers
500
+ * AWS Encryption SDK helpers
501
+ * Safe keyring management
502
+ * `src/unolock_mcp/transport/`
503
+ * `/start` and `/api` HTTP transport
504
+ * callback DTO handling
505
+ * `src/unolock_mcp/auth/`
506
+ * agent registration and access clients
507
+ * local compatibility probe
508
+ * single-flow auth state and local registration state
509
+ * `src/unolock_mcp/api/`
510
+ * authenticated Safe API client
511
+ * `src/unolock_mcp/domain/`
512
+ * domain objects and DTOs
513
+ * `src/unolock_mcp/mcp/`
514
+ * MCP tool surface only
515
+
516
+ ## Notes
517
+
518
+ * Server-side interop probes can still live under `server/safe-server/scripts/` when they are validating server behavior directly.
519
+ * Production agent auth is intended to use TPM/vTPM or equivalent device-backed storage. The software provider is the lower-assurance fallback when stronger host key protection is not available.
520
+ If you want OpenClaw to load the UnoLock skill as a plugin, the intended published install path is:
521
+
522
+ ```bash
523
+ openclaw plugins install @techsologic/unolock-agent
524
+ ```
525
+
526
+ For local testing before publishing that plugin path, point OpenClaw at this repo through `plugins.load.paths` and enable the `unolock-agent-access` plugin. See [examples/openclaw-plugin-config.json](examples/openclaw-plugin-config.json).
@@ -0,0 +1,239 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const fs = require("fs");
5
+ const os = require("os");
6
+ const path = require("path");
7
+ const https = require("https");
8
+ const { spawn } = require("child_process");
9
+
10
+ const PACKAGE_VERSION = "0.1.28";
11
+ const FALLBACK_BINARY_VERSION = "0.1.28";
12
+ const REPO = "TechSologic/unolock-agent-mcp";
13
+
14
+ function platformAssetInfo() {
15
+ const platform = process.platform;
16
+ const arch = process.arch;
17
+ if (platform === "linux" && arch === "x64") {
18
+ return { asset: "unolock-agent-linux-x86_64", executable: "unolock-agent-linux-x86_64" };
19
+ }
20
+ if (platform === "darwin" && arch === "arm64") {
21
+ return { asset: "unolock-agent-macos-arm64", executable: "unolock-agent-macos-arm64" };
22
+ }
23
+ if (platform === "darwin" && arch === "x64") {
24
+ return { asset: "unolock-agent-macos-x86_64", executable: "unolock-agent-macos-x86_64" };
25
+ }
26
+ if (platform === "win32" && arch === "x64") {
27
+ return { asset: "unolock-agent-windows-amd64.exe", executable: "unolock-agent-windows-amd64.exe" };
28
+ }
29
+ throw new Error(`Unsupported platform for UnoLock agent binary: ${platform}/${arch}`);
30
+ }
31
+
32
+ function cacheRoot() {
33
+ if (process.platform === "win32") {
34
+ return process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local");
35
+ }
36
+ return process.env.XDG_CACHE_HOME || path.join(os.homedir(), ".cache");
37
+ }
38
+
39
+ function metadataPath() {
40
+ return path.join(cacheRoot(), "unolock-agent", "release.json");
41
+ }
42
+
43
+ function binaryPath(releaseVersion) {
44
+ const { executable } = platformAssetInfo();
45
+ return path.join(cacheRoot(), "unolock-agent", releaseVersion, executable);
46
+ }
47
+
48
+ function binaryUrl(releaseVersion) {
49
+ if (process.env.UNOLOCK_AGENT_BINARY_URL) {
50
+ return process.env.UNOLOCK_AGENT_BINARY_URL;
51
+ }
52
+ const { asset } = platformAssetInfo();
53
+ return `https://github.com/${REPO}/releases/download/v${releaseVersion}/${asset}`;
54
+ }
55
+
56
+ function ensureDir(dir) {
57
+ fs.mkdirSync(dir, { recursive: true, mode: 0o755 });
58
+ }
59
+
60
+ function fetchToFile(url, dest) {
61
+ return new Promise((resolve, reject) => {
62
+ const temp = `${dest}.download`;
63
+ const request = https.get(url, (response) => {
64
+ if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
65
+ response.resume();
66
+ fetchToFile(response.headers.location, dest).then(resolve, reject);
67
+ return;
68
+ }
69
+ if (response.statusCode !== 200) {
70
+ response.resume();
71
+ reject(new Error(`Failed to download UnoLock agent binary: HTTP ${response.statusCode}`));
72
+ return;
73
+ }
74
+ const file = fs.createWriteStream(temp, { mode: 0o755 });
75
+ response.pipe(file);
76
+ file.on("finish", () => {
77
+ file.close((closeErr) => {
78
+ if (closeErr) {
79
+ reject(closeErr);
80
+ return;
81
+ }
82
+ fs.renameSync(temp, dest);
83
+ if (process.platform !== "win32") {
84
+ fs.chmodSync(dest, 0o755);
85
+ }
86
+ resolve();
87
+ });
88
+ });
89
+ file.on("error", (error) => {
90
+ file.close(() => {
91
+ try {
92
+ fs.unlinkSync(temp);
93
+ } catch {}
94
+ reject(error);
95
+ });
96
+ });
97
+ });
98
+ request.on("error", reject);
99
+ });
100
+ }
101
+
102
+ function fetchJson(url) {
103
+ return new Promise((resolve, reject) => {
104
+ const request = https.get(
105
+ url,
106
+ {
107
+ headers: {
108
+ "Accept": "application/vnd.github+json",
109
+ "User-Agent": "unolock-agent-npm-wrapper"
110
+ }
111
+ },
112
+ (response) => {
113
+ if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
114
+ response.resume();
115
+ fetchJson(response.headers.location).then(resolve, reject);
116
+ return;
117
+ }
118
+ if (response.statusCode !== 200) {
119
+ response.resume();
120
+ reject(new Error(`Failed to query UnoLock agent latest release: HTTP ${response.statusCode}`));
121
+ return;
122
+ }
123
+ let body = "";
124
+ response.setEncoding("utf8");
125
+ response.on("data", (chunk) => {
126
+ body += chunk;
127
+ });
128
+ response.on("end", () => {
129
+ try {
130
+ resolve(JSON.parse(body));
131
+ } catch (error) {
132
+ reject(error);
133
+ }
134
+ });
135
+ }
136
+ );
137
+ request.on("error", reject);
138
+ });
139
+ }
140
+
141
+ function normalizeVersion(value) {
142
+ if (!value || typeof value !== "string") {
143
+ return null;
144
+ }
145
+ const trimmed = value.trim();
146
+ if (!trimmed) {
147
+ return null;
148
+ }
149
+ return trimmed.startsWith("v") ? trimmed.slice(1) : trimmed;
150
+ }
151
+
152
+ function readReleaseMetadata() {
153
+ try {
154
+ return JSON.parse(fs.readFileSync(metadataPath(), "utf8"));
155
+ } catch {
156
+ return null;
157
+ }
158
+ }
159
+
160
+ function writeReleaseMetadata(releaseVersion) {
161
+ ensureDir(path.dirname(metadataPath()));
162
+ fs.writeFileSync(
163
+ metadataPath(),
164
+ JSON.stringify(
165
+ {
166
+ releaseVersion,
167
+ checkedAt: Date.now()
168
+ },
169
+ null,
170
+ 2
171
+ ),
172
+ "utf8"
173
+ );
174
+ }
175
+
176
+ async function resolveReleaseVersion() {
177
+ const override = normalizeVersion(process.env.UNOLOCK_AGENT_BINARY_VERSION);
178
+ if (override) {
179
+ return override;
180
+ }
181
+ const metadata = readReleaseMetadata();
182
+ try {
183
+ const payload = await fetchJson(`https://api.github.com/repos/${REPO}/releases/latest`);
184
+ const latest = normalizeVersion(payload && payload.tag_name);
185
+ if (latest) {
186
+ writeReleaseMetadata(latest);
187
+ return latest;
188
+ }
189
+ } catch (error) {
190
+ if (metadata && typeof metadata.releaseVersion === "string" && fs.existsSync(binaryPath(metadata.releaseVersion))) {
191
+ return metadata.releaseVersion;
192
+ }
193
+ process.stderr.write(`Warning: ${error.message}. Falling back to bundled release ${FALLBACK_BINARY_VERSION}.\n`);
194
+ }
195
+ writeReleaseMetadata(FALLBACK_BINARY_VERSION);
196
+ return FALLBACK_BINARY_VERSION;
197
+ }
198
+
199
+ async function ensureBinary() {
200
+ const releaseVersion = await resolveReleaseVersion();
201
+ const dest = binaryPath(releaseVersion);
202
+ if (fs.existsSync(dest)) {
203
+ return { dest, releaseVersion };
204
+ }
205
+ ensureDir(path.dirname(dest));
206
+ process.stderr.write(`Downloading UnoLock agent ${releaseVersion} for ${process.platform}/${process.arch}...\n`);
207
+ await fetchToFile(binaryUrl(releaseVersion), dest);
208
+ return { dest, releaseVersion };
209
+ }
210
+
211
+ async function main() {
212
+ const { dest, releaseVersion } = await ensureBinary();
213
+ const forwardedArgs = process.argv.length > 2 ? process.argv.slice(2) : [];
214
+ const child = spawn(dest, forwardedArgs, {
215
+ stdio: "inherit",
216
+ env: {
217
+ ...process.env,
218
+ UNOLOCK_AGENT_INSTALL_CHANNEL: "npm-wrapper",
219
+ UNOLOCK_AGENT_WRAPPER_VERSION: PACKAGE_VERSION,
220
+ UNOLOCK_AGENT_BINARY_VERSION: releaseVersion
221
+ }
222
+ });
223
+ child.on("exit", (code, signal) => {
224
+ if (signal) {
225
+ process.kill(process.pid, signal);
226
+ return;
227
+ }
228
+ process.exit(code ?? 1);
229
+ });
230
+ child.on("error", (error) => {
231
+ process.stderr.write(`${error.message}\n`);
232
+ process.exit(1);
233
+ });
234
+ }
235
+
236
+ main().catch((error) => {
237
+ process.stderr.write(`${error.message}\n`);
238
+ process.exit(1);
239
+ });
@@ -0,0 +1 @@
1
+ export default {};
@@ -0,0 +1,11 @@
1
+ {
2
+ "id": "unolock-agent-access",
3
+ "name": "UnoLock Agent Access",
4
+ "description": "Ships the UnoLock agent-access skill for OpenClaw. The skill uses the local UnoLock CLI.",
5
+ "configSchema": {
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "properties": {},
9
+ "required": []
10
+ }
11
+ }
@@ -0,0 +1,53 @@
1
+ ---
2
+ name: unolock-agent-access
3
+ description: Guides an AI agent through connecting to a user's UnoLock Safe with the UnoLock agent tool, then working in the current Space with notes, checklists, and files.
4
+ ---
5
+
6
+ # UnoLock Agent Access
7
+
8
+ Use this skill when a user wants to give their agent access to a UnoLock Safe.
9
+ This skill uses the local `unolock-agent` executable on the user's device. The CLI is the simplest way to work with UnoLock. The same executable also supports MCP for hosts that specifically require it.
10
+
11
+ ## Preferred Workflow
12
+
13
+ 1. If `unolock-agent` is not already installed, use `npx -y @techsologic/unolock-agent@latest`.
14
+ 2. On a fresh host, allow extra time for the first start.
15
+ 3. Ask the user for the one-time Agent Key URL and the PIN together.
16
+ 4. Link the key on this device:
17
+ `unolock-agent link-agent-key '<agent-key-url>' '<pin>'`
18
+ 5. List spaces and select the working space when needed:
19
+ `unolock-agent list-spaces`
20
+ `unolock-agent set-current-space <space_id>`
21
+ 6. Use the normal note, checklist, and file commands for day-to-day work.
22
+
23
+ ## Main CLI Commands
24
+
25
+ - `unolock-agent link-agent-key '<agent-key-url>' '<pin>'`: first setup on this device
26
+ - `unolock-agent set-agent-pin '<pin>'`: provide the PIN again after restart or re-authentication
27
+ - `unolock-agent list-spaces`, `unolock-agent get-current-space`, `unolock-agent set-current-space <space_id>`: inspect and switch the current Space
28
+ - `unolock-agent list-notes`, `unolock-agent create-note <title> <text>`, `unolock-agent update-note ...`, `unolock-agent append-note ...`: read and write notes
29
+ - `unolock-agent list-checklists`, `unolock-agent create-checklist ...`, `unolock-agent set-checklist-item-done ...`, `unolock-agent add-checklist-item ...`, `unolock-agent remove-checklist-item ...`: read and write checklists
30
+ - `unolock-agent list-files`, `unolock-agent get-file <archive_id>`, `unolock-agent download-file ...`, `unolock-agent upload-file ...`, `unolock-agent rename-file ...`, `unolock-agent replace-file ...`, `unolock-agent delete-file ...`: read and manage Cloud files
31
+ - `unolock-agent get-record <record_ref>` and `unolock-agent rename-record ...`: inspect or rename an existing note or checklist
32
+
33
+ ## MCP Fallback
34
+
35
+ If the host specifically requires MCP instead of CLI:
36
+
37
+ - run `npx -y @techsologic/unolock-agent@latest mcp`
38
+ - write MCP JSON-RPC to `stdin` and read it from `stdout`
39
+ - let the `mcp` subcommand auto-start and proxy through the local UnoLock daemon
40
+
41
+ ## Key Rules
42
+
43
+ - prefer CLI commands over MCP when the host supports direct command execution
44
+ - give the agent the Agent Key URL and PIN together for first setup
45
+ - if a command reports that the PIN is needed, run `unolock-agent set-agent-pin '<pin>'` and retry the original command
46
+ - keep the PIN in UnoLock process memory only
47
+
48
+ ## User-Facing Model
49
+
50
+ - The user manages the Agent Key in the UnoLock Safe web app.
51
+ - The user gives the agent a one-time Agent Key URL and the PIN.
52
+ - The local UnoLock agent uses those to set up access on the current device.
53
+ - After that, ongoing access uses the registered local Agent Key.
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@techsologic/unolock-agent",
3
+ "version": "0.1.28",
4
+ "description": "npx wrapper for the official UnoLock Agent release binaries",
5
+ "license": "UNLICENSED",
6
+ "homepage": "https://unolock.ai",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/TechSologic/unolock-agent-mcp.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/TechSologic/unolock-agent-mcp/issues"
13
+ },
14
+ "bin": {
15
+ "unolock-agent": "bin/unolock-agent.js"
16
+ },
17
+ "openclaw": {
18
+ "extensions": [
19
+ "./openclaw-plugin/index.js"
20
+ ]
21
+ },
22
+ "files": [
23
+ "bin/",
24
+ "skills/",
25
+ "openclaw-plugin/"
26
+ ],
27
+ "keywords": [
28
+ "unolock",
29
+ "mcp",
30
+ "agent",
31
+ "security",
32
+ "tpm"
33
+ ],
34
+ "engines": {
35
+ "node": ">=18"
36
+ }
37
+ }
@@ -0,0 +1,54 @@
1
+ ---
2
+ name: unolock-agent-access
3
+ description: Guides an AI agent through connecting to a user's UnoLock Safe with the UnoLock agent tool, then working in the current Space with notes, checklists, and files.
4
+ ---
5
+
6
+ # UnoLock Agent Access
7
+
8
+ Use this skill when a user wants to give their agent access to a UnoLock Safe.
9
+ This skill uses the local `unolock-agent` executable on the user's device. The CLI is the simplest way to work with UnoLock. The same executable also supports MCP for hosts that specifically require it.
10
+
11
+ ## Preferred Workflow
12
+
13
+ 1. Load this skill if the host supports skills.
14
+ 2. If `unolock-agent` is not already installed, use `npx -y @techsologic/unolock-agent@latest`.
15
+ 3. On a fresh host, allow extra time for the first start.
16
+ 4. Ask the user for the one-time Agent Key URL and the PIN together.
17
+ 5. Link the key on this device:
18
+ `unolock-agent link-agent-key '<agent-key-url>' '<pin>'`
19
+ 6. List spaces and select the working space when needed:
20
+ `unolock-agent list-spaces`
21
+ `unolock-agent set-current-space <space_id>`
22
+ 7. Use the normal note, checklist, and file commands for day-to-day work.
23
+
24
+ ## Main CLI Commands
25
+
26
+ - `unolock-agent link-agent-key '<agent-key-url>' '<pin>'`: first setup on this device
27
+ - `unolock-agent set-agent-pin '<pin>'`: provide the PIN again after restart or re-authentication
28
+ - `unolock-agent list-spaces`, `unolock-agent get-current-space`, `unolock-agent set-current-space <space_id>`: inspect and switch the current Space
29
+ - `unolock-agent list-notes`, `unolock-agent create-note <title> <text>`, `unolock-agent update-note ...`, `unolock-agent append-note ...`: read and write notes
30
+ - `unolock-agent list-checklists`, `unolock-agent create-checklist ...`, `unolock-agent set-checklist-item-done ...`, `unolock-agent add-checklist-item ...`, `unolock-agent remove-checklist-item ...`: read and write checklists
31
+ - `unolock-agent list-files`, `unolock-agent get-file <archive_id>`, `unolock-agent download-file ...`, `unolock-agent upload-file ...`, `unolock-agent rename-file ...`, `unolock-agent replace-file ...`, `unolock-agent delete-file ...`: read and manage Cloud files
32
+ - `unolock-agent get-record <record_ref>` and `unolock-agent rename-record ...`: inspect or rename an existing note or checklist
33
+
34
+ ## MCP Fallback
35
+
36
+ If the host specifically requires MCP instead of CLI:
37
+
38
+ - run `npx -y @techsologic/unolock-agent@latest mcp`
39
+ - write MCP JSON-RPC to `stdin` and read it from `stdout`
40
+ - let the `mcp` subcommand auto-start and proxy through the local UnoLock daemon
41
+
42
+ ## Key Rules
43
+
44
+ - prefer CLI commands over MCP when the host supports direct command execution
45
+ - give the agent the Agent Key URL and PIN together for first setup
46
+ - if a command reports that the PIN is needed, run `unolock-agent set-agent-pin '<pin>'` and retry the original command
47
+ - keep the PIN in UnoLock process memory only
48
+
49
+ ## User-Facing Model
50
+
51
+ - The user manages the Agent Key in the UnoLock Safe web app.
52
+ - The user gives the agent a one-time Agent Key URL and the PIN.
53
+ - The local UnoLock agent uses those to set up access on the current device.
54
+ - After that, ongoing access uses the registered local Agent Key.
@@ -0,0 +1,7 @@
1
+ interface:
2
+ display_name: "UnoLock Agent Access"
3
+ short_description: "Connect an agent to a user's UnoLock Safe"
4
+ default_prompt: "Use $unolock-agent-access to connect this agent to a user's UnoLock Safe and then work in the current Space."
5
+
6
+ policy:
7
+ allow_implicit_invocation: true