@agentlayer.tech/wallet 0.1.0

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.
Files changed (96) hide show
  1. package/.openclaw/AGENTS.md +98 -0
  2. package/.openclaw/extensions/agent-wallet/README.md +127 -0
  3. package/.openclaw/extensions/agent-wallet/index.ts +1520 -0
  4. package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +184 -0
  5. package/.openclaw/extensions/agent-wallet/package.json +11 -0
  6. package/.openclaw/extensions/agent-wallet/skills/wallet-operator/SKILL.md +20 -0
  7. package/CHANGELOG.md +42 -0
  8. package/LICENSE +104 -0
  9. package/README.md +332 -0
  10. package/RELEASING.md +204 -0
  11. package/agent-wallet/.env.example +62 -0
  12. package/agent-wallet/AGENTS.md +129 -0
  13. package/agent-wallet/README.md +527 -0
  14. package/agent-wallet/agent_wallet/__init__.py +11 -0
  15. package/agent-wallet/agent_wallet/approval.py +161 -0
  16. package/agent-wallet/agent_wallet/bootstrap.py +178 -0
  17. package/agent-wallet/agent_wallet/btc_user_wallets.py +217 -0
  18. package/agent-wallet/agent_wallet/config.py +382 -0
  19. package/agent-wallet/agent_wallet/encrypted_storage.py +161 -0
  20. package/agent-wallet/agent_wallet/evm_user_wallets.py +370 -0
  21. package/agent-wallet/agent_wallet/exceptions.py +9 -0
  22. package/agent-wallet/agent_wallet/file_ops.py +34 -0
  23. package/agent-wallet/agent_wallet/http_client.py +25 -0
  24. package/agent-wallet/agent_wallet/models.py +66 -0
  25. package/agent-wallet/agent_wallet/nonce_registry.py +59 -0
  26. package/agent-wallet/agent_wallet/openclaw_adapter.py +5128 -0
  27. package/agent-wallet/agent_wallet/openclaw_cli.py +626 -0
  28. package/agent-wallet/agent_wallet/openclaw_runtime.py +272 -0
  29. package/agent-wallet/agent_wallet/plugin_bundle.py +42 -0
  30. package/agent-wallet/agent_wallet/providers/__init__.py +1 -0
  31. package/agent-wallet/agent_wallet/providers/bags.py +259 -0
  32. package/agent-wallet/agent_wallet/providers/evm_portfolio.py +470 -0
  33. package/agent-wallet/agent_wallet/providers/jupiter.py +567 -0
  34. package/agent-wallet/agent_wallet/providers/kamino.py +215 -0
  35. package/agent-wallet/agent_wallet/providers/lifi.py +277 -0
  36. package/agent-wallet/agent_wallet/providers/solana_rpc.py +470 -0
  37. package/agent-wallet/agent_wallet/providers/wdk_btc_local.py +114 -0
  38. package/agent-wallet/agent_wallet/providers/wdk_evm_local.py +205 -0
  39. package/agent-wallet/agent_wallet/sealed_keys.py +61 -0
  40. package/agent-wallet/agent_wallet/solana_stake.py +103 -0
  41. package/agent-wallet/agent_wallet/solana_tx.py +93 -0
  42. package/agent-wallet/agent_wallet/spending_limits.py +101 -0
  43. package/agent-wallet/agent_wallet/transaction_policy.py +518 -0
  44. package/agent-wallet/agent_wallet/user_wallets.py +355 -0
  45. package/agent-wallet/agent_wallet/validation.py +31 -0
  46. package/agent-wallet/agent_wallet/wallet_layer/__init__.py +1 -0
  47. package/agent-wallet/agent_wallet/wallet_layer/base.py +808 -0
  48. package/agent-wallet/agent_wallet/wallet_layer/base58.py +44 -0
  49. package/agent-wallet/agent_wallet/wallet_layer/factory.py +102 -0
  50. package/agent-wallet/agent_wallet/wallet_layer/solana.py +4252 -0
  51. package/agent-wallet/agent_wallet/wallet_layer/wdk_btc.py +272 -0
  52. package/agent-wallet/agent_wallet/wallet_layer/wdk_evm.py +1628 -0
  53. package/agent-wallet/examples/bootstrap_wallet.py +21 -0
  54. package/agent-wallet/examples/openclaw_runtime_onboarding.py +28 -0
  55. package/agent-wallet/examples/openclaw_user_wallet_example.py +31 -0
  56. package/agent-wallet/examples/openclaw_wallet_adapter_example.py +33 -0
  57. package/agent-wallet/openclaw.plugin.json +138 -0
  58. package/agent-wallet/pyproject.toml +31 -0
  59. package/agent-wallet/scripts/bootstrap_openclaw_btc.py +278 -0
  60. package/agent-wallet/scripts/build_release_bundle.py +188 -0
  61. package/agent-wallet/scripts/finalize_openclaw_local_wallet_config.py +121 -0
  62. package/agent-wallet/scripts/install_agent_wallet.py +505 -0
  63. package/agent-wallet/scripts/install_openclaw_local_config.py +226 -0
  64. package/agent-wallet/scripts/install_openclaw_sealed_keys.py +105 -0
  65. package/agent-wallet/scripts/manage_openclaw_btc_wallet.py +244 -0
  66. package/agent-wallet/scripts/reveal_btc_seed.sh +130 -0
  67. package/agent-wallet/scripts/security_utils.py +37 -0
  68. package/agent-wallet/scripts/setup_btc_wallet.sh +146 -0
  69. package/agent-wallet/scripts/switch_openclaw_wallet_network.py +106 -0
  70. package/agent-wallet/skills/wallet-operator/SKILL.md +128 -0
  71. package/bin/openclaw-agent-wallet.mjs +487 -0
  72. package/install-from-github.sh +134 -0
  73. package/package.json +61 -0
  74. package/setup.sh +40 -0
  75. package/wdk-btc-wallet/README.md +325 -0
  76. package/wdk-btc-wallet/bootstrap.sh +22 -0
  77. package/wdk-btc-wallet/package-lock.json +1839 -0
  78. package/wdk-btc-wallet/package.json +18 -0
  79. package/wdk-btc-wallet/run-local.sh +21 -0
  80. package/wdk-btc-wallet/src/config.js +160 -0
  81. package/wdk-btc-wallet/src/json.js +35 -0
  82. package/wdk-btc-wallet/src/local_vault.js +432 -0
  83. package/wdk-btc-wallet/src/network_state.js +84 -0
  84. package/wdk-btc-wallet/src/server.js +257 -0
  85. package/wdk-btc-wallet/src/wdk_btc_wallet.js +332 -0
  86. package/wdk-evm-wallet/README.md +183 -0
  87. package/wdk-evm-wallet/bootstrap.sh +8 -0
  88. package/wdk-evm-wallet/package-lock.json +2340 -0
  89. package/wdk-evm-wallet/package.json +23 -0
  90. package/wdk-evm-wallet/run-local.sh +12 -0
  91. package/wdk-evm-wallet/src/config.js +274 -0
  92. package/wdk-evm-wallet/src/json.js +35 -0
  93. package/wdk-evm-wallet/src/local_vault.js +430 -0
  94. package/wdk-evm-wallet/src/network_state.js +92 -0
  95. package/wdk-evm-wallet/src/server.js +575 -0
  96. package/wdk-evm-wallet/src/wdk_evm_wallet.js +4981 -0
