@agentlayer.tech/wallet 0.1.43 → 0.1.44
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/.claude-plugin/marketplace.json +17 -0
- package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +1 -1
- package/.openclaw/extensions/agent-wallet/package.json +1 -1
- package/CHANGELOG.md +17 -0
- package/README.md +14 -0
- package/VERSION +1 -1
- package/agent-wallet/agent_wallet/__init__.py +1 -1
- package/agent-wallet/agent_wallet/openclaw_cli.py +22 -0
- package/agent-wallet/agent_wallet/telemetry.py +306 -0
- package/agent-wallet/openclaw.plugin.json +1 -1
- package/agent-wallet/pyproject.toml +1 -1
- package/claude-code/plugins/agent-wallet/.claude-plugin/plugin.json +1 -1
- package/claude-code/plugins/agent-wallet/README.md +30 -1
- package/claude-code/plugins/agent-wallet/commands/wallet-setup.md +24 -0
- package/claude-code/plugins/agent-wallet/hooks/hooks.json +16 -0
- package/claude-code/plugins/agent-wallet/scripts/bootstrap_backend.sh +136 -0
- package/claude-code/plugins/agent-wallet/scripts/run_mcp.sh +6 -1
- package/codex/plugins/agent-wallet/.codex-plugin/plugin.json +1 -1
- package/codex/plugins/agent-wallet/scripts/run_mcp.sh +5 -0
- package/hermes/plugins/agent_wallet/plugin.yaml +1 -1
- package/hermes/plugins/agent_wallet/tools.py +3 -0
- package/package.json +2 -1
- package/wdk-btc-wallet/package.json +1 -1
- package/wdk-evm-wallet/package.json +1 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agentlayer",
|
|
3
|
+
"description": "AgentLayer plugins for Claude Code. Install the wallet bridge and its backend runtime without leaving the CLI.",
|
|
4
|
+
"owner": {
|
|
5
|
+
"name": "AgentLayer",
|
|
6
|
+
"url": "https://github.com/lopushok9/Agent-Layer"
|
|
7
|
+
},
|
|
8
|
+
"plugins": [
|
|
9
|
+
{
|
|
10
|
+
"name": "agent-wallet",
|
|
11
|
+
"displayName": "Agent Wallet",
|
|
12
|
+
"description": "Bridge to the local AgentLayer wallet runtime (Solana, Bitcoin, EVM). Installs and connects the backend runtime on first use.",
|
|
13
|
+
"category": "development",
|
|
14
|
+
"source": "./claude-code/plugins/agent-wallet"
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "agent-wallet",
|
|
3
3
|
"name": "Agent Wallet",
|
|
4
4
|
"description": "Official OpenClaw plugin bridge for the agent-wallet backends, including Solana, local BTC, and local EVM.",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.44",
|
|
6
6
|
"contracts": {
|
|
7
7
|
"tools": [
|
|
8
8
|
"close_empty_token_accounts",
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## v0.1.44 - 2026-06-12
|
|
6
|
+
|
|
7
|
+
- Added anonymous, privacy-first adoption telemetry so wallet usage can be
|
|
8
|
+
measured across hosts (Claude Code / Codex / Hermes / OpenClaw) without any
|
|
9
|
+
PII.
|
|
10
|
+
- The wallet emits one event per tool invocation through the shared
|
|
11
|
+
`openclaw_cli invoke` chokepoint. Events carry only a random local install
|
|
12
|
+
id, host, the registered tool name, backend family, plugin version, and a
|
|
13
|
+
success flag — never addresses, balances, amounts, tx hashes, arguments, or
|
|
14
|
+
secrets. Secret-touching commands (onboard/wallet-create/unlock/import) are
|
|
15
|
+
never instrumented.
|
|
16
|
+
- Delivery uses a durable local spool plus a detached best-effort flush to the
|
|
17
|
+
provider-gateway, so a short-lived CLI process never adds latency or loses
|
|
18
|
+
events.
|
|
19
|
+
- Opt out at any time with `AGENT_WALLET_NO_TELEMETRY=1` (zero footprint).
|
|
20
|
+
- Per-frontend `host` tagging via `AGENT_WALLET_HOST` in each bridge.
|
|
21
|
+
|
|
5
22
|
## v0.1.41 - 2026-06-07
|
|
6
23
|
|
|
7
24
|
- Hardened Uniswap Trading API swap execution on active EVM mainnet markets.
|
package/README.md
CHANGED
|
@@ -18,6 +18,20 @@ For Codex:
|
|
|
18
18
|
npx @agentlayer.tech/wallet install --yes && npx @agentlayer.tech/wallet codex install --yes
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
+
For Claude Code:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx @agentlayer.tech/wallet install --yes && npx @agentlayer.tech/wallet claude-code install --yes
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Or install entirely from inside the Claude Code CLI, via the plugin marketplace
|
|
28
|
+
(no terminal/npx needed) — two commands, then restart:
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
/plugin marketplace add lopushok9/Agent-Layer
|
|
32
|
+
/plugin install agent-wallet@agentlayer
|
|
33
|
+
```
|
|
34
|
+
|
|
21
35
|
For Hermes:
|
|
22
36
|
|
|
23
37
|
```bash
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.1.
|
|
1
|
+
0.1.44
|
|
@@ -11,6 +11,12 @@ from typing import Any
|
|
|
11
11
|
|
|
12
12
|
from agent_wallet.wallet_layer.base import WalletBackendError
|
|
13
13
|
|
|
14
|
+
try: # telemetry is optional and must never break the CLI
|
|
15
|
+
from agent_wallet.telemetry import record as _telemetry_record
|
|
16
|
+
except Exception: # pragma: no cover - defensive
|
|
17
|
+
def _telemetry_record(*_args: Any, **_kwargs: Any) -> None:
|
|
18
|
+
return None
|
|
19
|
+
|
|
14
20
|
|
|
15
21
|
def _parse_bool(value: Any) -> str:
|
|
16
22
|
if value is None:
|
|
@@ -610,6 +616,15 @@ def main() -> int:
|
|
|
610
616
|
)
|
|
611
617
|
)
|
|
612
618
|
except Exception as exc:
|
|
619
|
+
# Anonymous adoption telemetry for tool invocations only (never for
|
|
620
|
+
# onboard/wallet-create/unlock/import — those touch secrets). Records
|
|
621
|
+
# just the tool name + backend family + failure flag; never raises.
|
|
622
|
+
if getattr(args, "command", "") == "invoke":
|
|
623
|
+
_telemetry_record(
|
|
624
|
+
getattr(args, "tool", ""),
|
|
625
|
+
backend=str(locals().get("config", {}).get("backend", "") or ""),
|
|
626
|
+
ok=False,
|
|
627
|
+
)
|
|
613
628
|
error_payload: dict[str, Any] = {"ok": False, "error": str(exc)}
|
|
614
629
|
if isinstance(exc, WalletBackendError):
|
|
615
630
|
if exc.code:
|
|
@@ -619,6 +634,13 @@ def main() -> int:
|
|
|
619
634
|
print(json.dumps(error_payload), file=sys.stderr)
|
|
620
635
|
return 1
|
|
621
636
|
|
|
637
|
+
if getattr(args, "command", "") == "invoke":
|
|
638
|
+
_telemetry_record(
|
|
639
|
+
getattr(args, "tool", ""),
|
|
640
|
+
backend=str(config.get("backend", "") or ""),
|
|
641
|
+
ok=True,
|
|
642
|
+
)
|
|
643
|
+
|
|
622
644
|
print(json.dumps(payload))
|
|
623
645
|
return 0
|
|
624
646
|
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
"""Anonymous, privacy-first adoption telemetry for the AgentLayer wallet.
|
|
2
|
+
|
|
3
|
+
Why this exists: we want to know how many people use the wallet and through which
|
|
4
|
+
host (Claude Code / Codex / Hermes / OpenClaw) — nothing more. It deliberately
|
|
5
|
+
records NO PII: no wallet addresses, balances, amounts, tx hashes, tool
|
|
6
|
+
arguments, or secrets. Only a random local install id, the host name, the
|
|
7
|
+
invoked tool's registered name, the backend family, the plugin version, and a
|
|
8
|
+
success flag.
|
|
9
|
+
|
|
10
|
+
Design for a short-lived CLI subprocess:
|
|
11
|
+
- `record()` appends one JSON line to a local spool file. This is instant and
|
|
12
|
+
durable: a single small append is atomic on POSIX, and the event survives
|
|
13
|
+
even if this process exits before any network call completes.
|
|
14
|
+
- A throttled, best-effort flush claims the spool (atomic rename), POSTs each
|
|
15
|
+
event to the provider-gateway, and re-spools anything that failed. Because
|
|
16
|
+
durability lives in the spool, a killed flush never loses data — the next
|
|
17
|
+
invocation (or a longer-lived process) ships it.
|
|
18
|
+
|
|
19
|
+
Everything here swallows its own errors. Telemetry must never slow down or break
|
|
20
|
+
a wallet operation. Users opt out with AGENT_WALLET_NO_TELEMETRY=1.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import json
|
|
26
|
+
import os
|
|
27
|
+
import subprocess
|
|
28
|
+
import sys
|
|
29
|
+
import time
|
|
30
|
+
import urllib.request
|
|
31
|
+
import uuid
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
from typing import Any
|
|
34
|
+
|
|
35
|
+
from . import __version__
|
|
36
|
+
from .config import DEFAULT_PROVIDER_GATEWAY_URL, resolve_openclaw_home
|
|
37
|
+
|
|
38
|
+
ALLOWED_HOSTS = {"claude-code", "codex", "hermes", "openclaw"}
|
|
39
|
+
|
|
40
|
+
SPOOL_NAME = "telemetry_spool.jsonl"
|
|
41
|
+
ID_NAME = "telemetry_id"
|
|
42
|
+
LAST_FLUSH_NAME = "telemetry_last_flush"
|
|
43
|
+
|
|
44
|
+
# Keep the spool bounded if the gateway is unreachable for a long time.
|
|
45
|
+
MAX_SPOOL_LINES = 500
|
|
46
|
+
# Don't attempt a network flush more than this often (seconds), unless the spool
|
|
47
|
+
# has grown past the soft cap below.
|
|
48
|
+
FLUSH_THROTTLE_SECONDS = 20
|
|
49
|
+
FLUSH_FORCE_LINES = 50
|
|
50
|
+
# Tight network bounds: telemetry never blocks meaningfully.
|
|
51
|
+
HTTP_TIMEOUT_SECONDS = 1.5
|
|
52
|
+
MAX_EVENTS_PER_FLUSH = 100
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _enabled() -> bool:
|
|
56
|
+
raw = os.getenv("AGENT_WALLET_NO_TELEMETRY", "").strip().lower()
|
|
57
|
+
return raw not in ("1", "true", "yes", "on")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _home() -> Path:
|
|
61
|
+
return resolve_openclaw_home()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _gateway_url() -> str:
|
|
65
|
+
url = os.getenv("PROVIDER_GATEWAY_URL", "").strip() or DEFAULT_PROVIDER_GATEWAY_URL
|
|
66
|
+
return url.rstrip("/")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _host() -> str:
|
|
70
|
+
host = os.getenv("AGENT_WALLET_HOST", "").strip().lower()
|
|
71
|
+
return host if host in ALLOWED_HOSTS else "unknown"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _install_id() -> str:
|
|
75
|
+
"""Stable, random, non-identifying id for this install. Created once."""
|
|
76
|
+
path = _home() / ID_NAME
|
|
77
|
+
try:
|
|
78
|
+
existing = path.read_text(encoding="utf-8").strip()
|
|
79
|
+
if existing:
|
|
80
|
+
return existing
|
|
81
|
+
except OSError:
|
|
82
|
+
pass
|
|
83
|
+
new_id = uuid.uuid4().hex
|
|
84
|
+
try:
|
|
85
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
86
|
+
path.write_text(new_id, encoding="utf-8")
|
|
87
|
+
except OSError:
|
|
88
|
+
pass
|
|
89
|
+
return new_id
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def record(
|
|
93
|
+
tool: str,
|
|
94
|
+
*,
|
|
95
|
+
backend: str = "",
|
|
96
|
+
ok: bool = True,
|
|
97
|
+
event: str = "tool_invoke",
|
|
98
|
+
) -> None:
|
|
99
|
+
"""Append an anonymous event to the spool and best-effort flush. Never raises."""
|
|
100
|
+
try:
|
|
101
|
+
if not _enabled():
|
|
102
|
+
return
|
|
103
|
+
payload = {
|
|
104
|
+
"event": event,
|
|
105
|
+
"install_id": _install_id(),
|
|
106
|
+
"host": _host(),
|
|
107
|
+
"tool": tool or "",
|
|
108
|
+
"backend": backend or "",
|
|
109
|
+
"plugin_version": __version__,
|
|
110
|
+
"ok": bool(ok),
|
|
111
|
+
"ts": int(time.time()),
|
|
112
|
+
}
|
|
113
|
+
_append_spool(payload)
|
|
114
|
+
_maybe_spawn_flush()
|
|
115
|
+
except Exception:
|
|
116
|
+
# Telemetry is never allowed to affect the wallet call.
|
|
117
|
+
pass
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# --- spool I/O --------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _spool_path() -> Path:
|
|
124
|
+
return _home() / SPOOL_NAME
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _append_spool(payload: dict[str, Any]) -> None:
|
|
128
|
+
path = _spool_path()
|
|
129
|
+
line = json.dumps(payload, separators=(",", ":")) + "\n"
|
|
130
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
131
|
+
# A single write of a short line is atomic across processes on POSIX.
|
|
132
|
+
with open(path, "a", encoding="utf-8") as fh:
|
|
133
|
+
fh.write(line)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _spool_line_count() -> int:
|
|
137
|
+
try:
|
|
138
|
+
with open(_spool_path(), "r", encoding="utf-8") as fh:
|
|
139
|
+
return sum(1 for _ in fh)
|
|
140
|
+
except OSError:
|
|
141
|
+
return 0
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _should_flush_now() -> bool:
|
|
145
|
+
count = _spool_line_count()
|
|
146
|
+
if count == 0:
|
|
147
|
+
return False
|
|
148
|
+
if count >= FLUSH_FORCE_LINES:
|
|
149
|
+
return True
|
|
150
|
+
last_path = _home() / LAST_FLUSH_NAME
|
|
151
|
+
try:
|
|
152
|
+
last = float(last_path.read_text(encoding="utf-8").strip() or "0")
|
|
153
|
+
except (OSError, ValueError):
|
|
154
|
+
last = 0.0
|
|
155
|
+
return (time.time() - last) >= FLUSH_THROTTLE_SECONDS
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _mark_flush_attempt() -> None:
|
|
159
|
+
try:
|
|
160
|
+
(_home() / LAST_FLUSH_NAME).write_text(str(time.time()), encoding="utf-8")
|
|
161
|
+
except OSError:
|
|
162
|
+
pass
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _maybe_spawn_flush() -> None:
|
|
166
|
+
"""Trigger a flush in a DETACHED subprocess that outlives this process.
|
|
167
|
+
|
|
168
|
+
A per-tool CLI invocation exits microseconds after recording, so a thread
|
|
169
|
+
would be killed mid-flush. A detached child (its own session, no wait) runs
|
|
170
|
+
the network call on its own time and re-spools failures reliably. Throttling
|
|
171
|
+
keeps this to roughly one spawn per FLUSH_THROTTLE_SECONDS, so most calls
|
|
172
|
+
just append to the spool and return.
|
|
173
|
+
"""
|
|
174
|
+
if not _should_flush_now():
|
|
175
|
+
return
|
|
176
|
+
_mark_flush_attempt()
|
|
177
|
+
try:
|
|
178
|
+
subprocess.Popen(
|
|
179
|
+
[sys.executable, "-m", "agent_wallet.telemetry", "--flush"],
|
|
180
|
+
stdout=subprocess.DEVNULL,
|
|
181
|
+
stderr=subprocess.DEVNULL,
|
|
182
|
+
stdin=subprocess.DEVNULL,
|
|
183
|
+
start_new_session=True, # detach so parent exit doesn't kill it
|
|
184
|
+
)
|
|
185
|
+
except Exception:
|
|
186
|
+
pass
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _pid_alive(pid: int) -> bool:
|
|
190
|
+
try:
|
|
191
|
+
os.kill(pid, 0)
|
|
192
|
+
except ProcessLookupError:
|
|
193
|
+
return False
|
|
194
|
+
except PermissionError:
|
|
195
|
+
return True # exists but owned by another user
|
|
196
|
+
except OSError:
|
|
197
|
+
return True
|
|
198
|
+
return True
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _claim_suffix_pid(path: Path) -> int | None:
|
|
202
|
+
# ...telemetry_spool.jsonl.flushing.<pid>
|
|
203
|
+
try:
|
|
204
|
+
return int(path.name.rsplit(".flushing.", 1)[1])
|
|
205
|
+
except (IndexError, ValueError):
|
|
206
|
+
return None
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _flush() -> None:
|
|
210
|
+
"""Claim the spool atomically, adopt dead-PID orphans, POST, re-spool failures."""
|
|
211
|
+
spool = _spool_path()
|
|
212
|
+
home = _home()
|
|
213
|
+
claim = spool.with_suffix(spool.suffix + f".flushing.{os.getpid()}")
|
|
214
|
+
|
|
215
|
+
claims: list[Path] = []
|
|
216
|
+
try:
|
|
217
|
+
os.rename(spool, claim) # atomic; only one process wins the live spool
|
|
218
|
+
claims.append(claim)
|
|
219
|
+
except OSError:
|
|
220
|
+
pass # nothing to flush, or another process claimed it
|
|
221
|
+
|
|
222
|
+
# Recover orphans left by a flush that died mid-run (crash/exit after the
|
|
223
|
+
# rename but before re-spooling). Only adopt claims whose PID is gone so we
|
|
224
|
+
# never steal a batch a live sibling is still working.
|
|
225
|
+
try:
|
|
226
|
+
for orphan in home.glob(SPOOL_NAME + ".flushing.*"):
|
|
227
|
+
if orphan == claim:
|
|
228
|
+
continue
|
|
229
|
+
pid = _claim_suffix_pid(orphan)
|
|
230
|
+
if pid is None or not _pid_alive(pid):
|
|
231
|
+
claims.append(orphan)
|
|
232
|
+
except OSError:
|
|
233
|
+
pass
|
|
234
|
+
|
|
235
|
+
if not claims:
|
|
236
|
+
return
|
|
237
|
+
|
|
238
|
+
lines: list[str] = []
|
|
239
|
+
for c in claims:
|
|
240
|
+
try:
|
|
241
|
+
lines.extend(c.read_text(encoding="utf-8").splitlines())
|
|
242
|
+
except OSError:
|
|
243
|
+
pass
|
|
244
|
+
|
|
245
|
+
# Bound a long backlog (gateway down for a while): keep the most recent.
|
|
246
|
+
if len(lines) > MAX_SPOOL_LINES:
|
|
247
|
+
lines = lines[-MAX_SPOOL_LINES:]
|
|
248
|
+
|
|
249
|
+
url = _gateway_url() + "/v1/telemetry"
|
|
250
|
+
failed: list[str] = []
|
|
251
|
+
sent = 0
|
|
252
|
+
for line in lines:
|
|
253
|
+
line = line.strip()
|
|
254
|
+
if not line:
|
|
255
|
+
continue
|
|
256
|
+
if sent >= MAX_EVENTS_PER_FLUSH:
|
|
257
|
+
failed.append(line) # defer the rest to the next flush
|
|
258
|
+
continue
|
|
259
|
+
if _post(url, line):
|
|
260
|
+
sent += 1
|
|
261
|
+
else:
|
|
262
|
+
failed.append(line)
|
|
263
|
+
|
|
264
|
+
# Re-spool failures BEFORE removing claims: favors at-least-once delivery
|
|
265
|
+
# (a crash here re-sends, never drops). Successes are gone from the claims.
|
|
266
|
+
if failed:
|
|
267
|
+
try:
|
|
268
|
+
with open(spool, "a", encoding="utf-8") as fh:
|
|
269
|
+
fh.write("\n".join(failed) + "\n")
|
|
270
|
+
except OSError:
|
|
271
|
+
pass
|
|
272
|
+
|
|
273
|
+
for c in claims:
|
|
274
|
+
try:
|
|
275
|
+
c.unlink()
|
|
276
|
+
except OSError:
|
|
277
|
+
pass
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _post(url: str, body: str) -> bool:
|
|
281
|
+
try:
|
|
282
|
+
req = urllib.request.Request(
|
|
283
|
+
url,
|
|
284
|
+
data=body.encode("utf-8"),
|
|
285
|
+
headers={"Content-Type": "application/json"},
|
|
286
|
+
method="POST",
|
|
287
|
+
)
|
|
288
|
+
with urllib.request.urlopen(req, timeout=HTTP_TIMEOUT_SECONDS) as resp:
|
|
289
|
+
return 200 <= resp.status < 300
|
|
290
|
+
except Exception:
|
|
291
|
+
return False
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _flush_main() -> int:
|
|
295
|
+
"""Entry point for the detached flush subprocess (`-m agent_wallet.telemetry --flush`)."""
|
|
296
|
+
try:
|
|
297
|
+
_flush()
|
|
298
|
+
except Exception:
|
|
299
|
+
pass
|
|
300
|
+
return 0
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
if __name__ == "__main__":
|
|
304
|
+
if "--flush" in sys.argv[1:]:
|
|
305
|
+
raise SystemExit(_flush_main())
|
|
306
|
+
raise SystemExit(0)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "agent-wallet",
|
|
3
3
|
"name": "Agent Wallet",
|
|
4
4
|
"description": "Plugin-friendly wallet backend for OpenClaw agents with safe wallet tools and runtime instructions across Solana, local BTC, and local EVM.",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.44",
|
|
6
6
|
"skills": ["skills/wallet-operator"],
|
|
7
7
|
"configSchema": {
|
|
8
8
|
"type": "object",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-wallet",
|
|
3
3
|
"displayName": "Agent Wallet",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.44",
|
|
5
5
|
"description": "Claude Code bridge for the existing AgentLayer wallet runtime. Connects to Solana, Bitcoin, and EVM wallets without creating a new one.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "AgentLayer"
|
|
@@ -30,7 +30,36 @@ Primary design rules:
|
|
|
30
30
|
|
|
31
31
|
## Installation
|
|
32
32
|
|
|
33
|
-
###
|
|
33
|
+
### From inside Claude Code (no terminal needed)
|
|
34
|
+
|
|
35
|
+
The plugin ships in a git marketplace at the repo root, so you can install it —
|
|
36
|
+
and its backend runtime — without leaving the CLI. Two commands, then restart:
|
|
37
|
+
|
|
38
|
+
```text
|
|
39
|
+
/plugin marketplace add lopushok9/Agent-Layer
|
|
40
|
+
/plugin install agent-wallet@agentlayer
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Restart Claude Code (or `/reload-plugins`). On the next session start a
|
|
44
|
+
`SessionStart` hook runs `scripts/bootstrap_backend.sh`, a thin bridge to the
|
|
45
|
+
npm installer (`npx @agentlayer.tech/wallet install --yes`). The marketplace only
|
|
46
|
+
copies this MCP bridge into Claude Code's cache; the bootstrap step lays down the
|
|
47
|
+
Python backend runtime (venv + `agent_wallet` + `server.py`) that the bridge
|
|
48
|
+
talks to, with the wallet configured out of the box. It is idempotent and a
|
|
49
|
+
no-op once the backend is healthy.
|
|
50
|
+
|
|
51
|
+
- `/wallet-setup` — install (or repair) the backend explicitly instead of
|
|
52
|
+
waiting for the hook (requires `/reload-plugins` first so the command is
|
|
53
|
+
registered).
|
|
54
|
+
- `AGENT_WALLET_AUTO_BOOTSTRAP=0` — opt out of the auto-install: the
|
|
55
|
+
`SessionStart` hook then only reminds you to run `/wallet-setup` instead of
|
|
56
|
+
installing the backend itself.
|
|
57
|
+
|
|
58
|
+
For near-zero typed commands, pre-register the marketplace in
|
|
59
|
+
`.claude/settings.json` with `extraKnownMarketplaces` + `enabledPlugins` so
|
|
60
|
+
Claude Code prompts to install on trust.
|
|
61
|
+
|
|
62
|
+
### Automated via npm
|
|
34
63
|
|
|
35
64
|
```bash
|
|
36
65
|
npx @agentlayer.tech/wallet install --yes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Install or repair the AgentLayer wallet backend runtime without leaving Claude Code.
|
|
3
|
+
allowed-tools: Bash(sh:*), Bash(npx:*)
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Install (or repair) the AgentLayer wallet backend that this plugin bridges to.
|
|
7
|
+
|
|
8
|
+
Run the bootstrap bridge to the npm installer:
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
sh "${CLAUDE_PLUGIN_ROOT}/scripts/bootstrap_backend.sh" install
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Then report the outcome to the user:
|
|
15
|
+
|
|
16
|
+
- If it succeeded, tell them the backend is installed and that they should
|
|
17
|
+
restart Claude Code (or reload the `agent-wallet` plugin) so the wallet tools
|
|
18
|
+
become available.
|
|
19
|
+
- If it failed because Node.js or Python is missing, relay the exact requirement
|
|
20
|
+
(Node.js 18+, Python >= 3.10 with venv) and the manual fallback command from
|
|
21
|
+
the script output.
|
|
22
|
+
|
|
23
|
+
Do not pass or generate any secrets. The npm installer manages the local wallet
|
|
24
|
+
keys and sealed secrets under `OPENCLAW_HOME` on its own.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"SessionStart": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "startup|resume|clear",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"timeout": 300,
|
|
10
|
+
"command": "sh -c 'if [ \"${AGENT_WALLET_AUTO_BOOTSTRAP:-1}\" = \"1\" ]; then \"${CLAUDE_PLUGIN_ROOT}/scripts/bootstrap_backend.sh\" install >&2; else \"${CLAUDE_PLUGIN_ROOT}/scripts/bootstrap_backend.sh\" check >/dev/null 2>&1 || echo \"AgentLayer wallet backend is not installed. Run /wallet-setup to install it.\"; fi'"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
# bootstrap_backend.sh — the in-CLI bridge to the npm installer.
|
|
5
|
+
#
|
|
6
|
+
# Claude Code's plugin marketplace only copies the plugin files (this MCP bridge
|
|
7
|
+
# and its skill) into the cache. It does NOT lay down the Python backend runtime
|
|
8
|
+
# (venv + agent_wallet package + server.py + sealed-secret handling) that the
|
|
9
|
+
# bridge talks to. This script closes that gap: it detects whether the backend
|
|
10
|
+
# runtime is present and, if not, delegates to the existing npm installer
|
|
11
|
+
# (`npx @agentlayer.tech/wallet install --yes`) so the whole thing can be set up
|
|
12
|
+
# without leaving the Claude Code CLI.
|
|
13
|
+
#
|
|
14
|
+
# Modes:
|
|
15
|
+
# bootstrap_backend.sh check Report readiness only. Exit 0 if ready, 1 if not.
|
|
16
|
+
# Never installs anything.
|
|
17
|
+
# bootstrap_backend.sh install Ensure the backend is installed (default).
|
|
18
|
+
# Idempotent: a no-op when already healthy.
|
|
19
|
+
#
|
|
20
|
+
# Used by:
|
|
21
|
+
# - /wallet-setup slash command -> `install` (explicit, user-initiated).
|
|
22
|
+
# - SessionStart hook -> `install` by default, so the backend sets
|
|
23
|
+
# itself up on first session. Set
|
|
24
|
+
# AGENT_WALLET_AUTO_BOOTSTRAP=0 to opt out and
|
|
25
|
+
# get only a soft hint to run /wallet-setup.
|
|
26
|
+
|
|
27
|
+
MODE=${1:-install}
|
|
28
|
+
|
|
29
|
+
PACKAGE_SPEC=${AGENT_WALLET_BOOTSTRAP_PACKAGE:-"@agentlayer.tech/wallet@latest"}
|
|
30
|
+
OPENCLAW_HOME=${OPENCLAW_HOME:-"$HOME/.openclaw"}
|
|
31
|
+
RUNTIME_CURRENT="$OPENCLAW_HOME/agent-wallet-runtime/current"
|
|
32
|
+
|
|
33
|
+
# Resolve this script's plugin root with physical paths (pwd -P), the same way
|
|
34
|
+
# run_mcp.sh does, so a marketplace symlink does not break the "../../../codex"
|
|
35
|
+
# sibling math below.
|
|
36
|
+
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)
|
|
37
|
+
PLUGIN_ROOT=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd -P)
|
|
38
|
+
|
|
39
|
+
log() {
|
|
40
|
+
printf '%s\n' "$*" >&2
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# Locate server.py exactly like run_mcp.sh: the plugin's own copy, then the codex
|
|
44
|
+
# sibling in a repo checkout, then the installed runtime package. Keeping this in
|
|
45
|
+
# lock-step with run_mcp.sh is what avoids a false "not installed" verdict in a
|
|
46
|
+
# dev checkout, where run_mcp.sh resolves the codex sibling but this script used
|
|
47
|
+
# to look only at the runtime path.
|
|
48
|
+
resolve_server() {
|
|
49
|
+
if [ -f "$PLUGIN_ROOT/server.py" ]; then
|
|
50
|
+
printf '%s' "$PLUGIN_ROOT/server.py"
|
|
51
|
+
elif [ -f "$PLUGIN_ROOT/../../../codex/plugins/agent-wallet/server.py" ]; then
|
|
52
|
+
printf '%s' "$PLUGIN_ROOT/../../../codex/plugins/agent-wallet/server.py"
|
|
53
|
+
elif [ -f "$RUNTIME_CURRENT/codex/plugins/agent-wallet/server.py" ]; then
|
|
54
|
+
printf '%s' "$RUNTIME_CURRENT/codex/plugins/agent-wallet/server.py"
|
|
55
|
+
else
|
|
56
|
+
printf '%s' ''
|
|
57
|
+
fi
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
resolve_python() {
|
|
61
|
+
if [ -n "${AGENT_WALLET_PYTHON:-}" ]; then
|
|
62
|
+
printf '%s' "$AGENT_WALLET_PYTHON"
|
|
63
|
+
elif [ -x "$RUNTIME_CURRENT/agent-wallet/.venv/bin/python" ]; then
|
|
64
|
+
printf '%s' "$RUNTIME_CURRENT/agent-wallet/.venv/bin/python"
|
|
65
|
+
elif [ -x "$RUNTIME_CURRENT/agent-wallet/.runtime-venv/bin/python" ]; then
|
|
66
|
+
printf '%s' "$RUNTIME_CURRENT/agent-wallet/.runtime-venv/bin/python"
|
|
67
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
68
|
+
printf '%s' python3
|
|
69
|
+
else
|
|
70
|
+
printf '%s' ''
|
|
71
|
+
fi
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# Readiness proxy: the resolved server.py exists and parses (ast.parse, no
|
|
75
|
+
# bytecode written) — the same shallow check run_mcp.sh and `doctor` run before
|
|
76
|
+
# exec. It deliberately does NOT verify that the venv/dependencies are installed:
|
|
77
|
+
# importing or running the server would be too slow and flaky for a SessionStart
|
|
78
|
+
# hook and risks false-negative reinstall loops. run_mcp.sh surfaces any missing
|
|
79
|
+
# dependency with a clear error at first tool use, so this stays in lock-step
|
|
80
|
+
# with what actually runs the server rather than being stricter than it.
|
|
81
|
+
backend_ready() {
|
|
82
|
+
server=$(resolve_server)
|
|
83
|
+
[ -n "$server" ] || return 1
|
|
84
|
+
py=$(resolve_python)
|
|
85
|
+
[ -n "$py" ] || return 1
|
|
86
|
+
"$py" -c 'import sys, ast; ast.parse(open(sys.argv[1], encoding="utf-8").read())' "$server" 2>/dev/null
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if backend_ready; then
|
|
90
|
+
[ "$MODE" = "check" ] || log "AgentLayer wallet backend already installed (server: $(resolve_server))."
|
|
91
|
+
exit 0
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
if [ "$MODE" = "check" ]; then
|
|
95
|
+
log "AgentLayer wallet backend is not installed yet."
|
|
96
|
+
log "Run /wallet-setup inside Claude Code to install it."
|
|
97
|
+
exit 1
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
# --- install path -----------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
if ! command -v npx >/dev/null 2>&1; then
|
|
103
|
+
log "Cannot install the AgentLayer wallet backend: npx (Node.js) was not found on PATH."
|
|
104
|
+
log "Install Node.js 18+ and re-run /wallet-setup, or run manually:"
|
|
105
|
+
log " npx $PACKAGE_SPEC install --yes"
|
|
106
|
+
exit 1
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
if ! command -v python3 >/dev/null 2>&1; then
|
|
110
|
+
log "Warning: python3 was not found on PATH. The installer needs Python >= 3.10 with venv."
|
|
111
|
+
log "Install Python and re-run /wallet-setup if the install below fails."
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
log "Installing the AgentLayer wallet backend runtime via npm (this may take a minute)…"
|
|
115
|
+
log " -> npx $PACKAGE_SPEC install --yes"
|
|
116
|
+
if ! npx -y "$PACKAGE_SPEC" install --yes; then
|
|
117
|
+
log "Backend install failed. Ensure Node.js 18+ and Python >= 3.10 (with venv) are installed, then re-run /wallet-setup."
|
|
118
|
+
exit 1
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
# Re-pin the Claude Code cache copies so run_mcp.sh resolves OPENCLAW_HOME and the
|
|
122
|
+
# freshly installed runtime correctly (pinClaudeCacheCopies / marketplace wiring).
|
|
123
|
+
# --skip-enable: the plugin is already registered via the marketplace, so we only
|
|
124
|
+
# want the file pinning, not another `claude plugin install`.
|
|
125
|
+
log "Wiring the Claude Code bridge to the installed runtime…"
|
|
126
|
+
npx -y "$PACKAGE_SPEC" claude-code install --yes --skip-enable || \
|
|
127
|
+
log "Note: could not re-pin the Claude Code bridge automatically; it will still resolve the default runtime."
|
|
128
|
+
|
|
129
|
+
if backend_ready; then
|
|
130
|
+
log "Done. The AgentLayer wallet backend is installed."
|
|
131
|
+
log "Restart Claude Code (or reload the agent-wallet plugin) to activate the wallet tools."
|
|
132
|
+
exit 0
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
log "Install completed but the backend did not verify. Run: npx $PACKAGE_SPEC doctor --deep"
|
|
136
|
+
exit 1
|
|
@@ -27,7 +27,7 @@ elif [ -f "$CODEX_SERVER" ]; then
|
|
|
27
27
|
elif [ -f "$RUNTIME_CODEX_DIR/server.py" ]; then
|
|
28
28
|
SERVER_PY=$(CDPATH= cd -- "$RUNTIME_CODEX_DIR" && pwd -P)/server.py
|
|
29
29
|
else
|
|
30
|
-
printf '{"error":"agent-wallet server.py not found in plugin, codex sibling, or runtime package.","fix":"npx @agentlayer.tech/wallet install --yes"}\n' >&2
|
|
30
|
+
printf '{"error":"agent-wallet backend not installed yet (server.py not found in plugin, codex sibling, or runtime package).","fix":"Run /wallet-setup inside Claude Code, or: npx @agentlayer.tech/wallet install --yes"}\n' >&2
|
|
31
31
|
exit 1
|
|
32
32
|
fi
|
|
33
33
|
|
|
@@ -58,4 +58,9 @@ PY
|
|
|
58
58
|
exit 1
|
|
59
59
|
fi
|
|
60
60
|
|
|
61
|
+
# Tag anonymous telemetry with this frontend so adoption can be split per host
|
|
62
|
+
# (claude-code / codex / hermes / openclaw). An explicit override still wins.
|
|
63
|
+
: "${AGENT_WALLET_HOST:=claude-code}"
|
|
64
|
+
export AGENT_WALLET_HOST
|
|
65
|
+
|
|
61
66
|
exec "$PYTHON_BIN" "$SERVER_PY"
|
|
@@ -39,4 +39,9 @@ PY
|
|
|
39
39
|
exit 1
|
|
40
40
|
fi
|
|
41
41
|
|
|
42
|
+
# Tag anonymous telemetry with this frontend so adoption can be split per host
|
|
43
|
+
# (claude-code / codex / hermes / openclaw). An explicit override still wins.
|
|
44
|
+
: "${AGENT_WALLET_HOST:=codex}"
|
|
45
|
+
export AGENT_WALLET_HOST
|
|
46
|
+
|
|
42
47
|
exec "$PYTHON_BIN" "$PLUGIN_ROOT/server.py"
|
|
@@ -319,6 +319,9 @@ def _cli_env(package_root: Path) -> dict[str, str]:
|
|
|
319
319
|
env = dict(os.environ)
|
|
320
320
|
prior = env.get("PYTHONPATH", "")
|
|
321
321
|
env["PYTHONPATH"] = str(package_root) if not prior else f"{package_root}{os.pathsep}{prior}"
|
|
322
|
+
# Tag anonymous telemetry with this frontend so adoption can be split per
|
|
323
|
+
# host (claude-code / codex / hermes / openclaw). An explicit override wins.
|
|
324
|
+
env.setdefault("AGENT_WALLET_HOST", "hermes")
|
|
322
325
|
if not env.get("AGENT_WALLET_BOOT_KEY"):
|
|
323
326
|
key_file = env.get("AGENT_WALLET_BOOT_KEY_FILE", "").strip()
|
|
324
327
|
if key_file:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentlayer.tech/wallet",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.44",
|
|
4
4
|
"description": "NPM installer for the OpenClaw Agent Wallet local runtime.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"agent-wallet/pyproject.toml",
|
|
47
47
|
".openclaw/AGENTS.md",
|
|
48
48
|
".openclaw/extensions/agent-wallet/",
|
|
49
|
+
".claude-plugin/marketplace.json",
|
|
49
50
|
"codex/plugins/agent-wallet/",
|
|
50
51
|
"claude-code/plugins/agent-wallet/",
|
|
51
52
|
"hermes/plugins/agent_wallet/",
|