@agentlayer.tech/wallet 0.1.27 → 0.1.30

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 (56) hide show
  1. package/.openclaw/extensions/agent-wallet/README.md +4 -5
  2. package/.openclaw/extensions/agent-wallet/dist/index.js +31 -31
  3. package/.openclaw/extensions/agent-wallet/index.ts +31 -31
  4. package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +2 -2
  5. package/.openclaw/extensions/agent-wallet/package.json +1 -1
  6. package/CHANGELOG.md +52 -0
  7. package/README.md +9 -0
  8. package/agent-wallet/README.md +18 -22
  9. package/agent-wallet/agent_wallet/bootstrap.py +28 -12
  10. package/agent-wallet/agent_wallet/btc_user_wallets.py +2 -7
  11. package/agent-wallet/agent_wallet/config.py +99 -22
  12. package/agent-wallet/agent_wallet/evm_user_wallets.py +2 -14
  13. package/agent-wallet/agent_wallet/openclaw_adapter.py +72 -108
  14. package/agent-wallet/agent_wallet/openclaw_runtime.py +3 -12
  15. package/agent-wallet/agent_wallet/providers/kamino.py +21 -4
  16. package/agent-wallet/agent_wallet/providers/solana_rpc.py +0 -23
  17. package/agent-wallet/agent_wallet/providers/x402.py +198 -18
  18. package/agent-wallet/agent_wallet/user_wallets.py +4 -3
  19. package/agent-wallet/agent_wallet/wallet_layer/base.py +3 -3
  20. package/agent-wallet/agent_wallet/wallet_layer/factory.py +8 -5
  21. package/agent-wallet/agent_wallet/wallet_layer/solana.py +437 -44
  22. package/agent-wallet/agent_wallet/wallet_layer/wdk_btc.py +2 -8
  23. package/agent-wallet/agent_wallet/wallet_layer/wdk_evm.py +13 -13
  24. package/agent-wallet/examples/openclaw_runtime_onboarding.py +1 -1
  25. package/agent-wallet/examples/openclaw_user_wallet_example.py +1 -1
  26. package/agent-wallet/openclaw.plugin.json +1 -1
  27. package/agent-wallet/pyproject.toml +2 -1
  28. package/agent-wallet/scripts/bootstrap_openclaw_btc.py +3 -5
  29. package/agent-wallet/scripts/bootstrap_openclaw_evm.py +2 -12
  30. package/agent-wallet/scripts/build_release_bundle.py +1 -0
  31. package/agent-wallet/scripts/flash-sdk-bridge/bridge.mjs +1 -4
  32. package/agent-wallet/scripts/install_agent_wallet.py +1 -0
  33. package/agent-wallet/scripts/install_openclaw_local_config.py +4 -6
  34. package/agent-wallet/scripts/manage_openclaw_btc_wallet.py +2 -4
  35. package/agent-wallet/scripts/manage_openclaw_evm_wallet.py +2 -15
  36. package/agent-wallet/scripts/reveal_btc_seed.sh +7 -16
  37. package/agent-wallet/scripts/setup_btc_wallet.sh +7 -16
  38. package/agent-wallet/scripts/setup_evm_wallet.sh +1 -11
  39. package/agent-wallet/scripts/switch_openclaw_wallet_network.py +4 -1
  40. package/agent-wallet/skills/wallet-operator/SKILL.md +0 -1
  41. package/bin/openclaw-agent-wallet.mjs +289 -0
  42. package/claude-code/plugins/agent-wallet/.claude-plugin/plugin.json +20 -0
  43. package/claude-code/plugins/agent-wallet/.mcp.json +14 -0
  44. package/claude-code/plugins/agent-wallet/README.md +65 -0
  45. package/claude-code/plugins/agent-wallet/scripts/run_mcp.sh +34 -0
  46. package/claude-code/plugins/agent-wallet/skills/wallet-operator/SKILL.md +18 -0
  47. package/codex/plugins/agent-wallet/.codex-plugin/plugin.json +38 -0
  48. package/codex/plugins/agent-wallet/.mcp.json +15 -0
  49. package/codex/plugins/agent-wallet/README.md +39 -0
  50. package/codex/plugins/agent-wallet/scripts/run_mcp.sh +21 -0
  51. package/codex/plugins/agent-wallet/server.py +1077 -0
  52. package/codex/plugins/agent-wallet/skills/wallet-operator/SKILL.md +18 -0
  53. package/hermes/plugins/agent_wallet/schemas.py +2 -2
  54. package/hermes/plugins/agent_wallet/tools.py +17 -3
  55. package/package.json +6 -1
  56. package/setup.sh +2 -0