@@ -0,0 +1,505 @@
1
+ """One-command installer for the local OpenClaw agent-wallet setup."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import os
8
+ import shutil
9
+ import subprocess
10
+ import sys
11
+ import tempfile
12
+ import venv
13
+ from pathlib import Path
14
+
15
+ INCLUDED_RUNTIME_ROOT_FILES = [
16
+ ".env.example",
17
+ "AGENTS.md",
18
+ "README.md",
19
+ "CHANGELOG.md",
20
+ "RELEASING.md",
21
+ "install-from-github.sh",
22
+ "requirements.txt",
23
+ "setup.sh",
24
+ ]
25
+ INCLUDED_RUNTIME_TOP_LEVEL_DIRS = [
26
+ ".openclaw",
27
+ "agent-wallet",
28
+ "agent-a2a-gateway",
29
+ "wdk-btc-wallet",
30
+ "wdk-evm-wallet",
31
+ ]
32
+ EXCLUDED_RUNTIME_DIR_NAMES = {
33
+ ".git",
34
+ ".venv",
35
+ ".pytest_cache",
36
+ ".ruff_cache",
37
+ ".runtime-venv",
38
+ "__pycache__",
39
+ "dist",
40
+ "extensions-local",
41
+ "graphify-out",
42
+ "node_modules",
43
+ }
44
+ EXCLUDED_RUNTIME_FILE_NAMES = {
45
+ ".DS_Store",
46
+ }
47
+ EXCLUDED_RUNTIME_SUFFIXES = {
48
+ ".pyc",
49
+ ".pyo",
50
+ }
51
+
52
+
53
+ def _repo_root() -> Path:
54
+ return Path(__file__).resolve().parents[2]
55
+
56
+
57
+ def _package_root() -> Path:
58
+ return Path(__file__).resolve().parents[1]
59
+
60
+
61
+ def _extension_path() -> Path:
62
+ return _repo_root() / ".openclaw" / "extensions" / "agent-wallet"
63
+
64
+
65
+ def _default_wdk_btc_root() -> Path:
66
+ return _repo_root() / "wdk-btc-wallet"
67
+
68
+
69
+ def _default_wdk_evm_root() -> Path:
70
+ return _repo_root() / "wdk-evm-wallet"
71
+
72
+
73
+ def _default_env_path() -> Path:
74
+ return _package_root() / ".env"
75
+
76
+
77
+ def _default_env_example_path() -> Path:
78
+ return _package_root() / ".env.example"
79
+
80
+
81
+ def _default_config_path() -> Path:
82
+ return Path(os.path.expanduser("~/.openclaw/openclaw.json"))
83
+
84
+
85
+ def _default_venv_path() -> Path:
86
+ return _package_root() / ".venv"
87
+
88
+
89
+ def _default_user_id() -> str:
90
+ return f"{os.getenv('USER', 'openclaw-user')}-local"
91
+
92
+
93
+ def _default_npm_bin() -> str:
94
+ return shutil.which("npm") or "npm"
95
+
96
+
97
+ def _resolve_openclaw_home() -> Path:
98
+ return Path(os.path.expanduser(os.getenv("OPENCLAW_HOME", "~/.openclaw")))
99
+
100
+
101
+ def _default_runtime_root() -> Path:
102
+ explicit_target = os.getenv("OPENCLAW_INSTALL_TARGET", "").strip()
103
+ if explicit_target:
104
+ return Path(explicit_target).expanduser()
105
+ explicit_root = os.getenv("OPENCLAW_INSTALL_ROOT", "").strip()
106
+ if explicit_root:
107
+ return Path(explicit_root).expanduser() / "current"
108
+ return _resolve_openclaw_home() / "agent-wallet-runtime" / "current"
109
+
110
+
111
+ def _resolve_sealed_keys_path() -> Path:
112
+ return _resolve_openclaw_home() / "sealed_keys.json"
113
+
114
+
115
+ def _atomic_write_text(path: Path, content: str, *, mode: int = 0o600) -> None:
116
+ path.parent.mkdir(parents=True, exist_ok=True)
117
+ fd, temp_path = tempfile.mkstemp(prefix=f".{path.name}.", dir=str(path.parent))
118
+ try:
119
+ with os.fdopen(fd, "w", encoding="utf-8") as handle:
120
+ handle.write(content)
121
+ handle.flush()
122
+ os.fsync(handle.fileno())
123
+ os.chmod(temp_path, mode)
124
+ os.replace(temp_path, path)
125
+ except Exception:
126
+ try:
127
+ os.unlink(temp_path)
128
+ except FileNotFoundError:
129
+ pass
130
+ raise
131
+
132
+
133
+ def _chmod_if_exists(path: Path, mode: int = 0o600) -> None:
134
+ try:
135
+ path.chmod(mode)
136
+ except FileNotFoundError:
137
+ return
138
+
139
+
140
+ def build_parser() -> argparse.ArgumentParser:
141
+ parser = argparse.ArgumentParser(description=__doc__)
142
+ parser.add_argument("--config-path", default=str(_default_config_path()))
143
+ parser.add_argument("--env-path", default=str(_default_env_path()))
144
+ parser.add_argument("--env-example-path", default=str(_default_env_example_path()))
145
+ parser.add_argument("--venv-path", default=str(_default_venv_path()))
146
+ parser.add_argument("--package-root", default=str(_package_root()))
147
+ parser.add_argument("--extension-path", default=str(_extension_path()))
148
+ parser.add_argument("--wdk-btc-root", default=str(_default_wdk_btc_root()))
149
+ parser.add_argument("--wdk-evm-root", default=str(_default_wdk_evm_root()))
150
+ parser.add_argument("--runtime-root", default=str(_default_runtime_root()))
151
+ parser.add_argument("--npm-bin", default=_default_npm_bin())
152
+ parser.add_argument("--plugin-id", default="agent-wallet")
153
+ parser.add_argument("--user-id", default=_default_user_id())
154
+ parser.add_argument("--backend", default="solana_local")
155
+ parser.add_argument("--network", default="devnet")
156
+ parser.add_argument("--rpc-url", default="")
157
+ parser.add_argument("--rpc-urls", default="")
158
+ parser.add_argument("--sign-only", action=argparse.BooleanOptionalAction, default=False)
159
+ parser.add_argument("--sync-runtime", action=argparse.BooleanOptionalAction, default=True)
160
+ parser.add_argument("--install-from-runtime", action=argparse.BooleanOptionalAction, default=False)
161
+ parser.add_argument("--skip-python-setup", action=argparse.BooleanOptionalAction, default=False)
162
+ parser.add_argument("--skip-node-setup", action=argparse.BooleanOptionalAction, default=False)
163
+ parser.add_argument("--dry-run", action=argparse.BooleanOptionalAction, default=False)
164
+ return parser
165
+
166
+
167
+ def _infer_source_root(
168
+ package_root: Path,
169
+ extension_path: Path,
170
+ wdk_btc_root: Path,
171
+ wdk_evm_root: Path,
172
+ ) -> Path:
173
+ candidates = [
174
+ package_root.parent,
175
+ Path(os.path.commonpath([package_root, extension_path, wdk_btc_root, wdk_evm_root])),
176
+ ]
177
+ seen: set[Path] = set()
178
+ for candidate in candidates:
179
+ resolved = candidate.resolve()
180
+ if resolved in seen:
181
+ continue
182
+ seen.add(resolved)
183
+ if (
184
+ (resolved / "agent-wallet").resolve() == package_root
185
+ and (resolved / ".openclaw" / "extensions" / "agent-wallet").resolve() == extension_path
186
+ and (resolved / "wdk-btc-wallet").resolve() == wdk_btc_root
187
+ and (resolved / "wdk-evm-wallet").resolve() == wdk_evm_root
188
+ and (resolved / "setup.sh").exists()
189
+ ):
190
+ return resolved
191
+ raise SystemExit(
192
+ "Could not infer the source root for runtime sync. Expected package-root, extension-path, "
193
+ "wdk-btc-root, and wdk-evm-root to belong to the same repo checkout."
194
+ )
195
+
196
+
197
+ def _ignore_runtime_entries(_directory: str, names: list[str]) -> set[str]:
198
+ ignored: set[str] = set()
199
+ for name in names:
200
+ if name in EXCLUDED_RUNTIME_DIR_NAMES:
201
+ ignored.add(name)
202
+ continue
203
+ if name in EXCLUDED_RUNTIME_FILE_NAMES:
204
+ ignored.add(name)
205
+ continue
206
+ if any(name.endswith(suffix) for suffix in EXCLUDED_RUNTIME_SUFFIXES):
207
+ ignored.add(name)
208
+ return ignored
209
+
210
+
211
+ def _replace_if_type_mismatch(source: Path, target: Path) -> None:
212
+ if not target.exists():
213
+ return
214
+ if source.is_dir() and target.is_file():
215
+ target.unlink()
216
+ elif source.is_file() and target.is_dir():
217
+ shutil.rmtree(target)
218
+
219
+
220
+ def _sync_runtime_tree(source_root: Path, runtime_root: Path) -> dict[str, object]:
221
+ source_root = source_root.resolve()
222
+ runtime_root = runtime_root.resolve()
223
+ if source_root == runtime_root:
224
+ return {
225
+ "enabled": True,
226
+ "skipped": True,
227
+ "reason": "source_root_matches_runtime_root",
228
+ "source_root": str(source_root),
229
+ "runtime_root": str(runtime_root),
230
+ "copied_paths": [],
231
+ }
232
+
233
+ runtime_root.mkdir(parents=True, exist_ok=True)
234
+ copied_paths: list[str] = []
235
+ for relative in INCLUDED_RUNTIME_ROOT_FILES + INCLUDED_RUNTIME_TOP_LEVEL_DIRS:
236
+ source = source_root / relative
237
+ if not source.exists():
238
+ continue
239
+ target = runtime_root / relative
240
+ _replace_if_type_mismatch(source, target)
241
+ if source.is_dir():
242
+ shutil.copytree(
243
+ source,
244
+ target,
245
+ dirs_exist_ok=True,
246
+ ignore=_ignore_runtime_entries,
247
+ )
248
+ else:
249
+ target.parent.mkdir(parents=True, exist_ok=True)
250
+ shutil.copy2(source, target)
251
+ copied_paths.append(relative)
252
+
253
+ return {
254
+ "enabled": True,
255
+ "skipped": False,
256
+ "reason": None,
257
+ "source_root": str(source_root),
258
+ "runtime_root": str(runtime_root),
259
+ "copied_paths": copied_paths,
260
+ }
261
+
262
+
263
+ def _ensure_env_file(env_path: Path, env_example_path: Path) -> bool:
264
+ if env_path.exists():
265
+ return False
266
+ env_path.parent.mkdir(parents=True, exist_ok=True)
267
+ shutil.copyfile(env_example_path, env_path)
268
+ _chmod_if_exists(env_path, 0o600)
269
+ return True
270
+
271
+
272
+ def _ensure_openclaw_config(config_path: Path) -> bool:
273
+ if config_path.exists():
274
+ return False
275
+ config_path.parent.mkdir(parents=True, exist_ok=True)
276
+ _atomic_write_text(
277
+ config_path,
278
+ json.dumps({"plugins": {"entries": {}}, "tools": {"alsoAllow": []}}, indent=2) + "\n",
279
+ mode=0o600,
280
+ )
281
+ return True
282
+
283
+
284
+ def _venv_python(venv_path: Path) -> Path:
285
+ if os.name == "nt":
286
+ return venv_path / "Scripts" / "python.exe"
287
+ return venv_path / "bin" / "python"
288
+
289
+
290
+ def _ensure_python_runtime(venv_path: Path, package_root: Path) -> tuple[Path, bool]:
291
+ created = False
292
+ python_bin = _venv_python(venv_path)
293
+ if not python_bin.exists():
294
+ venv.EnvBuilder(with_pip=True).create(venv_path)
295
+ created = True
296
+
297
+ subprocess.run(
298
+ [str(python_bin), "-m", "pip", "install", "-e", str(package_root)],
299
+ check=True,
300
+ )
301
+ return python_bin, created
302
+
303
+
304
+ def _ensure_node_runtime(npm_bin: str, project_root: Path) -> dict[str, object]:
305
+ package_json = project_root / "package.json"
306
+ if not package_json.exists():
307
+ raise SystemExit(f"Missing package.json for Node runtime at '{project_root}'.")
308
+ package_lock = project_root / "package-lock.json"
309
+ command = [npm_bin, "ci"] if package_lock.exists() else [npm_bin, "install"]
310
+ subprocess.run(command, cwd=project_root, check=True)
311
+ return {
312
+ "project_root": str(project_root),
313
+ "package_json": str(package_json),
314
+ "package_lock": str(package_lock) if package_lock.exists() else None,
315
+ "command": command,
316
+ }
317
+
318
+
319
+ def _pending_env_names() -> list[str]:
320
+ pending: list[str] = []
321
+ boot_key = os.getenv("AGENT_WALLET_BOOT_KEY", "").strip()
322
+ sealed_path = _resolve_sealed_keys_path()
323
+ if not boot_key:
324
+ pending.append("AGENT_WALLET_BOOT_KEY")
325
+ if not sealed_path.exists():
326
+ pending.extend(["AGENT_WALLET_MASTER_KEY", "AGENT_WALLET_APPROVAL_SECRET"])
327
+ elif not sealed_path.exists():
328
+ if not os.getenv("AGENT_WALLET_MASTER_KEY", "").strip():
329
+ pending.append("AGENT_WALLET_MASTER_KEY")
330
+ if not os.getenv("AGENT_WALLET_APPROVAL_SECRET", "").strip():
331
+ pending.append("AGENT_WALLET_APPROVAL_SECRET")
332
+ return pending
333
+
334
+
335
+ def _build_next_steps(
336
+ python_bin: Path,
337
+ script_path: Path,
338
+ args: argparse.Namespace,
339
+ ) -> list[str]:
340
+ command = [
341
+ str(python_bin),
342
+ str(script_path),
343
+ "--config-path",
344
+ str(Path(args.config_path).expanduser()),
345
+ "--plugin-id",
346
+ args.plugin_id,
347
+ "--user-id",
348
+ args.user_id,
349
+ "--backend",
350
+ args.backend,
351
+ "--network",
352
+ args.network,
353
+ ]
354
+ if args.rpc_url.strip():
355
+ command.extend(["--rpc-url", args.rpc_url.strip()])
356
+ if args.rpc_urls.strip():
357
+ command.extend(["--rpc-urls", args.rpc_urls.strip()])
358
+ command.append("--sign-only" if args.sign_only else "--no-sign-only")
359
+ command.extend(["--extension-path", str(Path(args.extension_path).expanduser())])
360
+ command.extend(["--package-root", str(Path(args.package_root).expanduser())])
361
+ command.extend(["--python-bin", str(python_bin)])
362
+ return command
363
+
364
+
365
+ def main() -> None:
366
+ args = build_parser().parse_args()
367
+ source_package_root = Path(args.package_root).expanduser().resolve()
368
+ source_extension_path = Path(args.extension_path).expanduser().resolve()
369
+ source_wdk_btc_root = Path(args.wdk_btc_root).expanduser().resolve()
370
+ source_wdk_evm_root = Path(args.wdk_evm_root).expanduser().resolve()
371
+ runtime_root = Path(args.runtime_root).expanduser().resolve()
372
+ config_path = Path(args.config_path).expanduser()
373
+ env_path = Path(args.env_path).expanduser()
374
+ env_example_path = Path(args.env_example_path).expanduser()
375
+ venv_path = Path(args.venv_path).expanduser()
376
+ source_root = _infer_source_root(
377
+ source_package_root,
378
+ source_extension_path,
379
+ source_wdk_btc_root,
380
+ source_wdk_evm_root,
381
+ )
382
+
383
+ runtime_sync: dict[str, object]
384
+ if not args.sync_runtime:
385
+ runtime_sync = {
386
+ "enabled": False,
387
+ "skipped": True,
388
+ "reason": "sync_runtime_disabled",
389
+ "source_root": str(source_root),
390
+ "runtime_root": str(runtime_root),
391
+ "copied_paths": [],
392
+ }
393
+ elif args.dry_run:
394
+ runtime_sync = {
395
+ "enabled": True,
396
+ "skipped": False,
397
+ "reason": "dry_run",
398
+ "source_root": str(source_root),
399
+ "runtime_root": str(runtime_root),
400
+ "copied_paths": INCLUDED_RUNTIME_ROOT_FILES + INCLUDED_RUNTIME_TOP_LEVEL_DIRS,
401
+ }
402
+ else:
403
+ runtime_sync = _sync_runtime_tree(source_root, runtime_root)
404
+
405
+ if args.install_from_runtime:
406
+ package_root = runtime_root / "agent-wallet"
407
+ extension_path = runtime_root / ".openclaw" / "extensions" / "agent-wallet"
408
+ wdk_btc_root = runtime_root / "wdk-btc-wallet"
409
+ wdk_evm_root = runtime_root / "wdk-evm-wallet"
410
+ else:
411
+ package_root = source_package_root
412
+ extension_path = source_extension_path
413
+ wdk_btc_root = source_wdk_btc_root
414
+ wdk_evm_root = source_wdk_evm_root
415
+
416
+ install_config_script = package_root / "scripts" / "install_openclaw_local_config.py"
417
+ if args.install_from_runtime:
418
+ default_source_env_path = source_package_root / ".env"
419
+ default_source_venv_path = source_package_root / ".venv"
420
+ if env_path.resolve() == default_source_env_path.resolve():
421
+ env_path = package_root / ".env"
422
+ if venv_path.resolve() == default_source_venv_path.resolve():
423
+ venv_path = package_root / ".venv"
424
+ source_env_example_path = source_package_root / ".env.example"
425
+ if env_example_path.resolve() == source_env_example_path.resolve():
426
+ env_example_path = package_root / ".env.example"
427
+
428
+ env_created = _ensure_env_file(env_path, env_example_path)
429
+ config_created = _ensure_openclaw_config(config_path)
430
+
431
+ python_bin = Path(sys.executable)
432
+ venv_created = False
433
+ if not args.skip_python_setup:
434
+ if not args.dry_run:
435
+ python_bin, venv_created = _ensure_python_runtime(venv_path, package_root)
436
+ else:
437
+ python_bin = _venv_python(venv_path)
438
+
439
+ node_runtime = {
440
+ "skipped": bool(args.skip_node_setup),
441
+ "npm_bin": args.npm_bin,
442
+ "projects": [],
443
+ }
444
+ if not args.skip_node_setup:
445
+ if args.dry_run:
446
+ node_runtime["projects"] = [
447
+ {
448
+ "project_root": str(wdk_btc_root),
449
+ "command": [args.npm_bin, "ci" if (wdk_btc_root / "package-lock.json").exists() else "install"],
450
+ },
451
+ {
452
+ "project_root": str(wdk_evm_root),
453
+ "command": [args.npm_bin, "ci" if (wdk_evm_root / "package-lock.json").exists() else "install"],
454
+ },
455
+ ]
456
+ else:
457
+ node_runtime["projects"] = [
458
+ _ensure_node_runtime(args.npm_bin, wdk_btc_root),
459
+ _ensure_node_runtime(args.npm_bin, wdk_evm_root),
460
+ ]
461
+
462
+ backend_enabled = args.backend.strip().lower() not in {"", "none"}
463
+ pending_env = _pending_env_names() if backend_enabled else []
464
+ configured = False
465
+ configure_stdout = ""
466
+ if backend_enabled and not pending_env and not args.dry_run:
467
+ result = subprocess.run(
468
+ _build_next_steps(python_bin, install_config_script, args),
469
+ capture_output=True,
470
+ text=True,
471
+ check=True,
472
+ )
473
+ configured = True
474
+ configure_stdout = result.stdout
475
+
476
+ print(
477
+ json.dumps(
478
+ {
479
+ "ok": True,
480
+ "env_path": str(env_path),
481
+ "env_created": env_created,
482
+ "config_path": str(config_path),
483
+ "config_created": config_created,
484
+ "package_root": str(package_root),
485
+ "extension_path": str(extension_path),
486
+ "wdk_btc_root": str(wdk_btc_root),
487
+ "wdk_evm_root": str(wdk_evm_root),
488
+ "runtime_root": str(runtime_root),
489
+ "install_from_runtime": bool(args.install_from_runtime),
490
+ "python_bin": str(python_bin),
491
+ "venv_created": venv_created,
492
+ "node_runtime": node_runtime,
493
+ "runtime_sync": runtime_sync,
494
+ "configured": configured,
495
+ "pending_env": pending_env,
496
+ "next_configure_command": _build_next_steps(python_bin, install_config_script, args),
497
+ "configure_result": json.loads(configure_stdout) if configure_stdout else None,
498
+ },
499
+ indent=2,
500
+ )
501
+ )
502
+
503
+
504
+ if __name__ == "__main__":
505
+ main()