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.
Files changed (36) hide show
  1. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/PKG-INFO +1 -1
  2. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/discover.py +11 -2
  3. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/jobs.py +3 -0
  4. mycelium_cli-0.3.0/mycelium_cli/commands/memory.py +206 -0
  5. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/main.py +5 -1
  6. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli.egg-info/PKG-INFO +1 -1
  7. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli.egg-info/SOURCES.txt +1 -0
  8. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/pyproject.toml +1 -1
  9. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/README.md +0 -0
  10. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/__init__.py +0 -0
  11. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/__init__.py +0 -0
  12. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/agent.py +0 -0
  13. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/call.py +0 -0
  14. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/check.py +0 -0
  15. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/compile.py +0 -0
  16. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/deal.py +0 -0
  17. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/deploy.py +0 -0
  18. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/doctor.py +0 -0
  19. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/events.py +0 -0
  20. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/fund.py +0 -0
  21. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/init.py +0 -0
  22. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/newwallet.py +0 -0
  23. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/pay.py +0 -0
  24. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/register.py +0 -0
  25. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/resolve.py +0 -0
  26. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/run.py +0 -0
  27. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/status.py +0 -0
  28. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/commands/test.py +0 -0
  29. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli/config.py +0 -0
  30. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli.egg-info/dependency_links.txt +0 -0
  31. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli.egg-info/entry_points.txt +0 -0
  32. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli.egg-info/requires.txt +0 -0
  33. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/mycelium_cli.egg-info/top_level.txt +0 -0
  34. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/setup.cfg +0 -0
  35. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/tests/test_agent.py +0 -0
  36. {mycelium_cli-0.2.0 → mycelium_cli-0.3.0}/tests/test_cli.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mycelium-cli
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Mycelium CLI — init, newwallet, compile, deploy, register
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -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
- print(f"[discover] Scanning Hive Registry {registry} on {network}...")
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(start_ledger=start_ledger, resolve=resolve)
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.2.0"
42
+ __version__ = "0.3.0"
39
43
 
40
44
 
41
45
  def _version_callback(value: bool):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mycelium-cli
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Mycelium CLI — init, newwallet, compile, deploy, register
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mycelium-cli"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  description = "Mycelium CLI — init, newwallet, compile, deploy, register"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
File without changes
File without changes