mycelium-cli 0.2.0__tar.gz → 0.3.0__tar.gz
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-0.2.0 → mycelium_cli-0.3.0}/PKG-INFO +1 -1
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/discover.py +11 -2
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/jobs.py +3 -0
- mycelium_cli-0.3.0/mycelium_cli/commands/memory.py +206 -0
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/main.py +5 -1
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli.egg-info/PKG-INFO +1 -1
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli.egg-info/SOURCES.txt +1 -0
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/pyproject.toml +1 -1
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/README.md +0 -0
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/__init__.py +0 -0
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/__init__.py +0 -0
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/agent.py +0 -0
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/call.py +0 -0
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/check.py +0 -0
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/compile.py +0 -0
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/deal.py +0 -0
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/deploy.py +0 -0
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/doctor.py +0 -0
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/events.py +0 -0
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/fund.py +0 -0
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/init.py +0 -0
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/newwallet.py +0 -0
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/pay.py +0 -0
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/register.py +0 -0
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/resolve.py +0 -0
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/run.py +0 -0
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/status.py +0 -0
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/test.py +0 -0
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/config.py +0 -0
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli.egg-info/dependency_links.txt +0 -0
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli.egg-info/entry_points.txt +0 -0
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli.egg-info/requires.txt +0 -0
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli.egg-info/top_level.txt +0 -0
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/setup.cfg +0 -0
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/tests/test_agent.py +0 -0
- {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/tests/test_cli.py +0 -0
|
@@ -31,9 +31,18 @@ def run_discover(
|
|
|
31
31
|
context = AgentContext.read_only(network_type=network)
|
|
32
32
|
hive = HiveClient(context, registry_address=registry)
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
# Hosted indexer first (instant, full history); falls back to the on-chain
|
|
35
|
+
# event-scan automatically when unreachable. An explicit --start-ledger means
|
|
36
|
+
# the caller wants the chain scan, so skip the indexer in that case.
|
|
37
|
+
prefer_indexer = start_ledger is None
|
|
38
|
+
if prefer_indexer:
|
|
39
|
+
print(f"[discover] Querying indexer (falls back to chain scan of {registry})...")
|
|
40
|
+
else:
|
|
41
|
+
print(f"[discover] Scanning Hive Registry {registry} on {network}...")
|
|
35
42
|
try:
|
|
36
|
-
agents = hive.discover_agents(
|
|
43
|
+
agents = hive.discover_agents(
|
|
44
|
+
start_ledger=start_ledger, resolve=resolve, prefer_indexer=prefer_indexer
|
|
45
|
+
)
|
|
37
46
|
except Exception as e:
|
|
38
47
|
print(f"❌ Discovery failed: {e}")
|
|
39
48
|
sys.exit(1)
|
|
@@ -155,6 +155,9 @@ def join(
|
|
|
155
155
|
board: str = typer.Option(None, "--board", help="JobBoard contract id override"),
|
|
156
156
|
):
|
|
157
157
|
"""Join a swarm job with an agreed bounty share."""
|
|
158
|
+
if not 0 < share <= 10000:
|
|
159
|
+
typer.echo(f"Error: --share must be between 1 and 10000 basis points (got {share}).")
|
|
160
|
+
raise typer.Exit(code=1)
|
|
158
161
|
client = _client(network, wallet, board, signing=True)
|
|
159
162
|
client.join_swarm(job_id, capability, share)
|
|
160
163
|
typer.echo(f"✓ Joined swarm for job #{job_id} ({share} bps, capability '{capability}').")
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""
|
|
2
|
+
`mycelium memory …` — persistent agent memory: a big mutable off-chain store
|
|
3
|
+
with a tiny on-chain commitment (root, uri, version) per agent.
|
|
4
|
+
|
|
5
|
+
Where the deal/job commands move money, memory moves *state*: an agent writes
|
|
6
|
+
durable memories off-chain (free, fast), then `anchor`s a hash of that memory
|
|
7
|
+
on-chain at checkpoints. On a fresh machine `rehydrate` reads the anchor, fetches
|
|
8
|
+
the blob, re-hashes it, and refuses to load if it doesn't match — so memory is
|
|
9
|
+
portable AND tamper-evident without putting the data itself on-chain.
|
|
10
|
+
|
|
11
|
+
remember write a memory off-chain (no tx)
|
|
12
|
+
recall semantic-ish search over off-chain memory (no tx)
|
|
13
|
+
anchor publish the blob (optional) + commit its root on-chain (1 tx)
|
|
14
|
+
verify recompute the local root and compare to the on-chain anchor
|
|
15
|
+
rehydrate on a fresh machine: read anchor → fetch blob → verify → load
|
|
16
|
+
status show local count + on-chain version/root/uri
|
|
17
|
+
|
|
18
|
+
Backend defaults to the offline local store; `--backend supermemory` uses the
|
|
19
|
+
managed cloud (needs SUPERMEMORY_API_KEY). The anchor contract + network default
|
|
20
|
+
from mycelium.toml, mirroring `deploy` / `register` / `job` / `deal`.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import os
|
|
24
|
+
from typing import List, Optional
|
|
25
|
+
|
|
26
|
+
import typer
|
|
27
|
+
|
|
28
|
+
from mycelium_cli.config import get_value
|
|
29
|
+
|
|
30
|
+
DEFAULT_WALLET_PATH = os.path.join(".mycelium", "wallet.json")
|
|
31
|
+
PASSPHRASE_ENV_VAR = "MYCELIUM_DECRYPT_KEY"
|
|
32
|
+
|
|
33
|
+
memory_app = typer.Typer(help="Persistent, portable, verifiable agent memory (off-chain + on-chain anchor).")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _resolve_passphrase(label: str = "Wallet passphrase") -> str:
|
|
37
|
+
"""MYCELIUM_DECRYPT_KEY if set, else prompt — matches the rest of the CLI."""
|
|
38
|
+
env_value = os.environ.get(PASSPHRASE_ENV_VAR)
|
|
39
|
+
if env_value:
|
|
40
|
+
return env_value
|
|
41
|
+
return typer.prompt(label, hide_input=True)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _context(network: Optional[str], wallet: str, *, signing: bool):
|
|
45
|
+
"""Build an AgentContext. Read-only commands skip wallet + passphrase."""
|
|
46
|
+
from mycelium_sdk import AgentContext
|
|
47
|
+
|
|
48
|
+
network = network or get_value("onchain", "network", "testnet")
|
|
49
|
+
if signing:
|
|
50
|
+
if not os.path.exists(wallet):
|
|
51
|
+
typer.echo(f"Error: wallet {wallet} not found. Run `mycelium newwallet` first.")
|
|
52
|
+
raise typer.Exit(code=1)
|
|
53
|
+
return AgentContext(keypair_path=wallet, network_type=network, passphrase=_resolve_passphrase())
|
|
54
|
+
return AgentContext.read_only(network_type=network)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _memory(context, backend: str, anchor: Optional[str]):
|
|
58
|
+
from mycelium_sdk import AgentMemory
|
|
59
|
+
|
|
60
|
+
anchor = anchor or get_value("memory", "anchor_address", None)
|
|
61
|
+
return AgentMemory(context, backend=backend, anchor_address=anchor)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _backend_owner(network: Optional[str], wallet: str, backend: str, anchor: Optional[str], *, signing: bool):
|
|
65
|
+
"""Common setup: context + AgentMemory, surfacing a clean error if the cloud key is missing."""
|
|
66
|
+
context = _context(network, wallet, signing=signing)
|
|
67
|
+
try:
|
|
68
|
+
mem = _memory(context, backend, anchor)
|
|
69
|
+
except RuntimeError as exc: # e.g. SupermemoryBackend without an API key
|
|
70
|
+
typer.echo(f"Error: {exc}")
|
|
71
|
+
raise typer.Exit(code=1)
|
|
72
|
+
return mem
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@memory_app.command("remember")
|
|
76
|
+
def remember(
|
|
77
|
+
content: str = typer.Argument(..., help="The memory text to store"),
|
|
78
|
+
tags: List[str] = typer.Option(None, "--tag", "-t", help="Tag(s) for the memory (repeatable)"),
|
|
79
|
+
backend: str = typer.Option("local", "--backend", help="local or supermemory"),
|
|
80
|
+
anchor: str = typer.Option(None, "--anchor", help="MemoryAnchor contract id override"),
|
|
81
|
+
network: str = typer.Option(None, help="testnet or mainnet (defaults to mycelium.toml)"),
|
|
82
|
+
wallet: str = typer.Option(DEFAULT_WALLET_PATH, help="Wallet path (identifies the memory owner)"),
|
|
83
|
+
):
|
|
84
|
+
"""Store a memory off-chain (no transaction)."""
|
|
85
|
+
mem = _backend_owner(network, wallet, backend, anchor, signing=True)
|
|
86
|
+
rid = mem.remember(content, list(tags) if tags else None)
|
|
87
|
+
typer.echo(f"✓ Remembered (id={rid}). Off-chain only — run `mycelium memory anchor` to checkpoint on-chain.")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@memory_app.command("recall")
|
|
91
|
+
def recall(
|
|
92
|
+
query: str = typer.Argument(..., help="What to search memory for"),
|
|
93
|
+
k: int = typer.Option(5, "-k", help="Number of results"),
|
|
94
|
+
backend: str = typer.Option("local", "--backend", help="local or supermemory"),
|
|
95
|
+
network: str = typer.Option(None, help="testnet or mainnet (defaults to mycelium.toml)"),
|
|
96
|
+
wallet: str = typer.Option(DEFAULT_WALLET_PATH, help="Wallet path (identifies the memory owner)"),
|
|
97
|
+
):
|
|
98
|
+
"""Search off-chain memory (no transaction)."""
|
|
99
|
+
mem = _backend_owner(network, wallet, backend, None, signing=True)
|
|
100
|
+
hits = mem.recall(query, k=k)
|
|
101
|
+
if not hits:
|
|
102
|
+
typer.echo("(no memories matched)")
|
|
103
|
+
return
|
|
104
|
+
for i, h in enumerate(hits, 1):
|
|
105
|
+
tags = f" [{', '.join(h.get('tags') or [])}]" if h.get("tags") else ""
|
|
106
|
+
score = h.get("score")
|
|
107
|
+
score_s = f" ({score:.3f})" if isinstance(score, (int, float)) else ""
|
|
108
|
+
typer.echo(f" {i}.{score_s} {h['content']}{tags}")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@memory_app.command("anchor")
|
|
112
|
+
def anchor(
|
|
113
|
+
uri: str = typer.Option("", "--uri", help="Where the blob is fetchable (https / file:// / supermemory://...)"),
|
|
114
|
+
publish: str = typer.Option(None, "--publish", help="Write the canonical blob to this file and anchor file://<path>"),
|
|
115
|
+
backend: str = typer.Option("local", "--backend", help="local or supermemory"),
|
|
116
|
+
anchor: str = typer.Option(None, "--anchor", help="MemoryAnchor contract id override"),
|
|
117
|
+
network: str = typer.Option(None, help="testnet or mainnet (defaults to mycelium.toml)"),
|
|
118
|
+
wallet: str = typer.Option(DEFAULT_WALLET_PATH, help="Wallet path"),
|
|
119
|
+
):
|
|
120
|
+
"""Checkpoint: commit the memory root (+uri) on-chain, bumping the version."""
|
|
121
|
+
mem = _backend_owner(network, wallet, backend, anchor, signing=True)
|
|
122
|
+
|
|
123
|
+
publish_fn = None
|
|
124
|
+
if publish:
|
|
125
|
+
def publish_fn(blob: bytes) -> str:
|
|
126
|
+
path = os.path.abspath(publish)
|
|
127
|
+
os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
|
|
128
|
+
with open(path, "wb") as f:
|
|
129
|
+
f.write(blob)
|
|
130
|
+
return f"file://{path}"
|
|
131
|
+
|
|
132
|
+
typer.echo("[memory] Computing root + anchoring on-chain...")
|
|
133
|
+
try:
|
|
134
|
+
version = mem.anchor(uri=uri, publish=publish_fn)
|
|
135
|
+
except Exception as exc:
|
|
136
|
+
typer.echo(f"Error anchoring: {exc}")
|
|
137
|
+
raise typer.Exit(code=1)
|
|
138
|
+
root = mem.memory_root().hex()
|
|
139
|
+
typer.echo(f"✓ Anchored version {version}")
|
|
140
|
+
typer.echo(f" root: {root}")
|
|
141
|
+
if mem.get_anchor():
|
|
142
|
+
typer.echo(f" uri: {mem.get_anchor().get('uri') or '(none)'}")
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@memory_app.command("verify")
|
|
146
|
+
def verify(
|
|
147
|
+
backend: str = typer.Option("local", "--backend", help="local or supermemory"),
|
|
148
|
+
anchor: str = typer.Option(None, "--anchor", help="MemoryAnchor contract id override"),
|
|
149
|
+
network: str = typer.Option(None, help="testnet or mainnet (defaults to mycelium.toml)"),
|
|
150
|
+
wallet: str = typer.Option(DEFAULT_WALLET_PATH, help="Wallet path"),
|
|
151
|
+
):
|
|
152
|
+
"""Recompute the local root and compare to the on-chain anchor."""
|
|
153
|
+
mem = _backend_owner(network, wallet, backend, anchor, signing=True)
|
|
154
|
+
onchain = mem.get_anchor()
|
|
155
|
+
if not onchain:
|
|
156
|
+
typer.echo("✗ No on-chain anchor for this agent yet — run `mycelium memory anchor`.")
|
|
157
|
+
raise typer.Exit(code=1)
|
|
158
|
+
if mem.verify():
|
|
159
|
+
typer.echo(f"✓ Verified: local memory matches on-chain anchor (version {onchain['version']}).")
|
|
160
|
+
else:
|
|
161
|
+
typer.echo("✗ MISMATCH: local memory differs from the on-chain anchor.")
|
|
162
|
+
typer.echo(f" local root: {mem.memory_root().hex()}")
|
|
163
|
+
typer.echo(f" on-chain root:{onchain['root'].hex()}")
|
|
164
|
+
raise typer.Exit(code=1)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@memory_app.command("rehydrate")
|
|
168
|
+
def rehydrate(
|
|
169
|
+
backend: str = typer.Option("local", "--backend", help="local or supermemory"),
|
|
170
|
+
anchor: str = typer.Option(None, "--anchor", help="MemoryAnchor contract id override"),
|
|
171
|
+
network: str = typer.Option(None, help="testnet or mainnet (defaults to mycelium.toml)"),
|
|
172
|
+
wallet: str = typer.Option(DEFAULT_WALLET_PATH, help="Wallet path"),
|
|
173
|
+
):
|
|
174
|
+
"""On a fresh machine: read the anchor, fetch the blob, verify the root, and load."""
|
|
175
|
+
mem = _backend_owner(network, wallet, backend, anchor, signing=True)
|
|
176
|
+
try:
|
|
177
|
+
out = mem.rehydrate()
|
|
178
|
+
except Exception as exc:
|
|
179
|
+
typer.echo(f"✗ Rehydrate failed: {exc}")
|
|
180
|
+
raise typer.Exit(code=1)
|
|
181
|
+
typer.echo(f"✓ Rehydrated {out['records']} record(s) from on-chain anchor version {out['version']}.")
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@memory_app.command("status")
|
|
185
|
+
def status(
|
|
186
|
+
backend: str = typer.Option("local", "--backend", help="local or supermemory"),
|
|
187
|
+
anchor: str = typer.Option(None, "--anchor", help="MemoryAnchor contract id override"),
|
|
188
|
+
network: str = typer.Option(None, help="testnet or mainnet (defaults to mycelium.toml)"),
|
|
189
|
+
wallet: str = typer.Option(DEFAULT_WALLET_PATH, help="Wallet path"),
|
|
190
|
+
):
|
|
191
|
+
"""Show local memory count and the on-chain anchor (version/root/uri)."""
|
|
192
|
+
mem = _backend_owner(network, wallet, backend, anchor, signing=True)
|
|
193
|
+
typer.echo(f"owner: {mem.owner}")
|
|
194
|
+
typer.echo(f"backend: {getattr(mem.backend, 'name', backend)}")
|
|
195
|
+
try:
|
|
196
|
+
typer.echo(f"local: {mem.backend.count()} record(s)")
|
|
197
|
+
except Exception:
|
|
198
|
+
typer.echo("local: (count unavailable)")
|
|
199
|
+
onchain = mem.get_anchor()
|
|
200
|
+
if not onchain:
|
|
201
|
+
typer.echo("on-chain: not anchored yet")
|
|
202
|
+
return
|
|
203
|
+
typer.echo(f"on-chain: version {onchain['version']}")
|
|
204
|
+
typer.echo(f" root: {onchain['root'].hex()}")
|
|
205
|
+
typer.echo(f" uri: {onchain.get('uri') or '(none)'}")
|
|
206
|
+
typer.echo(f" sync: {'in sync ✓' if mem.verify() else 'LOCAL AHEAD/DIVERGED ✗'}")
|
|
@@ -24,6 +24,7 @@ from mycelium_cli.commands.run import run_run
|
|
|
24
24
|
from mycelium_cli.commands.test import run_test
|
|
25
25
|
from mycelium_cli.commands.jobs import job_app
|
|
26
26
|
from mycelium_cli.commands.deal import deal_app
|
|
27
|
+
from mycelium_cli.commands.memory import memory_app
|
|
27
28
|
|
|
28
29
|
app = typer.Typer(help="Mycelium Developer Framework CLI")
|
|
29
30
|
# Sovereign Job Boards: `mycelium job post|list|claim|assign|join|submit|finalize|status`
|
|
@@ -31,11 +32,14 @@ app.add_typer(job_app, name="job")
|
|
|
31
32
|
# A2A commerce (conditional x402 escrow between two agents):
|
|
32
33
|
# `mycelium deal open|release|refund|status`
|
|
33
34
|
app.add_typer(deal_app, name="deal")
|
|
35
|
+
# Persistent agent memory (off-chain store + tiny on-chain anchor):
|
|
36
|
+
# `mycelium memory remember|recall|anchor|verify|rehydrate|status`
|
|
37
|
+
app.add_typer(memory_app, name="memory")
|
|
34
38
|
|
|
35
39
|
PASSPHRASE_ENV_VAR = "MYCELIUM_DECRYPT_KEY"
|
|
36
40
|
|
|
37
41
|
|
|
38
|
-
__version__ = "0.
|
|
42
|
+
__version__ = "0.3.0"
|
|
39
43
|
|
|
40
44
|
|
|
41
45
|
def _version_callback(value: bool):
|
|
@@ -22,6 +22,7 @@ mycelium_cli/commands/events.py
|
|
|
22
22
|
mycelium_cli/commands/fund.py
|
|
23
23
|
mycelium_cli/commands/init.py
|
|
24
24
|
mycelium_cli/commands/jobs.py
|
|
25
|
+
mycelium_cli/commands/memory.py
|
|
25
26
|
mycelium_cli/commands/newwallet.py
|
|
26
27
|
mycelium_cli/commands/pay.py
|
|
27
28
|
mycelium_cli/commands/register.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|