@@ -17,6 +17,7 @@ from urllib.request import urlopen
17
17
  sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
18
18
 
19
19
  from agent_wallet.file_ops import atomic_write_text, chmod_if_exists
20
+ from agent_wallet.config import normalize_evm_network
20
21
 
21
22
 
22
23
  def _default_config_path() -> Path:
@@ -44,18 +45,7 @@ def _script_path(name: str) -> Path:
44
45
 
45
46
 
46
47
  def _normalize_network(value: str) -> str:
47
- network = str(value or "").strip().lower()
48
- aliases = {
49
- "mainnet": "ethereum",
50
- "eth": "ethereum",
51
- "eth-mainnet": "ethereum",
52
- "base-mainnet": "base",
53
- "base_sepolia": "base-sepolia",
54
- }
55
- network = aliases.get(network, network)
56
- if network not in {"ethereum", "sepolia", "base", "base-sepolia"}:
57
- return "ethereum"
58
- return network
48
+ return normalize_evm_network(value)
59
49
 
60
50
 
61
51
  def build_parser() -> argparse.ArgumentParser:
@@ -24,6 +24,7 @@ INCLUDED_ROOT_FILES = [
24
24
  "setup.sh",
25
25
  ]
26
26
  INCLUDED_TOP_LEVEL_DIRS = [
27
+ "codex",
27
28
  ".openclaw",
28
29
  "agent-a2a-gateway",
29
30
  "agent-wallet",
@@ -264,16 +264,13 @@ function createReadOnlyWallet(web3, owner) {
264
264
  }
265
265
 
266
266
  function resolveClusterName(network) {
267
- return network === "mainnet" ? "mainnet-beta" : network;
267
+ return "mainnet-beta";
268
268
  }
269
269
 
270
270
  function defaultRpcUrlForNetwork(network) {
271
271
  if (network === "mainnet") {
272
272
  return "https://api.mainnet-beta.solana.com";
273
273
  }
274
- if (network === "devnet") {
275
- return "https://api.devnet.solana.com";
276
- }
277
274
  return "";
278
275
  }
279
276
 
@@ -25,6 +25,7 @@ INCLUDED_RUNTIME_ROOT_FILES = [
25
25
  "setup.sh",
26
26
  ]
27
27
  INCLUDED_RUNTIME_TOP_LEVEL_DIRS = [
28
+ "codex",
28
29
  ".openclaw",
29
30
  "agent-wallet",
30
31
  "agent-a2a-gateway",
@@ -13,6 +13,7 @@ from pathlib import Path
13
13
  sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
14
14
 
15
15
  from agent_wallet.file_ops import atomic_write_text, chmod_if_exists
16
+ from agent_wallet.config import normalize_btc_network, normalize_solana_network
16
17
  from agent_wallet.sealed_keys import resolve_sealed_keys_path, seal_keys, unseal_keys
17
18
  from security_utils import write_redacted_backup
18
19
 
@@ -42,7 +43,6 @@ LEGACY_ALLOWLIST_TOOLS = [
42
43
  "transfer_spl_token",
43
44
  "swap_solana_tokens",
44
45
  "close_empty_token_accounts",
45
- "request_devnet_airdrop",
46
46
  "get_flash_trade_markets",
47
47
  "get_flash_trade_positions",
48
48
  "flash_trade_open_position",
@@ -181,10 +181,8 @@ def _normalize_network(backend: str, network: str) -> str:
181
181
  backend_name = backend.strip().lower()
182
182
  normalized = network.strip().lower()
183
183
  if backend_name in {"wdk_btc_local", "wdk-btc-local", "btc_local", "btc-local"}:
184
- if normalized == "mainnet":
185
- return "bitcoin"
186
- return normalized or "bitcoin"
187
- return normalized or "devnet"
184
+ return normalize_btc_network(normalized or "bitcoin")
185
+ return normalize_solana_network(normalized or "mainnet")
188
186
 
189
187
 
190
188
  def build_parser() -> argparse.ArgumentParser:
@@ -193,7 +191,7 @@ def build_parser() -> argparse.ArgumentParser:
193
191
  parser.add_argument("--plugin-id", default="agent-wallet")
194
192
  parser.add_argument("--user-id", default="")
195
193
  parser.add_argument("--backend", default="solana_local")
196
- parser.add_argument("--network", default="devnet")
194
+ parser.add_argument("--network", default="mainnet")
197
195
  parser.add_argument("--rpc-url", default="")
198
196
  parser.add_argument("--rpc-urls", default="")
199
197
  parser.add_argument("--wdk-btc-service-url", default="")
@@ -21,13 +21,11 @@ from agent_wallet.btc_user_wallets import ( # noqa: E402
21
21
  reveal_user_btc_wallet_seed_phrase,
22
22
  unlock_user_btc_wallet,
23
23
  )
24
+ from agent_wallet.config import normalize_btc_network # noqa: E402
24
25
 
25
26
 
26
27
  def _normalize_network(value: str) -> str:
27
- network = str(value or "").strip().lower()
28
- if network == "mainnet":
29
- return "bitcoin"
30
- return network or "bitcoin"
28
+ return normalize_btc_network(value)
31
29
 
32
30
 
33
31
  def _read_secret(
@@ -15,7 +15,7 @@ PACKAGE_ROOT = Path(__file__).resolve().parents[1]
15
15
  if str(PACKAGE_ROOT) not in sys.path:
16
16
  sys.path.insert(0, str(PACKAGE_ROOT))
17
17
 
18
- from agent_wallet.config import settings # noqa: E402
18
+ from agent_wallet.config import normalize_evm_network, settings # noqa: E402
19
19
  from agent_wallet.evm_user_wallets import ( # noqa: E402
20
20
  bind_user_evm_wallet,
21
21
  create_user_evm_wallet,
@@ -29,26 +29,13 @@ from agent_wallet.providers.wdk_evm_local import WdkEvmLocalClient # noqa: E402
29
29
 
30
30
 
31
31
  def _normalize_network(value: str) -> str:
32
- network = str(value or "").strip().lower()
33
- aliases = {
34
- "mainnet": "ethereum",
35
- "eth": "ethereum",
36
- "eth-mainnet": "ethereum",
37
- "base-mainnet": "base",
38
- "base_sepolia": "base-sepolia",
39
- }
40
- network = aliases.get(network, network)
41
- if network not in {"ethereum", "sepolia", "base", "base-sepolia"}:
42
- return "ethereum"
43
- return network
32
+ return normalize_evm_network(value)
44
33
 
45
34
 
46
35
  def _paired_network(network: str) -> str | None:
47
36
  mapping = {
48
37
  "ethereum": "base",
49
38
  "base": "ethereum",
50
- "sepolia": "base-sepolia",
51
- "base-sepolia": "sepolia",
52
39
  }
53
40
  return mapping.get(_normalize_network(network))
54
41
 
@@ -65,12 +65,6 @@ normalize_network_value() {
65
65
  1|mainnet|bitcoin)
66
66
  printf "mainnet"
67
67
  ;;
68
- 2|testnet)
69
- printf "testnet"
70
- ;;
71
- 3|regtest)
72
- printf "regtest"
73
- ;;
74
68
  *)
75
69
  return 1
76
70
  ;;
@@ -84,18 +78,11 @@ prompt_network_choice() {
84
78
  return 0
85
79
  fi
86
80
 
87
- case "$default_value" in
88
- mainnet|bitcoin) default_hint="1" ;;
89
- testnet) default_hint="2" ;;
90
- regtest) default_hint="3" ;;
91
- *) default_hint="1" ;;
92
- esac
81
+ default_hint="1"
93
82
 
