@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 +526 -0
- package/bin/unolock-agent.js +239 -0
- package/openclaw-plugin/index.js +1 -0
- package/openclaw-plugin/openclaw.plugin.json +11 -0
- package/openclaw-plugin/skills/unolock-agent-access/SKILL.md +53 -0
- package/package.json +37 -0
- package/skills/unolock-agent-access/SKILL.md +54 -0
- package/skills/unolock-agent-access/agents/openai.yaml +7 -0
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
|