mycelium-cli 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,63 @@
1
+ """
2
+ `mycelium fund` — top up a testnet wallet from Friendbot.
3
+
4
+ Today funding only happens implicitly as a `mycelium deploy` side-effect when the
5
+ wallet is empty. This makes it explicit: `mycelium fund` requests Friendbot
6
+ lumens for the project wallet (or any --address) and polls until the funding
7
+ lands on-chain. Friendbot is testnet-only; mainnet wallets must be funded out of
8
+ band, so this refuses to run there.
9
+ """
10
+
11
+ import json
12
+ import os
13
+ import sys
14
+ from typing import Optional
15
+
16
+ from mycelium_sdk.constants import normalize_network
17
+
18
+ from mycelium_cli.config import get_value
19
+ from mycelium_cli.commands.deploy import (
20
+ DEFAULT_WALLET_PATH,
21
+ _fund_with_friendbot,
22
+ _native_balance,
23
+ )
24
+
25
+
26
+ def _wallet_public_key(wallet_path: str) -> Optional[str]:
27
+ """Read the wallet's plaintext public key (no passphrase needed)."""
28
+ if not os.path.exists(wallet_path):
29
+ return None
30
+ with open(wallet_path, "r") as f:
31
+ return json.load(f).get("public_key")
32
+
33
+
34
+ def run_fund(
35
+ address: Optional[str] = None,
36
+ network: Optional[str] = None,
37
+ wallet_path: str = DEFAULT_WALLET_PATH,
38
+ ) -> float:
39
+ """Fund `address` (or the project wallet) via Friendbot. Returns the new balance."""
40
+ network = normalize_network(network or get_value("onchain", "network", "testnet"))
41
+ if network != "testnet":
42
+ print("❌ Friendbot funding is testnet-only. Fund mainnet wallets manually.")
43
+ sys.exit(1)
44
+
45
+ public_key = address or _wallet_public_key(wallet_path)
46
+ if not public_key:
47
+ print(
48
+ f"Error: no address given and wallet {wallet_path} not found.\n"
49
+ " Run `mycelium newwallet` first, or pass --address G..."
50
+ )
51
+ sys.exit(1)
52
+
53
+ balance = _native_balance(network, public_key)
54
+ print(f"[fund] {public_key} currently holds {balance} XLM on {network}.")
55
+ try:
56
+ _fund_with_friendbot(network, public_key)
57
+ except Exception as e:
58
+ print(f"❌ Funding failed: {e}")
59
+ sys.exit(1)
60
+
61
+ new_balance = _native_balance(network, public_key)
62
+ print(f"✓ Funded. Balance: {new_balance} XLM (+{new_balance - balance:.4f}).")
63
+ return new_balance
@@ -0,0 +1,188 @@
1
+ """
2
+ `mycelium init` — scaffold a new agent project per sdk.md section 1.1:
3
+
4
+ <project>/
5
+ ├── mycelium.toml # project / agent / onchain / registry config
6
+ ├── agent.py # outer LLM-orchestration logic
7
+ ├── contract.py # inner Soroban contract (Mycelium DSL)
8
+ └── .mycelium/ # protected dir for the encrypted wallet (gitignored)
9
+ """
10
+
11
+ import os
12
+ import re
13
+
14
+ import tomli_w
15
+
16
+ from mycelium_sdk.constants import HIVEMIND_REGISTRY_ADDRESS
17
+
18
+ UNIQUE_NAME_RE = re.compile(r"^[a-zA-Z0-9_]{3,30}$")
19
+ VALID_FRAMEWORKS = ("langgraph", "gemini", "anthropic", "openai", "ollama", "custom")
20
+
21
+
22
+ def validate_unique_name(name: str) -> bool:
23
+ return bool(UNIQUE_NAME_RE.match(name))
24
+
25
+
26
+ # ── file templates ───────────────────────────────────────────────────────────
27
+ _CONTRACT_TEMPLATE = '''"""Inner logic: a strictly-typed Mycelium → Soroban contract."""
28
+
29
+ from mycelium import contract, external, view, Env, U64
30
+
31
+
32
+ @contract
33
+ class Counter:
34
+ def __init__(self, env: Env):
35
+ self.env = env
36
+ self.storage = env.storage()
37
+
38
+ @external
39
+ def increment(self) -> U64:
40
+ count = self.storage.get("count", U64(0))
41
+ count = count + U64(1)
42
+ self.storage.set("count", count)
43
+ return count
44
+
45
+ @view
46
+ def get_count(self) -> U64:
47
+ return self.storage.get("count", U64(0))
48
+ '''
49
+
50
+
51
+ # Frameworks that the one-call `run_agent_loop` helper drives directly.
52
+ _AGENT_LOOP_PROVIDERS = {"gemini", "anthropic"}
53
+
54
+
55
+ def _agent_loop_template(framework: str, model: str, unique_name: str, api_key_env: str) -> str:
56
+ """A complete agent in one call — run_agent_loop wires the {framework} loop."""
57
+ return f'''"""Outer logic: {framework}-orchestrated on-chain agent ({model})."""
58
+
59
+ import os
60
+
61
+ from mycelium import AgentContext, HiveClient, run_agent_loop, ContractTool
62
+
63
+ # Sovereign on-chain execution context (loads .mycelium/wallet.json).
64
+ context = AgentContext(keypair_path=".mycelium/wallet.json", network_type="testnet")
65
+ hive = HiveClient(context)
66
+
67
+ # Contract this agent is bound to (set by `mycelium run` / `mycelium agent --contract`).
68
+ CONTRACT_ID = os.environ.get("MYCELIUM_CONTRACT_ID", "")
69
+ # API key read from the environment (.env, gitignored, written by `mycelium init`).
70
+ API_KEY = os.environ.get("{api_key_env}")
71
+
72
+
73
+ def main():
74
+ print("Agent '{unique_name}' online as", context.keypair.public_key)
75
+ answer = run_agent_loop(
76
+ "You are an on-chain agent. Increment your counter contract, "
77
+ "then report the new value.",
78
+ context=context,
79
+ provider="{framework}",
80
+ model="{model}",
81
+ api_key=API_KEY,
82
+ contract_id=CONTRACT_ID,
83
+ tools=[
84
+ ContractTool("increment"),
85
+ ContractTool("get_count", read_only=True),
86
+ ],
87
+ hive=hive, # exposes a lookup_partner_agent tool for Hive Registry discovery
88
+ )
89
+ print(answer)
90
+
91
+
92
+ if __name__ == "__main__":
93
+ main()
94
+ '''
95
+
96
+
97
+ def _agent_template(framework: str, model: str, unique_name: str) -> str:
98
+ if framework in _AGENT_LOOP_PROVIDERS:
99
+ return _agent_loop_template(
100
+ framework, model, unique_name, _API_KEY_ENV.get(framework, "API_KEY")
101
+ )
102
+ return f'''"""Outer logic: AI orchestration ({framework} / {model})."""
103
+
104
+ from mycelium import AgentContext
105
+
106
+ # Sovereign on-chain execution context (loads .mycelium/wallet.json).
107
+ context = AgentContext(keypair_path=".mycelium/wallet.json", network_type="testnet")
108
+
109
+
110
+ def main():
111
+ print("Agent '{unique_name}' online as", context.keypair.public_key)
112
+ # TODO: wire your {framework} workflow here and expose contract calls
113
+ # via context.call_contract(...).
114
+
115
+
116
+ if __name__ == "__main__":
117
+ main()
118
+ '''
119
+
120
+
121
+ def _build_config(project_name: str, framework: str, model: str, unique_name: str) -> dict:
122
+ return {
123
+ "project": {"name": project_name, "version": "0.1.0", "author": "Developer"},
124
+ "agent": {"framework": framework, "model": model, "unique_name": unique_name},
125
+ "onchain": {
126
+ "source_contract": "contract.py",
127
+ "target_wasm": "build/contract.wasm",
128
+ "network": "testnet",
129
+ "contract_id": "",
130
+ "wallet_public_key": "",
131
+ },
132
+ "registry": {
133
+ "hive_registry_address": HIVEMIND_REGISTRY_ADDRESS,
134
+ "service_endpoint": f"https://{unique_name}.agents.mycelium.sh/api/v1",
135
+ "capabilities": [],
136
+ },
137
+ }
138
+
139
+
140
+ # Maps an API framework to the env var its agent template reads the key from.
141
+ _API_KEY_ENV = {"gemini": "GEMINI_API_KEY", "anthropic": "ANTHROPIC_API_KEY", "openai": "OPENAI_API_KEY"}
142
+
143
+
144
+ def run_init(
145
+ project_name: str,
146
+ framework: str = "custom",
147
+ model: str = "custom",
148
+ unique_name: str | None = None,
149
+ api_key: str | None = None,
150
+ ) -> str:
151
+ """
152
+ Scaffold a project directory. Prompting for framework/model/unique_name is
153
+ handled by the CLI layer; this function takes resolved values so it can be
154
+ driven non-interactively (tests/CI). When `api_key` is given it is written
155
+ to a gitignored `.env` so the agent can authenticate without re-prompting.
156
+ Returns the project path.
157
+ """
158
+ if framework not in VALID_FRAMEWORKS:
159
+ raise ValueError(f"framework must be one of {VALID_FRAMEWORKS}, got {framework!r}")
160
+ unique_name = unique_name or project_name
161
+ if not validate_unique_name(unique_name):
162
+ raise ValueError(
163
+ f"unique_name {unique_name!r} must match ^[a-zA-Z0-9_]{{3,30}}$"
164
+ )
165
+
166
+ os.makedirs(project_name, exist_ok=True)
167
+ os.makedirs(os.path.join(project_name, ".mycelium"), exist_ok=True)
168
+
169
+ with open(os.path.join(project_name, "mycelium.toml"), "wb") as f:
170
+ tomli_w.dump(_build_config(project_name, framework, model, unique_name), f)
171
+ with open(os.path.join(project_name, "contract.py"), "w") as f:
172
+ f.write(_CONTRACT_TEMPLATE)
173
+ with open(os.path.join(project_name, "agent.py"), "w") as f:
174
+ f.write(_agent_template(framework, model, unique_name))
175
+ with open(os.path.join(project_name, ".gitignore"), "w") as f:
176
+ f.write(".mycelium/\nbuild/\n.env\n__pycache__/\n")
177
+
178
+ if api_key:
179
+ env_var = _API_KEY_ENV.get(framework, "API_KEY")
180
+ env_path = os.path.join(project_name, ".env")
181
+ with open(env_path, "w") as f:
182
+ f.write(f"{env_var}={api_key}\n")
183
+ try:
184
+ os.chmod(env_path, 0o600)
185
+ except OSError:
186
+ pass
187
+
188
+ return project_name
@@ -0,0 +1,61 @@
1
+ """
2
+ `mycelium newwallet` — generate an encrypted Ed25519 wallet (sdk.md section 2.2).
3
+
4
+ Generates a Stellar keypair, encrypts the secret seed with AES-GCM-256 (key
5
+ derived from a passphrase via PBKDF2), and writes the payload to
6
+ `.mycelium/wallet.json`. The plaintext seed is never written to disk.
7
+ """
8
+
9
+ import json
10
+ import os
11
+
12
+ from mycelium_sdk import crypto
13
+
14
+ DEFAULT_WALLET_PATH = os.path.join(".mycelium", "wallet.json")
15
+
16
+
17
+ def run_newwallet(
18
+ path: str = DEFAULT_WALLET_PATH,
19
+ passphrase: str | None = None,
20
+ force: bool = False,
21
+ ) -> str:
22
+ """
23
+ Create an encrypted wallet at `path`. `passphrase` is resolved via
24
+ crypto.resolve_passphrase (explicit arg → MYCELIUM_DECRYPT_KEY). Refuses to
25
+ overwrite an existing wallet unless `force=True`. Returns the public key.
26
+ """
27
+ from stellar_sdk import Keypair
28
+
29
+ if os.path.exists(path) and not force:
30
+ raise FileExistsError(
31
+ f"A wallet already exists at {path}. Use --force to overwrite it "
32
+ "(this destroys the existing key)."
33
+ )
34
+
35
+ pw = crypto.resolve_passphrase(passphrase)
36
+ keypair = Keypair.random()
37
+ payload = {"public_key": keypair.public_key, **crypto.encrypt_secret(keypair.secret, pw)}
38
+
39
+ parent = os.path.dirname(path)
40
+ if parent:
41
+ os.makedirs(parent, mode=0o700, exist_ok=True)
42
+ try: # exist_ok won't fix the mode of a pre-existing dir
43
+ os.chmod(parent, 0o700)
44
+ except OSError:
45
+ pass
46
+
47
+ # Create the file with 0600 from the start so the ciphertext is never
48
+ # world-readable, even for the instant between write and chmod.
49
+ try:
50
+ fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
51
+ with os.fdopen(fd, "w") as f:
52
+ json.dump(payload, f, indent=2)
53
+ except (OSError, ValueError): # platforms without full POSIX mode support
54
+ with open(path, "w") as f:
55
+ json.dump(payload, f, indent=2)
56
+ try:
57
+ os.chmod(path, 0o600)
58
+ except OSError:
59
+ pass
60
+
61
+ return keypair.public_key
@@ -0,0 +1,83 @@
1
+ """
2
+ `mycelium pay <name|address> <amount>` — machine-to-machine XLM settlement.
3
+
4
+ Wraps what a2a_demo.py does by hand: resolve the recipient (a Hive Registry
5
+ unique name, or a raw G... address) to an address, then sign and submit a native
6
+ XLM payment from the project wallet. Reputation/endpoint come from the registry
7
+ so you can pay an agent by *name* without knowing its address.
8
+ """
9
+
10
+ import os
11
+ import sys
12
+ from typing import Optional
13
+
14
+ from mycelium_cli.config import get_value
15
+
16
+ DEFAULT_WALLET_PATH = ".mycelium/wallet.json"
17
+
18
+
19
+ def _resolve_recipient(recipient: str, network: str, registry: Optional[str]) -> str:
20
+ """Return a destination G-address for a name or a raw address."""
21
+ from stellar_sdk import StrKey
22
+
23
+ if StrKey.is_valid_ed25519_public_key(recipient):
24
+ return recipient
25
+
26
+ from mycelium_sdk import AgentContext, HiveClient
27
+
28
+ print(f"[pay] Resolving '{recipient}' via Hive Registry...")
29
+ ro = AgentContext.read_only(network_type=network)
30
+ entry = HiveClient(ro, registry_address=registry).resolve_agent(recipient)
31
+ dest = entry.get("public_key")
32
+ if not dest:
33
+ print(f"❌ '{recipient}' resolved to no address.")
34
+ sys.exit(1)
35
+ print(f"[pay] '{recipient}' -> {dest}")
36
+ return dest
37
+
38
+
39
+ def run_pay(
40
+ recipient: str,
41
+ amount: str,
42
+ network: Optional[str] = None,
43
+ wallet_path: str = DEFAULT_WALLET_PATH,
44
+ passphrase: Optional[str] = None,
45
+ ) -> str:
46
+ from stellar_sdk import Asset, TransactionBuilder
47
+
48
+ from mycelium_sdk import AgentContext
49
+ from mycelium_sdk.constants import HIVEMIND_REGISTRY_ADDRESS
50
+
51
+ network = network or get_value("onchain", "network", "testnet")
52
+ registry = get_value("registry", "hive_registry_address") or HIVEMIND_REGISTRY_ADDRESS
53
+
54
+ if not os.path.exists(wallet_path):
55
+ print(f"Error: wallet {wallet_path} not found. Run `mycelium newwallet` first.")
56
+ sys.exit(1)
57
+
58
+ dest = _resolve_recipient(recipient, network, registry)
59
+
60
+ context = AgentContext(keypair_path=wallet_path, network_type=network, passphrase=passphrase)
61
+ print(f"[pay] Sending {amount} XLM to {dest} as {context.keypair.public_key}...")
62
+
63
+ try:
64
+ source = context.horizon_server.load_account(context.keypair.public_key)
65
+ tx = (
66
+ TransactionBuilder(source, context.network_passphrase, base_fee=100)
67
+ .append_payment_op(destination=dest, asset=Asset.native(), amount=str(amount))
68
+ .set_timeout(60)
69
+ .build()
70
+ )
71
+ tx.sign(context.keypair)
72
+ resp = context.horizon_server.submit_transaction(tx)
73
+ except Exception as e:
74
+ print(f"❌ Payment failed: {e}")
75
+ sys.exit(1)
76
+
77
+ if not resp.get("successful", False):
78
+ print(f"❌ Payment rejected: {resp.get('result_xdr')}")
79
+ sys.exit(1)
80
+
81
+ tx_hash = resp["hash"]
82
+ print(f"✓ Paid {amount} XLM to {recipient}. Tx: {tx_hash}")
83
+ return tx_hash
@@ -0,0 +1,59 @@
1
+ """
2
+ `mycelium register` — register the agent's unique name on the Hive Registry
3
+ (sdk.md section 2.5).
4
+
5
+ Reads the agent metadata from mycelium.toml, builds an AgentContext from the
6
+ local wallet, and invokes `register_agent` on the registry contract. The
7
+ registry address comes from `[registry].hive_registry_address` (falling back to
8
+ the SDK default). If that address is not a deployed registry, the on-chain call
9
+ fails loudly — no fabricated success.
10
+ """
11
+
12
+ import os
13
+ import sys
14
+
15
+ DEFAULT_WALLET_PATH = os.path.join(".mycelium", "wallet.json")
16
+
17
+
18
+ def run_register(
19
+ network: str | None = None,
20
+ wallet_path: str = DEFAULT_WALLET_PATH,
21
+ passphrase: str | None = None,
22
+ ) -> str:
23
+ from mycelium_sdk import AgentContext, HiveClient
24
+
25
+ from mycelium_cli.config import get_value
26
+
27
+ unique_name = get_value("agent", "unique_name")
28
+ if not unique_name:
29
+ print("Error: [agent].unique_name missing from mycelium.toml.")
30
+ sys.exit(1)
31
+ endpoint = get_value("registry", "service_endpoint", "")
32
+ capabilities = get_value("registry", "capabilities", []) or []
33
+ registry_address = get_value("registry", "hive_registry_address")
34
+ network = network or get_value("onchain", "network", "testnet")
35
+ model = get_value("agent", "model", "custom")
36
+ role = get_value("agent", "role", "Autonomous Agent")
37
+ description = get_value("agent", "description", "Custom on-chain agent resolved from Hive Registry.")
38
+
39
+ if not os.path.exists(wallet_path):
40
+ print(f"Error: wallet {wallet_path} not found. Run `mycelium newwallet` first.")
41
+ sys.exit(1)
42
+
43
+ context = AgentContext(keypair_path=wallet_path, network_type=network, passphrase=passphrase)
44
+ hive = HiveClient(context, registry_address=registry_address)
45
+
46
+ print(f"[register] Registering '{unique_name}' on registry {hive.registry_address}...")
47
+ try:
48
+ result = hive.register(unique_name, capabilities, endpoint, model=model, role=role, desc=description)
49
+ except Exception as e:
50
+ m = str(e).lower()
51
+ if "nametaken" in m or ("name" in m and ("taken" in m or "collision" in m or "exists" in m)):
52
+ print(f"❌ Registration failed: the name '{unique_name}' is already taken.")
53
+ else:
54
+ print(f"❌ Registration failed: {e}")
55
+ sys.exit(1)
56
+
57
+ tx_hash = getattr(result, "hash", str(result))
58
+ print(f"✓ Registered '{unique_name}'. Tx: {tx_hash}")
59
+ return tx_hash
@@ -0,0 +1,54 @@
1
+ """
2
+ `mycelium resolve <name>` — look up a single agent in the Hive Registry.
3
+
4
+ Read-only and wallet-free: resolves a unique agent name to its on-chain
5
+ directory entry (address, capability hash, endpoint, reputation).
6
+ """
7
+
8
+ import sys
9
+ from typing import Optional
10
+
11
+ # The HiveRegistry contract reverts with ContractError.NOT_REGISTERED (code 2)
12
+ # when a name has never been registered.
13
+ _NOT_REGISTERED_MARKERS = ("contract, #2)", "not registered", "notregistered", "keyerror")
14
+
15
+
16
+ def _is_not_registered(error: Exception) -> bool:
17
+ m = str(error).lower()
18
+ return any(marker in m for marker in _NOT_REGISTERED_MARKERS)
19
+
20
+
21
+ def run_resolve(
22
+ name: str,
23
+ network: Optional[str] = None,
24
+ registry: Optional[str] = None,
25
+ ) -> dict:
26
+ from mycelium_sdk import AgentContext, HiveClient
27
+ from mycelium_sdk.constants import HIVEMIND_REGISTRY_ADDRESS
28
+
29
+ from mycelium_cli.config import get_value
30
+
31
+ network = network or get_value("onchain", "network", "testnet")
32
+ registry = registry or get_value("registry", "hive_registry_address") or HIVEMIND_REGISTRY_ADDRESS
33
+
34
+ context = AgentContext.read_only(network_type=network)
35
+ hive = HiveClient(context, registry_address=registry)
36
+
37
+ print(f"[resolve] Looking up '{name}' on registry {registry} ({network})...")
38
+ try:
39
+ entry = hive.resolve_agent(name)
40
+ except Exception as e:
41
+ if _is_not_registered(e):
42
+ print(f"❌ '{name}' is not registered in the Hive Registry.")
43
+ else:
44
+ print(f"❌ Resolution failed: {e}")
45
+ sys.exit(1)
46
+
47
+ cap = entry.get("capability_hash")
48
+ cap_str = cap.hex() if isinstance(cap, (bytes, bytearray)) else (cap or "—")
49
+ print(f"\n✓ {name}")
50
+ print(f" address : {entry.get('public_key') or '—'}")
51
+ print(f" endpoint : {entry.get('endpoint') or '—'}")
52
+ print(f" reputation : {entry.get('reputation', 0)}")
53
+ print(f" capability : {cap_str}\n")
54
+ return entry
@@ -0,0 +1,33 @@
1
+ """
2
+ `mycelium run` — run the project's agent with config auto-wired.
3
+
4
+ A convenience alias for `mycelium agent`: instead of passing
5
+ `--contract C...` by hand, it reads `[onchain].contract_id` (and the agent
6
+ script, defaulting to agent.py) from mycelium.toml. The network is exported as
7
+ `MYCELIUM_NETWORK` so the runtime can pick it up.
8
+ """
9
+
10
+ import os
11
+ import sys
12
+ from typing import Optional
13
+
14
+ from mycelium_cli.config import get_value
15
+ from mycelium_cli.commands.agent import run_agent
16
+
17
+
18
+ def run_run(file: Optional[str] = None, contract: Optional[str] = None) -> None:
19
+ file = file or get_value("agent", "script", "agent.py")
20
+ contract = contract or get_value("onchain", "contract_id") or ""
21
+ network = get_value("onchain", "network", "testnet")
22
+
23
+ if not os.path.exists(file):
24
+ print(f"Error: agent script {file} not found. Pass it explicitly or run from the project dir.")
25
+ sys.exit(1)
26
+ if not contract:
27
+ print(
28
+ "Warning: no [onchain].contract_id in mycelium.toml — running unbound.\n"
29
+ " Deploy first with `mycelium deploy`, or pass --contract C..."
30
+ )
31
+
32
+ os.environ.setdefault("MYCELIUM_NETWORK", network)
33
+ run_agent(file, contract)
@@ -0,0 +1,127 @@
1
+ """
2
+ `mycelium status` — the "am I set up correctly?" command.
3
+
4
+ One screen that answers, for the current project: which wallet am I, how much
5
+ XLM do I hold, on which network, is my contract deployed, and am I registered in
6
+ the Hive Registry (with what reputation). Everything here is read-only and
7
+ wallet-free — it never prompts for a passphrase or spends a fee.
8
+ """
9
+
10
+ import json
11
+ import os
12
+ import sys
13
+ from typing import Optional
14
+
15
+ from mycelium_cli.config import get_value, load_config
16
+
17
+ DEFAULT_WALLET_PATH = os.path.join(".mycelium", "wallet.json")
18
+
19
+ _OK = "✓"
20
+ _NO = "✗"
21
+
22
+
23
+ def _wallet_public_key(wallet_path: str) -> Optional[str]:
24
+ if not os.path.exists(wallet_path):
25
+ return None
26
+ try:
27
+ with open(wallet_path, "r") as f:
28
+ return json.load(f).get("public_key")
29
+ except (OSError, json.JSONDecodeError):
30
+ return None
31
+
32
+
33
+ def _safe_balance(network: str, public_key: str) -> Optional[float]:
34
+ try:
35
+ from mycelium_cli.commands.deploy import _native_balance
36
+
37
+ return _native_balance(network, public_key)
38
+ except Exception:
39
+ return None
40
+
41
+
42
+ def _registry_entry(network: str, registry: Optional[str], name: str) -> Optional[dict]:
43
+ """Resolve `name` in the registry, returning the entry or None if unregistered."""
44
+ try:
45
+ from mycelium_sdk import AgentContext, HiveClient
46
+
47
+ context = AgentContext.read_only(network_type=network)
48
+ hive = HiveClient(context, registry_address=registry)
49
+ return hive.resolve_agent(name)
50
+ except Exception:
51
+ return None
52
+
53
+
54
+ def run_status(
55
+ network: Optional[str] = None,
56
+ wallet_path: str = DEFAULT_WALLET_PATH,
57
+ ) -> dict:
58
+ try:
59
+ load_config()
60
+ except FileNotFoundError as e:
61
+ print(f"Error: {e}")
62
+ sys.exit(1)
63
+
64
+ from mycelium_sdk.constants import HIVEMIND_REGISTRY_ADDRESS
65
+
66
+ project = get_value("project", "name", "—")
67
+ unique_name = get_value("agent", "unique_name")
68
+ framework = get_value("agent", "framework", "—")
69
+ network = network or get_value("onchain", "network", "testnet")
70
+ contract_id = get_value("onchain", "contract_id") or ""
71
+ registry = get_value("registry", "hive_registry_address") or HIVEMIND_REGISTRY_ADDRESS
72
+
73
+ public_key = _wallet_public_key(wallet_path)
74
+ balance = _safe_balance(network, public_key) if public_key else None
75
+ entry = _registry_entry(network, registry, unique_name) if unique_name else None
76
+
77
+ print(f"\nMycelium status — project '{project}' ({framework})\n")
78
+
79
+ # Wallet + funding.
80
+ if public_key:
81
+ bal_str = f"{balance} XLM" if balance is not None else "unknown (RPC unreachable)"
82
+ funded = balance is not None and balance > 0
83
+ print(f" {_OK} wallet {public_key}")
84
+ print(f" {_OK if funded else _NO} balance {bal_str}")
85
+ else:
86
+ print(f" {_NO} wallet not found — run `mycelium newwallet`")
87
+ print(f" {_NO} balance —")
88
+
89
+ print(f" • network {network}")
90
+
91
+ # Deploy state.
92
+ if contract_id:
93
+ print(f" {_OK} contract {contract_id}")
94
+ else:
95
+ print(f" {_NO} contract not deployed — run `mycelium deploy`")
96
+
97
+ # Registry registration.
98
+ if not unique_name:
99
+ print(f" {_NO} registry no [agent].unique_name in mycelium.toml")
100
+ elif entry and entry.get("public_key"):
101
+ rep = entry.get("reputation", 0)
102
+ print(f" {_OK} registry '{unique_name}' registered (reputation {rep})")
103
+ if entry.get("endpoint"):
104
+ print(f" endpoint {entry['endpoint']}")
105
+ else:
106
+ print(f" {_NO} registry '{unique_name}' not registered — run `mycelium register`")
107
+
108
+ # One-line "what next?" nudge.
109
+ if not public_key:
110
+ nxt = "mycelium newwallet"
111
+ elif not balance:
112
+ nxt = "mycelium fund"
113
+ elif not contract_id:
114
+ nxt = "mycelium compile && mycelium deploy"
115
+ elif not (entry and entry.get("public_key")):
116
+ nxt = "mycelium register"
117
+ else:
118
+ nxt = None
119
+ print(f"\n Next: {nxt}\n" if nxt else "\n Everything is set up. 🍄\n")
120
+
121
+ return {
122
+ "public_key": public_key,
123
+ "balance": balance,
124
+ "network": network,
125
+ "contract_id": contract_id,
126
+ "registered": bool(entry and entry.get("public_key")),
127
+ }