94
83
  while true; do
95
84
  printf "BTC network:\n" >&2
96
85
  printf " 1) mainnet\n" >&2
97
- printf " 2) testnet\n" >&2
98
- printf " 3) regtest\n" >&2
99
86
  printf "Choose network [%s]: " "$default_hint" >&2
100
87
  read -r choice
101
88
  if [ -z "${choice:-}" ]; then
@@ -105,12 +92,16 @@ prompt_network_choice() {
105
92
  printf "%s" "$network"
106
93
  return 0
107
94
  fi
108
- printf "Invalid choice. Enter 1, 2, 3, mainnet, testnet, or regtest.\n" >&2
95
+ printf "Invalid choice. Enter 1, mainnet, or bitcoin.\n" >&2
109
96
  done
110
97
  }
111
98
 
112
99
  DEFAULT_USER_ID=${OPENCLAW_BTC_USER_ID:-${USER:-openclaw-user}-local}
113
- DEFAULT_NETWORK=${OPENCLAW_BTC_NETWORK:-mainnet}
100
+ if DEFAULT_NETWORK=$(normalize_network_value "${OPENCLAW_BTC_NETWORK:-mainnet}" 2>/dev/null); then
101
+ :
102
+ else
103
+ DEFAULT_NETWORK=mainnet
104
+ fi
114
105
  DEFAULT_SERVICE_URL=${OPENCLAW_BTC_SERVICE_URL:-http://127.0.0.1:8080}
115
106
 
116
107
  if ! has_flag --user-id "$@"; then
@@ -65,12 +65,6 @@ normalize_network_value() {
65
65
  1|mainnet|bitcoin)
66
66
  printf "mainnet"
67
67
  ;;
68
- 2|testnet)
69
- printf "testnet"
70
- ;;
71
- 3|regtest)
72
- printf "regtest"
73
- ;;
74
68
  *)
