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.
- mycelium_cli/__init__.py +1 -0
- mycelium_cli/commands/__init__.py +1 -0
- mycelium_cli/commands/agent.py +70 -0
- mycelium_cli/commands/call.py +96 -0
- mycelium_cli/commands/check.py +21 -0
- mycelium_cli/commands/compile.py +59 -0
- mycelium_cli/commands/deploy.py +140 -0
- mycelium_cli/commands/discover.py +67 -0
- mycelium_cli/commands/doctor.py +106 -0
- mycelium_cli/commands/events.py +116 -0
- mycelium_cli/commands/fund.py +63 -0
- mycelium_cli/commands/init.py +188 -0
- mycelium_cli/commands/newwallet.py +61 -0
- mycelium_cli/commands/pay.py +83 -0
- mycelium_cli/commands/register.py +59 -0
- mycelium_cli/commands/resolve.py +54 -0
- mycelium_cli/commands/run.py +33 -0
- mycelium_cli/commands/status.py +127 -0
- mycelium_cli/commands/test.py +72 -0
- mycelium_cli/config.py +47 -0
- mycelium_cli/main.py +298 -0
- mycelium_cli-0.1.0.dist-info/METADATA +239 -0
- mycelium_cli-0.1.0.dist-info/RECORD +26 -0
- mycelium_cli-0.1.0.dist-info/WHEEL +5 -0
- mycelium_cli-0.1.0.dist-info/entry_points.txt +2 -0
- mycelium_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
`mycelium test` — dry-run the agent against a simulated ledger.
|
|
3
|
+
|
|
4
|
+
Runs the project's agent script exactly as `mycelium run` would, but with
|
|
5
|
+
dry-run mode forced on (MYCELIUM_DRY_RUN=1): every state-changing contract call
|
|
6
|
+
the agent makes is *simulated* (no signature, no fee, no on-chain mutation) and
|
|
7
|
+
recorded. Afterwards it prints a summary of every on-chain action the agent
|
|
8
|
+
would have taken, with the simulated return value and estimated fee — so you can
|
|
9
|
+
see what your agent does before it spends real lumens on testnet.
|
|
10
|
+
|
|
11
|
+
Read-only calls behave identically to a live run, so views still return real data.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
import sys
|
|
16
|
+
from typing import Optional
|
|
17
|
+
|
|
18
|
+
from mycelium_cli.config import get_value
|
|
19
|
+
from mycelium_cli.commands.agent import run_agent
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _stroops_to_xlm(stroops: Optional[int]) -> str:
|
|
23
|
+
if stroops is None:
|
|
24
|
+
return "—"
|
|
25
|
+
return f"{stroops / 1e7:.7f}"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _print_summary() -> None:
|
|
29
|
+
from mycelium_sdk import context as ctx_mod
|
|
30
|
+
|
|
31
|
+
log = ctx_mod.DRY_RUN_LOG
|
|
32
|
+
print("\n──────── dry-run summary ────────")
|
|
33
|
+
if not log:
|
|
34
|
+
print(" No state-changing contract calls were attempted.")
|
|
35
|
+
print(" (Read-only calls run normally and are not listed here.)\n")
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
total_fee = 0
|
|
39
|
+
for i, rec in enumerate(log, 1):
|
|
40
|
+
args = ", ".join(map(repr, rec["args"]))
|
|
41
|
+
print(f" {i}. {rec['function']}({args})")
|
|
42
|
+
print(f" contract : {rec['contract_id']}")
|
|
43
|
+
print(f" returns : {rec['sim_return']}")
|
|
44
|
+
print(f" est fee : {_stroops_to_xlm(rec['est_fee_stroops'])} XLM")
|
|
45
|
+
if rec["est_fee_stroops"]:
|
|
46
|
+
total_fee += rec["est_fee_stroops"]
|
|
47
|
+
print(f"\n {len(log)} action(s) simulated · est. total fees {_stroops_to_xlm(total_fee)} XLM")
|
|
48
|
+
print(" Nothing was signed or submitted. Run `mycelium run` to execute for real.\n")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def run_test(file: Optional[str] = None, contract: Optional[str] = None) -> list:
|
|
52
|
+
from mycelium_sdk import context as ctx_mod
|
|
53
|
+
|
|
54
|
+
file = file or get_value("agent", "script", "agent.py")
|
|
55
|
+
contract = contract or get_value("onchain", "contract_id") or ""
|
|
56
|
+
network = get_value("onchain", "network", "testnet")
|
|
57
|
+
|
|
58
|
+
if not os.path.exists(file):
|
|
59
|
+
print(f"Error: agent script {file} not found. Pass it explicitly or run from the project dir.")
|
|
60
|
+
sys.exit(1)
|
|
61
|
+
|
|
62
|
+
print(f"[test] Dry-running {file} ({network}) — state changes will be simulated only.\n")
|
|
63
|
+
os.environ["MYCELIUM_DRY_RUN"] = "1"
|
|
64
|
+
os.environ.setdefault("MYCELIUM_NETWORK", network)
|
|
65
|
+
ctx_mod.reset_dry_run_log()
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
run_agent(file, contract)
|
|
69
|
+
finally:
|
|
70
|
+
_print_summary()
|
|
71
|
+
|
|
72
|
+
return list(ctx_mod.DRY_RUN_LOG)
|
mycelium_cli/config.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""
|
|
2
|
+
mycelium.toml read/write helpers.
|
|
3
|
+
|
|
4
|
+
Reading uses the stdlib `tomllib` (Python 3.11+); writing uses `tomli-w`. We
|
|
5
|
+
only ever patch flat values (e.g. `onchain.contract_id` after deploy), so the
|
|
6
|
+
lack of comment preservation is acceptable.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import tomllib
|
|
11
|
+
|
|
12
|
+
import tomli_w
|
|
13
|
+
|
|
14
|
+
DEFAULT_CONFIG_PATH = "mycelium.toml"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def load_config(path: str = DEFAULT_CONFIG_PATH) -> dict:
|
|
18
|
+
"""Load and parse a mycelium.toml file. Raises FileNotFoundError if absent."""
|
|
19
|
+
if not os.path.exists(path):
|
|
20
|
+
raise FileNotFoundError(
|
|
21
|
+
f"{path} not found. Run `mycelium init <name>` first, or run from a project directory."
|
|
22
|
+
)
|
|
23
|
+
with open(path, "rb") as f:
|
|
24
|
+
return tomllib.load(f)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def save_config(data: dict, path: str = DEFAULT_CONFIG_PATH) -> None:
|
|
28
|
+
"""Serialize a config dict back to TOML."""
|
|
29
|
+
with open(path, "wb") as f:
|
|
30
|
+
tomli_w.dump(data, f)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def set_value(table: str, key: str, value, path: str = DEFAULT_CONFIG_PATH) -> dict:
|
|
34
|
+
"""Set `[table].key = value` in the config file, creating the table if needed."""
|
|
35
|
+
data = load_config(path)
|
|
36
|
+
data.setdefault(table, {})[key] = value
|
|
37
|
+
save_config(data, path)
|
|
38
|
+
return data
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_value(table: str, key: str, default=None, path: str = DEFAULT_CONFIG_PATH):
|
|
42
|
+
"""Read `[table].key`, returning `default` if missing."""
|
|
43
|
+
try:
|
|
44
|
+
data = load_config(path)
|
|
45
|
+
except FileNotFoundError:
|
|
46
|
+
return default
|
|
47
|
+
return data.get(table, {}).get(key, default)
|
mycelium_cli/main.py
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
"""Mycelium CLI entry point (Typer)."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from mycelium_sdk.banner import show_startup_banner
|
|
8
|
+
from mycelium_cli.commands.init import run_init, validate_unique_name, VALID_FRAMEWORKS
|
|
9
|
+
from mycelium_cli.commands.newwallet import run_newwallet, DEFAULT_WALLET_PATH
|
|
10
|
+
from mycelium_cli.commands.compile import run_compile
|
|
11
|
+
from mycelium_cli.commands.deploy import run_deploy
|
|
12
|
+
from mycelium_cli.commands.register import run_register
|
|
13
|
+
from mycelium_cli.commands.check import run_check
|
|
14
|
+
from mycelium_cli.commands.agent import run_agent
|
|
15
|
+
from mycelium_cli.commands.discover import run_discover
|
|
16
|
+
from mycelium_cli.commands.resolve import run_resolve
|
|
17
|
+
from mycelium_cli.commands.status import run_status
|
|
18
|
+
from mycelium_cli.commands.fund import run_fund
|
|
19
|
+
from mycelium_cli.commands.call import run_call
|
|
20
|
+
from mycelium_cli.commands.pay import run_pay
|
|
21
|
+
from mycelium_cli.commands.doctor import run_doctor
|
|
22
|
+
from mycelium_cli.commands.events import run_events
|
|
23
|
+
from mycelium_cli.commands.run import run_run
|
|
24
|
+
from mycelium_cli.commands.test import run_test
|
|
25
|
+
|
|
26
|
+
app = typer.Typer(help="Mycelium Developer Framework CLI")
|
|
27
|
+
|
|
28
|
+
PASSPHRASE_ENV_VAR = "MYCELIUM_DECRYPT_KEY"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@app.callback(invoke_without_command=True)
|
|
32
|
+
def _root(ctx: typer.Context):
|
|
33
|
+
"""Mycelium Developer Framework CLI."""
|
|
34
|
+
show_startup_banner()
|
|
35
|
+
if ctx.invoked_subcommand is None:
|
|
36
|
+
typer.echo(ctx.get_help())
|
|
37
|
+
raise typer.Exit()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _resolve_passphrase(label: str, confirm: bool = False) -> str:
|
|
41
|
+
"""Use MYCELIUM_DECRYPT_KEY if set, otherwise prompt interactively."""
|
|
42
|
+
env_value = os.environ.get(PASSPHRASE_ENV_VAR)
|
|
43
|
+
if env_value:
|
|
44
|
+
return env_value
|
|
45
|
+
return typer.prompt(label, hide_input=True, confirmation_prompt=confirm)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _select_model(framework: str) -> tuple[str, str | None]:
|
|
49
|
+
"""
|
|
50
|
+
Resolve the target model for `framework`, returning (model, api_key).
|
|
51
|
+
|
|
52
|
+
For API-backed frameworks we NEVER let the developer free-type a model name
|
|
53
|
+
(a hallucinated id fails at runtime). Instead we take their API key, query
|
|
54
|
+
the provider's live model catalogue, and have them pick from the real list.
|
|
55
|
+
If discovery fails (bad key / offline) we fall back to manual entry so the
|
|
56
|
+
wizard never hard-blocks. `api_key` is None for non-API frameworks.
|
|
57
|
+
"""
|
|
58
|
+
from mycelium_sdk import models as model_discovery
|
|
59
|
+
|
|
60
|
+
if not model_discovery.supports_discovery(framework):
|
|
61
|
+
return typer.prompt("Target model", default="custom"), None
|
|
62
|
+
|
|
63
|
+
# Cloud providers need a key; local runtimes (ollama) need a base URL instead.
|
|
64
|
+
api_key = None
|
|
65
|
+
base_url = None
|
|
66
|
+
if model_discovery.requires_api_key(framework):
|
|
67
|
+
api_key = typer.prompt(f"{framework.capitalize()} API key", hide_input=True).strip()
|
|
68
|
+
else:
|
|
69
|
+
base_url = typer.prompt(
|
|
70
|
+
f"{framework.capitalize()} server URL",
|
|
71
|
+
default=model_discovery.DEFAULT_OLLAMA_URL,
|
|
72
|
+
).strip()
|
|
73
|
+
|
|
74
|
+
typer.echo(f" Fetching available {framework} models...")
|
|
75
|
+
try:
|
|
76
|
+
available = model_discovery.list_models(framework, api_key, base_url=base_url)
|
|
77
|
+
except model_discovery.ModelDiscoveryError as exc:
|
|
78
|
+
typer.echo(f" ⚠ Could not list models ({exc}).")
|
|
79
|
+
return typer.prompt("Enter the model id manually"), api_key
|
|
80
|
+
|
|
81
|
+
typer.echo(f" {len(available)} models available:")
|
|
82
|
+
for i, name in enumerate(available, 1):
|
|
83
|
+
typer.echo(f" [{i}] {name}")
|
|
84
|
+
while True:
|
|
85
|
+
choice = typer.prompt("Select a model by number")
|
|
86
|
+
try:
|
|
87
|
+
idx = int(choice)
|
|
88
|
+
if 1 <= idx <= len(available):
|
|
89
|
+
return available[idx - 1], api_key
|
|
90
|
+
except ValueError:
|
|
91
|
+
pass
|
|
92
|
+
typer.echo(f" Enter a number between 1 and {len(available)}.")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@app.command()
|
|
96
|
+
def init(
|
|
97
|
+
project_name: str = typer.Argument(..., help="Name of the new project"),
|
|
98
|
+
non_interactive: bool = typer.Option(
|
|
99
|
+
False, "--yes", "--non-interactive", help="Skip prompts, use defaults"
|
|
100
|
+
),
|
|
101
|
+
):
|
|
102
|
+
"""Initialize a new Mycelium agent project."""
|
|
103
|
+
framework, model, unique_name, api_key = "custom", "custom", project_name, None
|
|
104
|
+
|
|
105
|
+
if not non_interactive:
|
|
106
|
+
framework = typer.prompt(f"AI framework {list(VALID_FRAMEWORKS)}", default="custom")
|
|
107
|
+
while framework not in VALID_FRAMEWORKS:
|
|
108
|
+
typer.echo(f" Must be one of {list(VALID_FRAMEWORKS)}.")
|
|
109
|
+
framework = typer.prompt("AI framework", default="custom")
|
|
110
|
+
model, api_key = _select_model(framework)
|
|
111
|
+
unique_name = typer.prompt("Unique name (^[a-zA-Z0-9_]{3,30}$)", default=project_name)
|
|
112
|
+
while not validate_unique_name(unique_name):
|
|
113
|
+
typer.echo(" Invalid: 3-30 chars, alphanumeric/underscore only.")
|
|
114
|
+
unique_name = typer.prompt("Unique name", default=project_name)
|
|
115
|
+
|
|
116
|
+
path = run_init(
|
|
117
|
+
project_name, framework=framework, model=model, unique_name=unique_name, api_key=api_key
|
|
118
|
+
)
|
|
119
|
+
typer.echo(f"✓ Project '{path}' initialized.")
|
|
120
|
+
typer.echo(" Next: cd into it, run `mycelium newwallet`, then `mycelium compile`.")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@app.command()
|
|
124
|
+
def newwallet(
|
|
125
|
+
path: str = typer.Option(DEFAULT_WALLET_PATH, help="Wallet output path"),
|
|
126
|
+
force: bool = typer.Option(False, "--force", help="Overwrite an existing wallet"),
|
|
127
|
+
):
|
|
128
|
+
"""Generate an encrypted Ed25519 wallet."""
|
|
129
|
+
passphrase = _resolve_passphrase("Encryption passphrase", confirm=True)
|
|
130
|
+
public_key = run_newwallet(path=path, passphrase=passphrase, force=force)
|
|
131
|
+
typer.echo(f"✓ Wallet created at {path}")
|
|
132
|
+
typer.echo(f" Public key: {public_key}")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@app.command()
|
|
136
|
+
def compile(
|
|
137
|
+
file: str = typer.Argument(None, help="Contract file (defaults to mycelium.toml)"),
|
|
138
|
+
output: str = typer.Option(None, "-o", "--output", help="Output WASM path"),
|
|
139
|
+
optimize: bool = typer.Option(False, "--optimize", help="Size-optimize the WASM"),
|
|
140
|
+
):
|
|
141
|
+
"""Compile a Python contract to Soroban WASM."""
|
|
142
|
+
run_compile(file, output, optimize=optimize)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@app.command()
|
|
146
|
+
def check(file: str = typer.Argument(..., help="Python contract file to validate")):
|
|
147
|
+
"""Validate a contract's AST and types without compiling."""
|
|
148
|
+
run_check(file)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@app.command()
|
|
152
|
+
def deploy(
|
|
153
|
+
network: str = typer.Option("testnet", help="testnet or mainnet"),
|
|
154
|
+
wasm: str = typer.Option(None, help="WASM path (defaults to mycelium.toml)"),
|
|
155
|
+
wallet: str = typer.Option(DEFAULT_WALLET_PATH, help="Wallet path"),
|
|
156
|
+
):
|
|
157
|
+
"""Deploy the compiled contract to Stellar/Soroban."""
|
|
158
|
+
passphrase = _resolve_passphrase("Wallet passphrase")
|
|
159
|
+
run_deploy(network=network, wasm_path=wasm, wallet_path=wallet, passphrase=passphrase)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@app.command()
|
|
163
|
+
def register(
|
|
164
|
+
network: str = typer.Option(None, help="testnet or mainnet (defaults to mycelium.toml)"),
|
|
165
|
+
wallet: str = typer.Option(DEFAULT_WALLET_PATH, help="Wallet path"),
|
|
166
|
+
):
|
|
167
|
+
"""Register the agent's unique name on the Hive Registry."""
|
|
168
|
+
passphrase = _resolve_passphrase("Wallet passphrase")
|
|
169
|
+
run_register(network=network, wallet_path=wallet, passphrase=passphrase)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@app.command()
|
|
173
|
+
def agent(
|
|
174
|
+
file: str = typer.Argument(..., help="Agent runtime script"),
|
|
175
|
+
contract: str = typer.Option(..., "--contract", help="On-chain contract id to bind"),
|
|
176
|
+
):
|
|
177
|
+
"""Start a Mycelium agent runtime."""
|
|
178
|
+
run_agent(file, contract)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@app.command()
|
|
182
|
+
def agents(
|
|
183
|
+
network: str = typer.Option(None, help="testnet or mainnet (defaults to mycelium.toml)"),
|
|
184
|
+
registry: str = typer.Option(None, "--registry", help="Hive Registry contract id override"),
|
|
185
|
+
start_ledger: int = typer.Option(
|
|
186
|
+
None, "--start-ledger", help="First ledger to scan (defaults to the RPC retention horizon)"
|
|
187
|
+
),
|
|
188
|
+
no_resolve: bool = typer.Option(
|
|
189
|
+
False, "--no-resolve", help="Skip per-agent resolution (faster; names + addresses only)"
|
|
190
|
+
),
|
|
191
|
+
):
|
|
192
|
+
"""Discover every agent registered on the Hive Registry (read-only, no wallet)."""
|
|
193
|
+
run_discover(
|
|
194
|
+
network=network, registry=registry, start_ledger=start_ledger, resolve=not no_resolve
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@app.command()
|
|
199
|
+
def resolve(
|
|
200
|
+
name: str = typer.Argument(..., help="Unique agent name to look up"),
|
|
201
|
+
network: str = typer.Option(None, help="testnet or mainnet (defaults to mycelium.toml)"),
|
|
202
|
+
registry: str = typer.Option(None, "--registry", help="Hive Registry contract id override"),
|
|
203
|
+
):
|
|
204
|
+
"""Resolve a single agent name to its Hive Registry entry (read-only, no wallet)."""
|
|
205
|
+
run_resolve(name, network=network, registry=registry)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@app.command()
|
|
209
|
+
def status(
|
|
210
|
+
network: str = typer.Option(None, help="testnet or mainnet (defaults to mycelium.toml)"),
|
|
211
|
+
wallet: str = typer.Option(DEFAULT_WALLET_PATH, help="Wallet path"),
|
|
212
|
+
):
|
|
213
|
+
"""Show wallet, balance, network, deploy, and registry state in one screen."""
|
|
214
|
+
run_status(network=network, wallet_path=wallet)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@app.command()
|
|
218
|
+
def fund(
|
|
219
|
+
address: str = typer.Option(None, "--address", help="Address to fund (defaults to project wallet)"),
|
|
220
|
+
network: str = typer.Option(None, help="testnet or mainnet (defaults to mycelium.toml)"),
|
|
221
|
+
wallet: str = typer.Option(DEFAULT_WALLET_PATH, help="Wallet path"),
|
|
222
|
+
):
|
|
223
|
+
"""Top up a testnet wallet from Friendbot."""
|
|
224
|
+
run_fund(address=address, network=network, wallet_path=wallet)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
@app.command()
|
|
228
|
+
def call(
|
|
229
|
+
function_name: str = typer.Argument(..., help="Contract function to invoke"),
|
|
230
|
+
args: list[str] = typer.Argument(None, help="Positional arguments (ints/bools/addresses auto-typed)"),
|
|
231
|
+
contract: str = typer.Option(None, "--contract", help="Contract id (defaults to mycelium.toml)"),
|
|
232
|
+
network: str = typer.Option(None, help="testnet or mainnet (defaults to mycelium.toml)"),
|
|
233
|
+
send: bool = typer.Option(False, "--send", help="Sign & submit a state-changing tx (default: read-only)"),
|
|
234
|
+
wallet: str = typer.Option(DEFAULT_WALLET_PATH, help="Wallet path (only with --send)"),
|
|
235
|
+
):
|
|
236
|
+
"""Invoke a deployed contract function (read-only by default)."""
|
|
237
|
+
passphrase = _resolve_passphrase("Wallet passphrase") if send else None
|
|
238
|
+
run_call(
|
|
239
|
+
function_name, args=args, contract=contract, network=network,
|
|
240
|
+
send=send, wallet_path=wallet, passphrase=passphrase,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
@app.command()
|
|
245
|
+
def pay(
|
|
246
|
+
recipient: str = typer.Argument(..., help="Registry name or G... address to pay"),
|
|
247
|
+
amount: str = typer.Argument(..., help="Amount of XLM to send"),
|
|
248
|
+
network: str = typer.Option(None, help="testnet or mainnet (defaults to mycelium.toml)"),
|
|
249
|
+
wallet: str = typer.Option(DEFAULT_WALLET_PATH, help="Wallet path"),
|
|
250
|
+
):
|
|
251
|
+
"""Send an XLM payment to a registry name or address (M2M settlement)."""
|
|
252
|
+
passphrase = _resolve_passphrase("Wallet passphrase")
|
|
253
|
+
run_pay(recipient, amount, network=network, wallet_path=wallet, passphrase=passphrase)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@app.command()
|
|
257
|
+
def doctor(
|
|
258
|
+
network: str = typer.Option(None, help="testnet or mainnet (defaults to mycelium.toml)"),
|
|
259
|
+
):
|
|
260
|
+
"""Verify the toolchain (stellar-cli, rust+wasm, RPC) and print fixes."""
|
|
261
|
+
run_doctor(network=network)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
@app.command()
|
|
265
|
+
def events(
|
|
266
|
+
contract: str = typer.Option(None, "--contract", help="Contract id (defaults to mycelium.toml)"),
|
|
267
|
+
network: str = typer.Option(None, help="testnet or mainnet (defaults to mycelium.toml)"),
|
|
268
|
+
start_ledger: int = typer.Option(None, "--start-ledger", help="First ledger to scan"),
|
|
269
|
+
follow: bool = typer.Option(False, "--follow", "-f", help="Stream new events until interrupted"),
|
|
270
|
+
):
|
|
271
|
+
"""Show (or stream with --follow) a contract's on-chain events."""
|
|
272
|
+
run_events(contract=contract, network=network, start_ledger=start_ledger, follow=follow)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@app.command()
|
|
276
|
+
def run(
|
|
277
|
+
file: str = typer.Argument(None, help="Agent script (defaults to agent.py)"),
|
|
278
|
+
contract: str = typer.Option(None, "--contract", help="Contract id (defaults to mycelium.toml)"),
|
|
279
|
+
):
|
|
280
|
+
"""Run the project's agent, auto-reading contract id + network from mycelium.toml."""
|
|
281
|
+
run_run(file=file, contract=contract)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
@app.command()
|
|
285
|
+
def test(
|
|
286
|
+
file: str = typer.Argument(None, help="Agent script (defaults to agent.py)"),
|
|
287
|
+
contract: str = typer.Option(None, "--contract", help="Contract id (defaults to mycelium.toml)"),
|
|
288
|
+
):
|
|
289
|
+
"""Dry-run the agent: simulate every on-chain action without signing or spending."""
|
|
290
|
+
run_test(file=file, contract=contract)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def main():
|
|
294
|
+
app()
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
if __name__ == "__main__":
|
|
298
|
+
main()
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mycelium-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Mycelium CLI — init, newwallet, compile, deploy, register
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: typer>=0.9
|
|
8
|
+
Requires-Dist: tomli-w>=1.0
|
|
9
|
+
Requires-Dist: stellar-sdk<15,>=14
|
|
10
|
+
|
|
11
|
+
# Mycelium CLI
|
|
12
|
+
|
|
13
|
+
The Mycelium CLI (`mycelium` command-line tool) is the developer command center for the Mycelium framework. It provides interactive scaffolding, local wallet/keypair management, contract checking and compilation, Soroban blockchain deployments, agent directory registration in the Hive registry, and execution runners for autonomous agent loops.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 🚀 Installation & Setup
|
|
18
|
+
|
|
19
|
+
Install the CLI toolchain directly from PyPI (packaged within `mycelium-cli` or bundled inside the parent `mycelium-stellar` wrapper):
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install mycelium-cli
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Verify that the installation was successful by running:
|
|
26
|
+
```bash
|
|
27
|
+
mycelium --help
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## ⚙️ Configuration Reference (`mycelium.toml`)
|
|
33
|
+
|
|
34
|
+
All Mycelium CLI operations run relative to a project root containing a `mycelium.toml` file. This configuration serves as the single source of truth for the local agent and its corresponding on-chain contract.
|
|
35
|
+
|
|
36
|
+
```toml
|
|
37
|
+
[project]
|
|
38
|
+
name = "sentinel_agent"
|
|
39
|
+
version = "0.1.0"
|
|
40
|
+
author = "Developer"
|
|
41
|
+
|
|
42
|
+
[agent]
|
|
43
|
+
framework = "gemini" # Options: "langgraph" | "gemini" | "anthropic" | "custom"
|
|
44
|
+
model = "gemini-2.0-flash" # Target LLM model string
|
|
45
|
+
unique_name = "sentinel_alpha" # Alphanumeric agent registry name
|
|
46
|
+
|
|
47
|
+
[onchain]
|
|
48
|
+
source_contract = "contract.py" # Path to smart contract source file
|
|
49
|
+
target_wasm = "build/contract.wasm" # Output binary path
|
|
50
|
+
network = "testnet" # Default ledger target: "testnet" | "mainnet"
|
|
51
|
+
contract_id = "CC..." # Automatically populated after deployment
|
|
52
|
+
wallet_public_key = "GD..." # Automatically populated after deployment
|
|
53
|
+
|
|
54
|
+
[registry]
|
|
55
|
+
hive_registry_address = "CCQ..." # Hex contract address of the Hive Registry
|
|
56
|
+
service_endpoint = "https://agent.sentinel.mycelium.sh" # Agent API URL
|
|
57
|
+
capabilities = ["data-analysis", "stellar-arbitrage"] # List of capability tags
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 🛠️ Complete CLI Command Reference
|
|
63
|
+
|
|
64
|
+
### 1. `mycelium init`
|
|
65
|
+
Scaffolds a new Mycelium project from scratch. It launches an interactive setup wizard that prompts you for project properties.
|
|
66
|
+
|
|
67
|
+
* **Syntax**:
|
|
68
|
+
```bash
|
|
69
|
+
mycelium init <project_name> [options]
|
|
70
|
+
```
|
|
71
|
+
* **Interactive Wizard Options**:
|
|
72
|
+
- **AI Core Framework**: Select from `langgraph`, `gemini`, `anthropic`, or `custom`.
|
|
73
|
+
- **Target LLM Model**: Pick from recommended defaults or input a custom string.
|
|
74
|
+
- **Unique Name**: Choose a registry name (regex validated against `^[a-zA-Z0-9_]{3,30}$`).
|
|
75
|
+
* **Flags**:
|
|
76
|
+
- `--yes` / `-y`: Skip all interactive questions and initialize using standard default configurations.
|
|
77
|
+
- `--force` / `-f`: Overwrite the destination directory if it already exists.
|
|
78
|
+
|
|
79
|
+
### 2. `mycelium newwallet`
|
|
80
|
+
Generates a new secure Stellar keypair (Ed25519) and saves it to `.mycelium/wallet.json`.
|
|
81
|
+
|
|
82
|
+
* **Syntax**:
|
|
83
|
+
```bash
|
|
84
|
+
mycelium newwallet [options]
|
|
85
|
+
```
|
|
86
|
+
* **Security Details**:
|
|
87
|
+
- The secret seed is encrypted at rest using PBKDF2-HMAC-SHA256 (600,000 iterations) + AES-256-GCM.
|
|
88
|
+
- Prompts securely for an encryption passphrase.
|
|
89
|
+
- Filesystem permissions on `.mycelium/wallet.json` are automatically restricted to `0600` (read/write by owner only).
|
|
90
|
+
* **Flags**:
|
|
91
|
+
- `--passphrase <text>`: Provide the encryption passphrase directly (convenient for automated environments).
|
|
92
|
+
- `--force`: Force generation, overwriting any existing wallet configuration.
|
|
93
|
+
|
|
94
|
+
### 3. `mycelium compile`
|
|
95
|
+
Parses and compiles a Python-DSL contract file into a WebAssembly contract binary.
|
|
96
|
+
|
|
97
|
+
* **Syntax**:
|
|
98
|
+
```bash
|
|
99
|
+
mycelium compile [source_file] [options]
|
|
100
|
+
```
|
|
101
|
+
* **Flags**:
|
|
102
|
+
- `--output <path>` / `-o <path>`: Specify the output WASM file path (defaults to `build/contract.wasm`).
|
|
103
|
+
- `--optimize`: Enable maximum optimization passes (release profile, targeting size reduction).
|
|
104
|
+
|
|
105
|
+
### 4. `mycelium check`
|
|
106
|
+
Performs static evaluation and type verification on a contract script without generating a WASM binary. Useful for checking syntax in IDEs, git pre-commit hooks, or CI pipelines.
|
|
107
|
+
|
|
108
|
+
* **Syntax**:
|
|
109
|
+
```bash
|
|
110
|
+
mycelium check [source_file]
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 5. `mycelium deploy`
|
|
114
|
+
Deploys the compiled WASM binary directly to Stellar/Soroban.
|
|
115
|
+
|
|
116
|
+
* **Syntax**:
|
|
117
|
+
```bash
|
|
118
|
+
mycelium deploy [options]
|
|
119
|
+
```
|
|
120
|
+
* **Behaviors**:
|
|
121
|
+
- **Testnet**: Checks the balance. If the balance is zero, the CLI automatically requests funds from the Stellar Friendbot API, waits for ledger confirmation, and broadcasts the deployment transaction.
|
|
122
|
+
- **Mainnet**: Asserts the wallet has a minimum balance of `5 XLM` (to satisfy reserves). If insufficient, it halts with an error and displays the public key.
|
|
123
|
+
- On success, updates `contract_id` and `wallet_public_key` in `mycelium.toml`.
|
|
124
|
+
* **Flags**:
|
|
125
|
+
- `--network <name>`: Override the network target (`testnet` or `mainnet`).
|
|
126
|
+
- `--wasm <path>`: Override the WASM file path to deploy.
|
|
127
|
+
|
|
128
|
+
### 6. `mycelium register`
|
|
129
|
+
Submits a signed transaction to the global Hive Registry mapping your agent's configuration parameters.
|
|
130
|
+
|
|
131
|
+
* **Syntax**:
|
|
132
|
+
```bash
|
|
133
|
+
mycelium register [options]
|
|
134
|
+
```
|
|
135
|
+
* **Details**:
|
|
136
|
+
- Packages the agent name, service endpoint, public address, and the SHA-256 hash of capability tags.
|
|
137
|
+
- Verifies that local keys match the owner keys if updating an existing registration.
|
|
138
|
+
|
|
139
|
+
### 7. `mycelium status`
|
|
140
|
+
Displays the comprehensive deployment and configuration status of the active project in a single screen.
|
|
141
|
+
|
|
142
|
+
* **Syntax**:
|
|
143
|
+
```bash
|
|
144
|
+
mycelium status
|
|
145
|
+
```
|
|
146
|
+
* **Output Fields**:
|
|
147
|
+
- **Wallet Address**: G-address extracted from local wallet config.
|
|
148
|
+
- **Wallet Balance**: Balance retrieved from Horizon RPC.
|
|
149
|
+
- **Network**: Deployed target network passphrase identifier.
|
|
150
|
+
- **Contract Deployment**: Verification status of the contract ID on the ledger.
|
|
151
|
+
- **Registry Entry**: Name verification, registration state, reputation score, and API endpoint details.
|
|
152
|
+
|
|
153
|
+
### 8. `mycelium fund`
|
|
154
|
+
Explicitly requests Friendbot funding for the agent's wallet. Used to top up testnet gas balances.
|
|
155
|
+
|
|
156
|
+
* **Syntax**:
|
|
157
|
+
```bash
|
|
158
|
+
mycelium fund [options]
|
|
159
|
+
```
|
|
160
|
+
* **Flags**:
|
|
161
|
+
- `--amount <number>`: Request a specific amount (if supported by network node limits).
|
|
162
|
+
|
|
163
|
+
### 9. `mycelium call`
|
|
164
|
+
Invokes an on-chain contract function directly from your terminal.
|
|
165
|
+
|
|
166
|
+
* **Syntax**:
|
|
167
|
+
```bash
|
|
168
|
+
mycelium call <function_name> [args...] [options]
|
|
169
|
+
```
|
|
170
|
+
* **Details**:
|
|
171
|
+
- Automatically maps plain argument strings to the correct Soroban type based on the contract specification.
|
|
172
|
+
* **Flags**:
|
|
173
|
+
- `--read-only`: Execute as a simulate-only view invocation (free, does not require passphrase or signature).
|
|
174
|
+
- `--contract <id>`: Override the target contract ID.
|
|
175
|
+
|
|
176
|
+
### 10. `mycelium resolve`
|
|
177
|
+
Queries the on-chain Hive Registry to resolve details of another agent by its name.
|
|
178
|
+
|
|
179
|
+
* **Syntax**:
|
|
180
|
+
```bash
|
|
181
|
+
mycelium resolve <agent_name>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### 11. `mycelium pay`
|
|
185
|
+
Triggers an agent-to-agent XLM settlement payment. It resolves the destination agent's wallet address from the registry.
|
|
186
|
+
|
|
187
|
+
* **Syntax**:
|
|
188
|
+
```bash
|
|
189
|
+
mycelium pay <recipient_name_or_address> <amount_xlm>
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### 12. `mycelium events` / `mycelium logs`
|
|
193
|
+
Streams on-chain event topics emitted by the agent's smart contract.
|
|
194
|
+
|
|
195
|
+
* **Syntax**:
|
|
196
|
+
```bash
|
|
197
|
+
mycelium events [options]
|
|
198
|
+
```
|
|
199
|
+
* **Flags**:
|
|
200
|
+
- `--contract <id>`: Override the contract ID to monitor.
|
|
201
|
+
- `--start-ledger <number>`: Begin streaming historical events from a specific ledger sequence.
|
|
202
|
+
|
|
203
|
+
### 13. `mycelium doctor`
|
|
204
|
+
Runs a suite of sanity checks to verify the state of your local toolchain:
|
|
205
|
+
1. Asserts `stellar-cli` is present on your system path.
|
|
206
|
+
2. Checks if local cargo/wasm targets are properly configured.
|
|
207
|
+
3. Tests network connectivity and latency to Horizon and Soroban RPC nodes.
|
|
208
|
+
4. Identifies version mismatches and prints corrective shell actions.
|
|
209
|
+
|
|
210
|
+
* **Syntax**:
|
|
211
|
+
```bash
|
|
212
|
+
mycelium doctor
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### 14. `mycelium run`
|
|
216
|
+
Spins up the agent's execution loop (`agent.py`) in your terminal, pre-loading context configurations, wallet files, and contract IDs from the project directory.
|
|
217
|
+
|
|
218
|
+
* **Syntax**:
|
|
219
|
+
```bash
|
|
220
|
+
mycelium run [options]
|
|
221
|
+
```
|
|
222
|
+
* **Flags**:
|
|
223
|
+
- `--steps <number>`: Limit the maximum number of steps the LLM loop is permitted to run.
|
|
224
|
+
|
|
225
|
+
### 15. `mycelium test`
|
|
226
|
+
Performs a simulation dry-run of the agent loop. It intercepts all state-changing contract calls, executes them via simulation, logs estimated resource fees, and returns without signing or broadcasting transactions.
|
|
227
|
+
|
|
228
|
+
* **Syntax**:
|
|
229
|
+
```bash
|
|
230
|
+
mycelium test
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## 🔐 Environment Variables
|
|
236
|
+
|
|
237
|
+
* `MYCELIUM_DECRYPT_KEY`: Set this env variable to bypass interactive wallet decryption password prompts. Essential for CI/CD and non-interactive workflows.
|
|
238
|
+
* `MYCELIUM_CONTRACT_ID`: Override default contract target.
|
|
239
|
+
* `STELLAR_NETWORK`: Overrides default network selection (`testnet` / `mainnet`).
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
mycelium_cli/__init__.py,sha256=tpCzAXMonpTR8IZGY0iylQ4F7z53U94PimWsUmz9WkE,23
|
|
2
|
+
mycelium_cli/config.py,sha256=6sGrJxXgRfC6PkpgZneDRAi41NFKxl3jjInsHiWWR98,1469
|
|
3
|
+
mycelium_cli/main.py,sha256=a-YO4uwn_9mud3LrIvFr0Hx7koNf0RKPdAfDoTCNlFg,12174
|
|
4
|
+
mycelium_cli/commands/__init__.py,sha256=2bKymwd1hBv5CD-sr9Fi7PRbj6EfrtzlFX9CoKerQtU,26
|
|
5
|
+
mycelium_cli/commands/agent.py,sha256=c-lzZhN3F9kCP_NCAE3abcSdAIUG09KXtCXkaoKDcxI,2869
|
|
6
|
+
mycelium_cli/commands/call.py,sha256=1lJKLv-dPLMyPQQGjQ0c5EHJhpazPy33LqYgvBa-UrM,3117
|
|
7
|
+
mycelium_cli/commands/check.py,sha256=4jn47BAtpUwqajFeE4_lwozEbA8seURz2medy0mXsNc,662
|
|
8
|
+
mycelium_cli/commands/compile.py,sha256=uM1OCN9s0w1TKPYH7I6-tCFwc3cG7ztk15-TsrlnTQs,1962
|
|
9
|
+
mycelium_cli/commands/deploy.py,sha256=0o7uSyEJd1uuUQn7aeeWBeegyab5uKz-AQ4BtM0D8Ws,4993
|
|
10
|
+
mycelium_cli/commands/discover.py,sha256=SNNyeZzc-G8WqvGi5oQx0A9dI7CiZiJBNLPwF_RWxW0,2495
|
|
11
|
+
mycelium_cli/commands/doctor.py,sha256=TzKR14VHSGVD0BBDIDo1QqG1j8frUlQJpsxHgYkDbSs,3586
|
|
12
|
+
mycelium_cli/commands/events.py,sha256=M2jvIMJSZRFHWViawxLu3TrHJqmBhMOXuD1PMPKhik0,4068
|
|
13
|
+
mycelium_cli/commands/fund.py,sha256=CmXjmSOS7L3zJbuC1ZfcLE8P-NWclomG8nU8wCCTxp4,2148
|
|
14
|
+
mycelium_cli/commands/init.py,sha256=qXdp0N6-gFHp3MYP0yY6odh8J9p8VQXPDVSFiXOyA4A,6579
|
|
15
|
+
mycelium_cli/commands/newwallet.py,sha256=Fmu_GDVjeZyWX3S8dyMHzDw4j-65FtQKwaqKPO-zH-A,2053
|
|
16
|
+
mycelium_cli/commands/pay.py,sha256=8On-vC-5IAeBb8VEXNfZU0lTLfpVPpn_JfwTLCbNmGw,2905
|
|
17
|
+
mycelium_cli/commands/register.py,sha256=rKYPk4-EUcUDEV4O6OAy1z-NO3bO-BuA78Xlpr8x8Kg,2425
|
|
18
|
+
mycelium_cli/commands/resolve.py,sha256=NOsUOqV-Pe_P8nz_3agHbEUzEK5t8FyAI7i5vcMN-Do,1939
|
|
19
|
+
mycelium_cli/commands/run.py,sha256=foQSkQxq4qcft6hZXr68mzxQq00B-3fbPLiRUv5b5hs,1208
|
|
20
|
+
mycelium_cli/commands/status.py,sha256=ydVsmbTefvfP08doUXB1dPWerRjfSbWZOY89an1nJYQ,4295
|
|
21
|
+
mycelium_cli/commands/test.py,sha256=wmltJEVD8GQnKBoUwxPv3P9gxQVU1tURqsc3ijze-W0,2703
|
|
22
|
+
mycelium_cli-0.1.0.dist-info/METADATA,sha256=kpBlnMOZYNwT88E8lVxWFpp4RiloAiyijyXErztqhXU,8901
|
|
23
|
+
mycelium_cli-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
24
|
+
mycelium_cli-0.1.0.dist-info/entry_points.txt,sha256=i9ViGx8gDutPLaEaPtl77T2u-WVOVZFWz3I8Bo1viqg,52
|
|
25
|
+
mycelium_cli-0.1.0.dist-info/top_level.txt,sha256=TiBW8eXzuqoNq1iHWzrjkpZ1RK2TXNd144SXBzcICds,13
|
|
26
|
+
mycelium_cli-0.1.0.dist-info/RECORD,,
|