75
69
  return 1
76
70
  ;;
@@ -84,18 +78,11 @@ prompt_network_choice() {
84
78
  return 0
85
79
  fi
86
80
 
87
- case "$default_value" in
88
- mainnet|bitcoin) default_hint="1" ;;
89
- testnet) default_hint="2" ;;
90
- regtest) default_hint="3" ;;
91
- *) default_hint="1" ;;
92
- esac
81
+ default_hint="1"
93
82
 
94
83
  while true; do
95
84
  printf "BTC network:\n" >&2
96
85
  printf " 1) mainnet\n" >&2
97
- printf " 2) testnet\n" >&2
98
- printf " 3) regtest\n" >&2
99
86
  printf "Choose network [%s]: " "$default_hint" >&2
100
87
  read -r choice
101
88
  if [ -z "${choice:-}" ]; then
@@ -105,12 +92,16 @@ prompt_network_choice() {
105
92
  printf "%s" "$network"
106
93
  return 0
107
94
  fi
108
- printf "Invalid choice. Enter 1, 2, 3, mainnet, testnet, or regtest.\n" >&2
95
+ printf "Invalid choice. Enter 1, mainnet, or bitcoin.\n" >&2
109
96
  done
110
97
  }
111
98
 
112
99
  DEFAULT_USER_ID=${OPENCLAW_BTC_USER_ID:-${USER:-openclaw-user}-local}
113
- DEFAULT_NETWORK=${OPENCLAW_BTC_NETWORK:-mainnet}
100
+ if DEFAULT_NETWORK=$(normalize_network_value "${OPENCLAW_BTC_NETWORK:-mainnet}" 2>/dev/null); then
101
+ :
102
+ else
103
+ DEFAULT_NETWORK=mainnet
104
+ fi
114
105
  DEFAULT_SERVICE_URL=${OPENCLAW_BTC_SERVICE_URL:-http://127.0.0.1:8080}
115
106
 
116
107
  if ! has_flag --user-id "$@"; then
@@ -68,12 +68,6 @@ normalize_network_value() {
68
68
  2|base)
69
69
  printf "base"
70
70
  ;;
71
- 3|sepolia)
72
- printf "sepolia"
73
- ;;
74
- 4|base-sepolia|base_sepolia)
75
- printf "base-sepolia"
76
- ;;
77
71
  *)
78
72
  return 1
79
73
  ;;
@@ -90,8 +84,6 @@ prompt_network_choice() {
90
84
  case "$default_value" in
91
85
  ethereum) default_hint="1" ;;
92
86
  base) default_hint="2" ;;
93
- sepolia) default_hint="3" ;;
94
- base-sepolia) default_hint="4" ;;
95
87
  *) default_hint="2" ;;
96
88
  esac
97
89
 
@@ -99,8 +91,6 @@ prompt_network_choice() {
99
91
  printf "EVM network:\n" >&2
100
92
  printf " 1) ethereum\n" >&2
101
93
  printf " 2) base\n" >&2
102
- printf " 3) sepolia\n" >&2
103
- printf " 4) base-sepolia\n" >&2
104
94
  printf "Choose network [%s]: " "$default_hint" >&2
105
95
  read -r choice
106
96
  if [ -z "${choice:-}" ]; then
@@ -110,7 +100,7 @@ prompt_network_choice() {
110
100
  printf "%s" "$network"
111
101
  return 0
112
102
  fi
113
- printf "Invalid choice. Enter 1, 2, 3, 4, ethereum, base, sepolia, or base-sepolia.\n" >&2
103
+ printf "Invalid choice. Enter 1, 2, ethereum, or base.\n" >&2
114
104
  done
115
105
  }
116
106
 
@@ -5,9 +5,12 @@ from __future__ import annotations
5
5
  import argparse
6
6
  import json
7
7
  import os
8
+ import sys
8
9
  from datetime import datetime, timezone
9
10
  from pathlib import Path
10
11
 
12
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
13
+
11
14
  from agent_wallet.file_ops import atomic_write_text, chmod_if_exists
12
15
  from security_utils import write_redacted_backup
13
16
 
@@ -20,7 +23,7 @@ def build_parser() -> argparse.ArgumentParser:
20
23
  parser = argparse.ArgumentParser(description=__doc__)
21
24
  parser.add_argument("--config-path", default=str(_default_config_path()))
22
25
  parser.add_argument("--plugin-id", default="agent-wallet")
23
- parser.add_argument("--network", required=True, choices=["mainnet", "devnet", "testnet"])
26
+ parser.add_argument("--network", required=True, choices=["mainnet"])
24
27
  parser.add_argument("--rpc-url", default="")
25
28
  parser.add_argument("--rpc-urls", default="")
26
29
  parser.add_argument("--sign-only", action=argparse.BooleanOptionalAction, default=None)
@@ -116,7 +116,6 @@ Use this skill before calling OpenClaw wallet tools. It is the routing guide for
116
116
  - `claim_bags_fees`: `token_mint`, `mode`, `purpose`.
117
117
  - `launch_bags_token`: `name`, `symbol`, `description`, `base_mint`, `claimers`, `basis_points`, `initial_buy_sol`, `mode`, `purpose`; optional socials/image/config type.
118
118
  - `close_empty_token_accounts`: `limit`, `mode` (`preview` or `execute`), `purpose`.
119
- - `request_devnet_airdrop`: `amount`; only outside mainnet.
120
119
 
121
120
  ## Approval Flow Template
122
121
 
@@ -22,6 +22,8 @@ function printHelp() {
22
22
  Usage:
23
23
  openclaw-agent-wallet install [options]
24
24
  openclaw-agent-wallet hermes install [options]
25
+ openclaw-agent-wallet codex install [options]
26
+ openclaw-agent-wallet claude-code install [options]
25
27
  openclaw-agent-wallet update [options]
26
28
  openclaw-agent-wallet status
27
29
  openclaw-agent-wallet rollback [--to <version>]
@@ -37,6 +39,8 @@ Common install options:
37
39
  Examples:
38
40
  npx @agentlayer.tech/wallet install --yes
39
41
  npx @agentlayer.tech/wallet hermes install --yes
42
+ npx @agentlayer.tech/wallet codex install --yes
43
+ npx @agentlayer.tech/wallet claude-code install --yes
40
44
  npx @agentlayer.tech/wallet install --backend none
41
45
  npx @agentlayer.tech/wallet update --yes
42
46
  npx @agentlayer.tech/wallet update --yes --dry-run
@@ -90,6 +94,10 @@ function resolveHermesHome(env = process.env) {
90
94
  return path.resolve(expandHome(env.HERMES_HOME || "~/.hermes"));
91
95
  }
92
96
 
97
+ function resolveCodexHome(env = process.env) {
98
+ return path.resolve(expandHome(env.CODEX_HOME || "~/.codex"));
99
+ }
100
+
93
101
  function releaseRootFor(version, env = process.env) {
94
102
  return path.join(resolveRuntimeBase(env), "releases", version);
95
103
  }
@@ -545,6 +553,20 @@ function readEnvFile(pathname) {
545
553
  }
546
554
  }
547
555
 
556
+ function readJsonFile(pathname) {
557
+ try {
558
+ return JSON.parse(fs.readFileSync(pathname, "utf8"));
559
+ } catch (error) {
560
+ if (error?.code === "ENOENT") return null;
561
+ throw error;
562
+ }
563
+ }
564
+
565
+ function writeJsonFile(pathname, value) {
566
+ fs.mkdirSync(path.dirname(pathname), { recursive: true });
567
+ fs.writeFileSync(pathname, `${JSON.stringify(value, null, 2)}\n`);
568
+ }
569
+
548
570
  function currentBootKey(env = process.env) {
549
571
  const currentRoot = resolvedCurrentRuntimeRoot(env);
550
572
  if (!currentRoot) return "";
@@ -600,6 +622,8 @@ function runDoctor() {
600
622
  ["setup.sh", setupPath],
601
623
  ["agent-wallet", path.join(packageRoot, "agent-wallet")],
602
624
  ["OpenClaw extension", path.join(packageRoot, ".openclaw", "extensions", "agent-wallet")],
625
+ ["Codex plugin", path.join(packageRoot, "codex", "plugins", "agent-wallet", ".codex-plugin", "plugin.json")],
626
+ ["Claude Code plugin", path.join(packageRoot, "claude-code", "plugins", "agent-wallet", ".claude-plugin", "plugin.json")],
603
627
  ["wdk-btc-wallet", path.join(packageRoot, "wdk-btc-wallet", "package.json")],
604
628
  ["wdk-evm-wallet", path.join(packageRoot, "wdk-evm-wallet", "package.json")],
605
629
  ];
@@ -961,6 +985,97 @@ function resolveHermesPluginSource() {
961
985
  throw new Error(`Missing Hermes plugin bundle. Checked: ${candidates.join(", ")}`);
962
986
  }
963
987
 
988
+ function resolveCodexPluginSource() {
989
+ const currentRoot = resolvedCurrentRuntimeRoot();
990
+ const candidates = [];
991
+ if (currentRoot) {
992
+ candidates.push(path.join(currentRoot, "codex", "plugins", "agent-wallet"));
993
+ }
994
+ candidates.push(path.join(packageRoot, "codex", "plugins", "agent-wallet"));
995
+ for (const source of candidates) {
996
+ if (fs.existsSync(path.join(source, ".codex-plugin", "plugin.json"))) {
997
+ return source;
998
+ }
999
+ }
1000
+ throw new Error(`Missing Codex plugin bundle. Checked: ${candidates.join(", ")}`);
1001
+ }
1002
+
1003
+ function resolveClaudeCodePluginSource() {
1004
+ const currentRoot = resolvedCurrentRuntimeRoot();
1005
+ const candidates = [];
1006
+ if (currentRoot) {
1007
+ candidates.push(path.join(currentRoot, "claude-code", "plugins", "agent-wallet"));
1008
+ }
1009
+ candidates.push(path.join(packageRoot, "claude-code", "plugins", "agent-wallet"));
1010
+ for (const source of candidates) {
1011
+ if (fs.existsSync(path.join(source, ".claude-plugin", "plugin.json"))) {
1012
+ return source;
1013
+ }
1014
+ }
1015
+ throw new Error(`Missing Claude Code plugin bundle. Checked: ${candidates.join(", ")}`);
1016
+ }
1017
+
1018
+ function resolveCodexPluginInstallRoot(env = process.env) {
1019
+ return path.resolve(expandHome(env.AGENT_WALLET_CODEX_PLUGIN_ROOT || "~/plugins"));
1020
+ }
1021
+
1022
+ function resolveCodexMarketplacePath(env = process.env) {
1023
+ return path.resolve(
1024
+ expandHome(env.AGENT_WALLET_CODEX_MARKETPLACE_PATH || "~/.agents/plugins/marketplace.json"),
1025
+ );
1026
+ }
1027
+
1028
+ function ensureCodexMarketplaceEntry({ marketplacePath, pluginName }) {
1029
+ const existing = readJsonFile(marketplacePath);
1030
+ const payload = existing && typeof existing === "object"
1031
+ ? existing
1032
+ : {
1033
+ name: "local",
1034
+ interface: {
1035
+ displayName: "Local Plugins",
1036
+ },
1037
+ plugins: [],
1038
+ };
1039
+
1040
+ if (typeof payload.name !== "string" || !payload.name.trim()) {
1041
+ payload.name = "local";
1042
+ }
1043
+ if (!payload.interface || typeof payload.interface !== "object") {
1044
+ payload.interface = { displayName: "Local Plugins" };
1045
+ }
1046
+ if (typeof payload.interface.displayName !== "string" || !payload.interface.displayName.trim()) {
1047
+ payload.interface.displayName = "Local Plugins";
1048
+ }
1049
+ if (!Array.isArray(payload.plugins)) {
1050
+ payload.plugins = [];
1051
+ }
1052
+
1053
+ const entry = {
1054
+ name: pluginName,
1055
+ source: {
1056
+ source: "local",
1057
+ path: `./plugins/${pluginName}`,
1058
+ },
1059
+ policy: {
1060
+ installation: "AVAILABLE",
1061
+ authentication: "ON_INSTALL",
1062
+ },
1063
+ category: "Coding",
1064
+ };
1065
+ const index = payload.plugins.findIndex((item) => item && item.name === pluginName);
1066
+ if (index >= 0) {
1067
+ payload.plugins[index] = entry;
1068
+ } else {
1069
+ payload.plugins.push(entry);
1070
+ }
1071
+ writeJsonFile(marketplacePath, payload);
1072
+ return {
1073
+ marketplace_name: payload.name,
1074
+ marketplace_path: marketplacePath,
1075
+ entry,
1076
+ };
1077
+ }
1078
+
964
1079
  function resolveAgentWalletPackageRoot(env = process.env) {
965
1080
  const currentRoot = resolvedCurrentRuntimeRoot(env);
966
1081
  if (currentRoot) {
@@ -1072,6 +1187,160 @@ function runHermesInstall(args) {
1072
1187
  return enable.skipped || enable.ok ? 0 : 1;
1073
1188
  }
1074
1189
 
1190
+ function runCodexInstall(args) {
1191
+ const codexHome = resolveCodexHome();
1192
+ const pluginSource = resolveCodexPluginSource();
1193
+ const pluginRoot = resolveCodexPluginInstallRoot();
1194
+ const pluginTarget = path.join(pluginRoot, "agent-wallet");
1195
+ const marketplacePath = resolveCodexMarketplacePath();
1196
+ const force = hasFlag(args, "--force");
1197
+ const skipEnable = hasFlag(args, "--skip-enable");
1198
+ const codexBin = commandPath("codex");
1199
+
1200
+ fs.mkdirSync(pluginRoot, { recursive: true });
1201
+ try {
1202
+ const existing = fs.lstatSync(pluginTarget);
1203
+ if (!existing.isSymbolicLink()) {
1204
+ if (!force) {
1205
+ throw new Error(`${pluginTarget} exists and is not a symlink. Pass --force to replace it.`);
1206
+ }
1207
+ fs.rmSync(pluginTarget, { recursive: true, force: true });
1208
+ } else {
1209
+ fs.unlinkSync(pluginTarget);
1210
+ }
1211
+ } catch (error) {
1212
+ if (error?.code !== "ENOENT") throw error;
1213
+ }
1214
+ fs.symlinkSync(pluginSource, pluginTarget, "dir");
1215
+
1216
+ const marketplace = ensureCodexMarketplaceEntry({
1217
+ marketplacePath,
1218
+ pluginName: "agent-wallet",
1219
+ });
1220
+
1221
+ let add = { attempted: false, ok: false, skipped: skipEnable, error: "" };
1222
+ if (!skipEnable) {
1223
+ if (!codexBin) {
1224
+ add = {
1225
+ attempted: false,
1226
+ ok: false,
1227
+ skipped: false,
1228
+ error: "Codex CLI was not found on PATH. Run `codex plugin add agent-wallet@local` after installing Codex.",
1229
+ };
1230
+ } else {
1231
+ const result = spawnSync(
1232
+ codexBin,
1233
+ ["plugin", "add", `agent-wallet@${marketplace.marketplace_name}`],
1234
+ {
1235
+ cwd: packageRoot,
1236
+ encoding: "utf8",
1237
+ env: {
1238
+ ...process.env,
1239
+ CODEX_HOME: codexHome,
1240
+ },
1241
+ },
1242
+ );
1243
+ add = {
1244
+ attempted: true,
1245
+ ok: result.status === 0,
1246
+ skipped: false,
1247
+ error: result.status === 0 ? "" : (result.stderr || result.stdout || "").trim(),
1248
+ };
1249
+ }
1250
+ }
1251
+
1252
+ console.log(
1253
+ JSON.stringify(
1254
+ {
1255
+ ok: add.skipped || add.ok,
1256
+ codex_home: codexHome,
1257
+ plugin_source: pluginSource,
1258
+ plugin_target: pluginTarget,
1259
+ marketplace_path: marketplace.marketplace_path,
1260
+ marketplace_name: marketplace.marketplace_name,
1261
+ codex_add: add,
1262
+ restart_required: true,
1263
+ },
1264
+ null,
1265
+ 2,
1266
+ ),
1267
+ );
1268
+ return add.skipped || add.ok ? 0 : 1;
1269
+ }
1270
+
1271
+ function runClaudeCodeInstall(args) {
1272
+ const pluginSource = resolveClaudeCodePluginSource();
1273
+ const force = hasFlag(args, "--force");
1274
+ const skipEnable = hasFlag(args, "--skip-enable");
1275
+ const claudeBin = commandPath("claude");
1276
+
1277
+ // Symlink the plugin into ~/.claude/plugins/agent-wallet so Claude Code can load it.
1278
+ const pluginInstallDir = path.join(os.homedir(), ".claude", "plugins");
1279
+ const pluginTarget = path.join(pluginInstallDir, "agent-wallet");
1280
+
1281
+ fs.mkdirSync(pluginInstallDir, { recursive: true });
1282
+ try {
1283
+ const existing = fs.lstatSync(pluginTarget);
1284
+ if (!existing.isSymbolicLink()) {
1285
+ if (!force) {
1286
+ throw new Error(`${pluginTarget} exists and is not a symlink. Pass --force to replace it.`);
1287
+ }
1288
+ fs.rmSync(pluginTarget, { recursive: true, force: true });
1289
+ } else {
1290
+ fs.unlinkSync(pluginTarget);
1291
+ }
1292
+ } catch (error) {
1293
+ if (error?.code !== "ENOENT") throw error;
1294
+ }
1295
+ fs.symlinkSync(pluginSource, pluginTarget, "dir");
1296
+
1297
+ // Try `claude plugin install <path>` when the CLI is present.
1298
+ let enable = { attempted: false, ok: false, skipped: skipEnable, error: "" };
1299
+ if (!skipEnable) {
1300
+ if (!claudeBin) {
1301
+ enable = {
1302
+ attempted: false,
1303
+ ok: false,
1304
+ skipped: false,
1305
+ error:
1306
+ "Claude Code CLI was not found on PATH. Load the plugin manually with: claude --plugin-dir " +
1307
+ pluginTarget,
1308
+ };
1309
+ } else {
1310
+ const result = spawnSync(claudeBin, ["plugin", "install", pluginTarget, "--scope", "user"], {
1311
+ encoding: "utf8",
1312
+ stdio: "pipe",
1313
+ });
1314
+ enable = {
1315
+ attempted: true,
1316
+ ok: result.status === 0,
1317
+ skipped: false,
1318
+ error: result.status === 0 ? "" : (result.stderr || result.stdout || "").trim(),
1319
+ };
1320
+ }
1321
+ }
1322
+
1323
+ const pluginDirFlag = `claude --plugin-dir ${pluginTarget}`;
1324
+ console.log(
1325
+ JSON.stringify(
1326
+ {
1327
+ ok: enable.skipped || enable.ok,
1328
+ plugin_source: pluginSource,
1329
+ plugin_target: pluginTarget,
1330
+ claude_code_install: enable,
1331
+ manual_load: pluginDirFlag,
1332
+ restart_required: true,
1333
+ note: enable.ok
1334
+ ? "Plugin registered. Restart Claude Code to activate."
1335
+ : `If automatic registration failed, load the plugin with: ${pluginDirFlag}`,
1336
+ },
1337
+ null,
1338
+ 2,
1339
+ ),
1340
+ );
1341
+ return enable.skipped || enable.ok ? 0 : 1;
1342
+ }
1343
+
1075
1344
  const args = process.argv.slice(2);
1076
1345
  const command = args[0] || "install";
1077
1346
 
@@ -1115,6 +1384,26 @@ if (command === "hermes") {
1115
1384
  process.exit(2);
1116
1385
  }
1117
1386
 
1387
+ if (command === "codex") {
1388
+ const subcommand = args[1] || "install";
1389
+ if (subcommand === "install" || subcommand === "setup") {
1390
+ process.exit(runCodexInstall(args.slice(2)));
1391
+ }
1392
+ console.error(`Unknown codex command: ${subcommand}`);
1393
+ console.error("Run `openclaw-agent-wallet codex install --yes` to connect Codex.");
1394
+ process.exit(2);
1395
+ }
1396
+
1397
+ if (command === "claude-code") {
1398
+ const subcommand = args[1] || "install";
1399
+ if (subcommand === "install" || subcommand === "setup") {
1400
+ process.exit(runClaudeCodeInstall(args.slice(2)));
1401
+ }
1402
+ console.error(`Unknown claude-code command: ${subcommand}`);
1403
+ console.error("Run `openclaw-agent-wallet claude-code install --yes` to connect Claude Code.");
1404
+ process.exit(2);
1405
+ }
1406
+
1118
1407
  if (command.startsWith("-")) {
1119
1408
  process.exit(runInstall(args, { commandName: "install" }));
1120
1409
  }