axon-protocol-cli 0.1.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.
@@ -0,0 +1,73 @@
1
+ Metadata-Version: 2.4
2
+ Name: axon-protocol-cli
3
+ Version: 0.1.0
4
+ Summary: CLI tool for Axon Protocol — interact with your Axon server from the terminal
5
+ Author-email: Sarthak Dhatrak <sarthakmdhtr@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/axon-protocol/axon-cli
8
+ Requires-Python: >=3.10
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: click>=8.1.0
11
+ Requires-Dist: rich>=13.0.0
12
+ Requires-Dist: httpx>=0.27.0
13
+
14
+ # Axon Protocol CLI (`axon-cli`)
15
+
16
+ The official command-line interface for the **Axon Protocol** ecosystem. Monitor and manage agents, memory, locks, and receipts directly from your terminal.
17
+
18
+ ---
19
+
20
+ ## Installation
21
+
22
+ Install `axon-cli` locally in editable mode:
23
+
24
+ ```bash
25
+ cd e:\Axon\axon-cli
26
+ pip install -e .
27
+ ```
28
+
29
+ To install from production (once published):
30
+
31
+ ```bash
32
+ pip install axon-cli
33
+ ```
34
+
35
+ ---
36
+
37
+ ## Command Reference
38
+
39
+ ### System Initialization & Health
40
+
41
+ - **`axon init`**: Initialize or modify the `.axon` config file in the current directory.
42
+ - **`axon doctor`**: Run quick readiness diagnostics on the database, Redis, and embedding model services.
43
+
44
+ ### Agent Management
45
+
46
+ - **`axon agent register <name>`**: Register a new agent in a project. Automagically updates the local `.axon` config file with the newly generated API key.
47
+ - **`axon agent me`**: Show current agent status, capabilities, and profile info.
48
+ - **`axon agent list`**: List all agents registered in the active project.
49
+
50
+ ### Vector Memory
51
+
52
+ - **`axon memory store "<content>"`**: Store a semantic memory.
53
+ - Option `-t, --tag key=value`: Attach custom metadata tags (can be passed multiple times).
54
+ - Option `-s, --scope <scope>`: Specify memory visibility scope (e.g. `agent` or `project`, defaults to `project`).
55
+ - Option `--ttl <seconds>`: Set time-to-live for the memory.
56
+ - **`axon memory search "<query>"`**: Find semantically similar memories.
57
+ - Option `-l, --limit <n>`: Max results (default 10).
58
+ - Option `-m, --min-similarity <f>`: Similarity threshold (default 0.5).
59
+ - **`axon memory list`**: List recent memories in the project.
60
+ - **`axon memory delete <memory_id>`**: Delete a memory permanently.
61
+
62
+ ### Distributed Locking
63
+
64
+ - **`axon lock list`**: List active locks in the project.
65
+ - **`axon lock status <resource_id>`**: Get detailed status of a resource lock.
66
+ - **`axon lock acquire <resource_id>`**: Acquire an exclusive lock on a resource.
67
+ - **`axon lock release <resource_id>`**: Release a lock you hold.
68
+
69
+ ### Reasoning Receipts
70
+
71
+ - **`axon receipt list`**: Browse recent receipts.
72
+ - **`axon receipt show <receipt_id>`**: View complete details and reasoning chain for a receipt.
73
+ - **`axon receipt verify <receipt_id>`**: Recompute and cryptographically verify a receipt's integrity.
@@ -0,0 +1,60 @@
1
+ # Axon Protocol CLI (`axon-cli`)
2
+
3
+ The official command-line interface for the **Axon Protocol** ecosystem. Monitor and manage agents, memory, locks, and receipts directly from your terminal.
4
+
5
+ ---
6
+
7
+ ## Installation
8
+
9
+ Install `axon-cli` locally in editable mode:
10
+
11
+ ```bash
12
+ cd e:\Axon\axon-cli
13
+ pip install -e .
14
+ ```
15
+
16
+ To install from production (once published):
17
+
18
+ ```bash
19
+ pip install axon-cli
20
+ ```
21
+
22
+ ---
23
+
24
+ ## Command Reference
25
+
26
+ ### System Initialization & Health
27
+
28
+ - **`axon init`**: Initialize or modify the `.axon` config file in the current directory.
29
+ - **`axon doctor`**: Run quick readiness diagnostics on the database, Redis, and embedding model services.
30
+
31
+ ### Agent Management
32
+
33
+ - **`axon agent register <name>`**: Register a new agent in a project. Automagically updates the local `.axon` config file with the newly generated API key.
34
+ - **`axon agent me`**: Show current agent status, capabilities, and profile info.
35
+ - **`axon agent list`**: List all agents registered in the active project.
36
+
37
+ ### Vector Memory
38
+
39
+ - **`axon memory store "<content>"`**: Store a semantic memory.
40
+ - Option `-t, --tag key=value`: Attach custom metadata tags (can be passed multiple times).
41
+ - Option `-s, --scope <scope>`: Specify memory visibility scope (e.g. `agent` or `project`, defaults to `project`).
42
+ - Option `--ttl <seconds>`: Set time-to-live for the memory.
43
+ - **`axon memory search "<query>"`**: Find semantically similar memories.
44
+ - Option `-l, --limit <n>`: Max results (default 10).
45
+ - Option `-m, --min-similarity <f>`: Similarity threshold (default 0.5).
46
+ - **`axon memory list`**: List recent memories in the project.
47
+ - **`axon memory delete <memory_id>`**: Delete a memory permanently.
48
+
49
+ ### Distributed Locking
50
+
51
+ - **`axon lock list`**: List active locks in the project.
52
+ - **`axon lock status <resource_id>`**: Get detailed status of a resource lock.
53
+ - **`axon lock acquire <resource_id>`**: Acquire an exclusive lock on a resource.
54
+ - **`axon lock release <resource_id>`**: Release a lock you hold.
55
+
56
+ ### Reasoning Receipts
57
+
58
+ - **`axon receipt list`**: Browse recent receipts.
59
+ - **`axon receipt show <receipt_id>`**: View complete details and reasoning chain for a receipt.
60
+ - **`axon receipt verify <receipt_id>`**: Recompute and cryptographically verify a receipt's integrity.
@@ -0,0 +1 @@
1
+ # axon_cli package
@@ -0,0 +1 @@
1
+ # axon_cli/commands package
@@ -0,0 +1,105 @@
1
+ import click
2
+ from axon_cli.http import AxonHttpClient
3
+ from axon_cli.config import save_config, load_config
4
+ from axon_cli.display import print_success, print_info, print_table, print_panel
5
+
6
+ @click.group()
7
+ def agent():
8
+ """Manage agents and agent registration."""
9
+ pass
10
+
11
+ @agent.command("register")
12
+ @click.argument("name")
13
+ @click.option("--project-id", help="Project ID for the agent (defaults to config Project ID)")
14
+ @click.option("--org-id", help="Organization ID (optional)")
15
+ @click.option("-c", "--capability", multiple=True, help="Capabilities of the agent (e.g. -c memory -c planning)")
16
+ def register(name, project_id, org_id, capability):
17
+ """Register a new agent with the Axon server."""
18
+ config = load_config()
19
+ target_project = project_id or config.get("project_id")
20
+ if not target_project:
21
+ target_project = click.prompt("Project ID")
22
+
23
+ # Connect to client
24
+ client = AxonHttpClient()
25
+
26
+ payload = {
27
+ "name": name,
28
+ "project_id": target_project,
29
+ "org_id": org_id,
30
+ "capabilities": list(capability) if capability else []
31
+ }
32
+
33
+ print_info(f"Registering agent '{name}' in project '{target_project}'...")
34
+ res = client.post("/v1/agents/register", json=payload)
35
+
36
+ # Save Project key, project_id, and agent info to local config
37
+ api_key_to_save = res.get("api_key") or client.api_key or ""
38
+ save_config(
39
+ base_url=client.base_url,
40
+ api_key=api_key_to_save,
41
+ project_id=target_project,
42
+ agent_id=res["id"],
43
+ agent_token=res["token"]
44
+ )
45
+
46
+ # Display registration result
47
+ content = (
48
+ f"[bold]Agent ID:[/bold] {res['id']}\n"
49
+ f"[bold]Name:[/bold] {res['name']}\n"
50
+ f"[bold]Project ID:[/bold] {res['project_id']}\n"
51
+ f"[bold]API Key:[/bold] [yellow]{api_key_to_save}[/yellow] *(Saved to .axon)*\n"
52
+ f"[bold]JWT Token:[/bold] {res['token'][:30]}...\n"
53
+ f"[bold]Created At:[/bold] {res['created_at']}"
54
+ )
55
+ print_panel("Agent Registered", content, subtitle="Configuration Auto-Updated")
56
+ print_success(f"Successfully registered and authenticated agent '{name}'!")
57
+
58
+ @agent.command("me")
59
+ def me():
60
+ """Get details of the currently authenticated agent."""
61
+ client = AxonHttpClient()
62
+ if not client.api_key:
63
+ raise click.ClickException("No API key configured. Run 'axon init' or 'axon agent register'.")
64
+
65
+ print_info("Retrieving agent profile...")
66
+ res = client.get("/v1/agents/me")
67
+
68
+ content = (
69
+ f"[bold]Agent ID:[/bold] {res['id']}\n"
70
+ f"[bold]Name:[/bold] {res['name']}\n"
71
+ f"[bold]Project ID:[/bold] {res['project_id']}\n"
72
+ f"[bold]Capabilities:[/bold] {', '.join(res.get('capabilities', [])) or 'None'}\n"
73
+ f"[bold]Status:[/bold] {res.get('status', 'unknown')}\n"
74
+ f"[bold]Last Seen:[/bold] {res.get('last_seen_at', 'never')}\n"
75
+ f"[bold]Created At:[/bold] {res['created_at']}"
76
+ )
77
+ print_panel("Agent Profile", content)
78
+
79
+ @agent.command("list")
80
+ def list_agents():
81
+ """List all agents registered in the current project."""
82
+ client = AxonHttpClient()
83
+ if not client.api_key:
84
+ raise click.ClickException("No API key configured. Run 'axon init' or 'axon agent register'.")
85
+
86
+ print_info(f"Listing agents for project '{client.project_id}'...")
87
+ res = client.get("/v1/agents/list")
88
+ agents = res.get("agents", [])
89
+
90
+ if not agents:
91
+ print_info("No agents found in this project.")
92
+ return
93
+
94
+ headers = ["ID", "Name", "Capabilities", "Status", "Last Seen"]
95
+ rows = []
96
+ for a in agents:
97
+ rows.append([
98
+ a["id"],
99
+ a["name"],
100
+ ", ".join(a.get("capabilities", [])) or "None",
101
+ a.get("status", "unknown"),
102
+ a.get("last_seen_at") or "never"
103
+ ])
104
+
105
+ print_table("Project Agents", headers, rows)
@@ -0,0 +1,30 @@
1
+ import click
2
+ from axon_cli.config import save_config, load_config
3
+ from axon_cli.display import print_success, print_info
4
+
5
+ @click.command()
6
+ @click.option("--base-url", default="http://localhost:8000", help="Axon server base URL (default: http://localhost:8000)")
7
+ @click.option("--api-key", default="", help="Agent API key")
8
+ @click.option("--project-id", default="", help="Project ID")
9
+ @click.option("--interactive/--no-interactive", default=True, help="Prompt interactively if not provided via options")
10
+ def init(base_url, api_key, project_id, interactive):
11
+ """Initialize a new .axon configuration file in the current directory."""
12
+ print_info("Initializing Axon Protocol CLI...")
13
+
14
+ current_config = load_config()
15
+
16
+ if interactive:
17
+ # Prompt for base URL
18
+ default_url = current_config.get("base_url") or base_url
19
+ base_url = click.prompt("Axon server base URL", default=default_url)
20
+
21
+ # Prompt for api key
22
+ default_key = current_config.get("api_key") or api_key
23
+ api_key = click.prompt("Agent API key (optional)", default=default_key, show_default=True)
24
+
25
+ # Prompt for project ID
26
+ default_project = current_config.get("project_id") or project_id
27
+ project_id = click.prompt("Project ID (optional)", default=default_project, show_default=True)
28
+
29
+ config_path = save_config(base_url, api_key, project_id)
30
+ print_success(f"Configuration saved successfully to {config_path}")
@@ -0,0 +1,115 @@
1
+ import click
2
+ from axon_cli.http import AxonHttpClient
3
+ from axon_cli.display import print_success, print_info, print_error, print_table, print_panel
4
+
5
+ @click.group()
6
+ def lock():
7
+ """Manage distributed resource locks."""
8
+ pass
9
+
10
+ @lock.command("list")
11
+ def list_locks():
12
+ """List all active locks in the project."""
13
+ client = AxonHttpClient()
14
+ if not client.api_key:
15
+ raise click.ClickException("No API key configured. Run 'axon init' or 'axon agent register'.")
16
+
17
+ print_info("Listing active locks...")
18
+ res = client.get("/v1/lock/list")
19
+ locks = res.get("locks", [])
20
+
21
+ if not locks:
22
+ print_info("No active locks found.")
23
+ return
24
+
25
+ headers = ["Resource ID", "Holder Agent ID", "Locked At", "Expires At"]
26
+ rows = []
27
+ for l in locks:
28
+ rows.append([
29
+ l["resource_id"],
30
+ l["agent_id"],
31
+ l["locked_at"][:19],
32
+ l["expires_at"][:19]
33
+ ])
34
+
35
+ print_table("Active Locks", headers, rows)
36
+
37
+ @lock.command("status")
38
+ @click.argument("resource_id")
39
+ def status(resource_id):
40
+ """Check the lock status of a resource."""
41
+ client = AxonHttpClient()
42
+ if not client.api_key:
43
+ raise click.ClickException("No API key configured. Run 'axon init' or 'axon agent register'.")
44
+
45
+ print_info(f"Checking lock status for resource '{resource_id}'...")
46
+ res = client.get(f"/v1/lock/status/{resource_id}")
47
+
48
+ locked = res.get("locked", False)
49
+ if locked:
50
+ content = (
51
+ f"[bold]Status:[/bold] [red]LOCKED[/red]\n"
52
+ f"[bold]Resource ID:[/bold] {res['resource_id']}\n"
53
+ f"[bold]Holder Agent ID:[/bold] {res.get('holder_agent_id')}\n"
54
+ f"[bold]Locked At:[/bold] {res.get('locked_at')}\n"
55
+ f"[bold]Expires At:[/bold] {res.get('expires_at')}"
56
+ )
57
+ print_panel("Lock Status", content, border_style="red")
58
+ else:
59
+ content = (
60
+ f"[bold]Status:[/bold] [green]UNLOCKED[/green]\n"
61
+ f"[bold]Resource ID:[/bold] {res['resource_id']}"
62
+ )
63
+ print_panel("Lock Status", content, border_style="green")
64
+
65
+ @lock.command("acquire")
66
+ @click.argument("resource_id")
67
+ @click.option("-t", "--timeout", type=int, default=300, help="Expiration timeout in seconds (default: 300)")
68
+ @click.option("-m", "--metadata", multiple=True, help="Optional metadata (e.g. -m owner=daemon)")
69
+ def acquire(resource_id, timeout, metadata):
70
+ """Acquire an exclusive lock on a resource."""
71
+ client = AxonHttpClient()
72
+ if not client.api_key:
73
+ raise click.ClickException("No API key configured. Run 'axon init' or 'axon agent register'.")
74
+
75
+ meta_dict = {}
76
+ for item in metadata:
77
+ if "=" in item:
78
+ k, v = item.split("=", 1)
79
+ meta_dict[k.strip()] = v.strip()
80
+ else:
81
+ meta_dict[item.strip()] = True
82
+
83
+ payload = {
84
+ "resource_id": resource_id,
85
+ "timeout": timeout,
86
+ }
87
+ if meta_dict:
88
+ payload["metadata"] = meta_dict
89
+
90
+ print_info(f"Acquiring lock on resource '{resource_id}'...")
91
+ res = client.post("/v1/lock/acquire", json=payload)
92
+
93
+ content = (
94
+ f"[bold]Lock ID:[/bold] {res['lock_id']}\n"
95
+ f"[bold]Resource ID:[/bold] {res['resource_id']}\n"
96
+ f"[bold]Expires At:[/bold] {res['expires_at']}"
97
+ )
98
+ print_panel("Lock Acquired", content)
99
+ print_success(f"Successfully locked resource '{resource_id}'!")
100
+
101
+ @lock.command("release")
102
+ @click.argument("resource_id")
103
+ def release(resource_id):
104
+ """Release a lock on a resource."""
105
+ client = AxonHttpClient()
106
+ if not client.api_key:
107
+ raise click.ClickException("No API key configured. Run 'axon init' or 'axon agent register'.")
108
+
109
+ print_info(f"Releasing lock on resource '{resource_id}'...")
110
+ res = client.post("/v1/lock/release", params={"resource_id": resource_id})
111
+
112
+ if res.get("released"):
113
+ print_success(f"Lock on resource '{resource_id}' released successfully.")
114
+ else:
115
+ print_error(f"Failed to release lock on resource '{resource_id}'.")
@@ -0,0 +1,129 @@
1
+ import click
2
+ from axon_cli.http import AxonHttpClient
3
+ from axon_cli.display import print_success, print_info, print_table, print_panel
4
+
5
+ @click.group()
6
+ def memory():
7
+ """Manage vector memory storage and retrieval."""
8
+ pass
9
+
10
+ @memory.command("store")
11
+ @click.argument("content")
12
+ @click.option("-t", "--tag", multiple=True, help="Custom tags to attach to the memory (e.g. -t priority=high)")
13
+ @click.option("-s", "--scope", default="project", help="Scope of the memory (e.g. project, agent) (default: project)")
14
+ @click.option("--ttl", type=int, help="Time to live in seconds (optional)")
15
+ def store(content, tag, scope, ttl):
16
+ """Store a memory in the Axon server."""
17
+ client = AxonHttpClient()
18
+ if not client.api_key:
19
+ raise click.ClickException("No API key configured. Run 'axon init' or 'axon agent register'.")
20
+
21
+ tags = {}
22
+ for item in tag:
23
+ if "=" in item:
24
+ k, v = item.split("=", 1)
25
+ tags[k.strip()] = v.strip()
26
+ else:
27
+ tags[item.strip()] = True
28
+
29
+ payload = {
30
+ "content": content,
31
+ "scope": scope,
32
+ }
33
+ if tags:
34
+ payload["tags"] = tags
35
+ if ttl is not None:
36
+ payload["ttl"] = ttl
37
+
38
+ print_info("Storing memory...")
39
+ res = client.post("/v1/memory/store", json=payload)
40
+
41
+ print_success(f"Memory stored successfully!")
42
+ print_info(f"ID: {res['id']}")
43
+ print_info(f"Created At: {res['created_at']}")
44
+
45
+ @memory.command("search")
46
+ @click.argument("query")
47
+ @click.option("-l", "--limit", type=int, default=10, help="Max number of results (default: 10)")
48
+ @click.option("-m", "--min-similarity", type=float, default=0.5, help="Minimum cosine similarity threshold (default: 0.5)")
49
+ @click.option("-s", "--scope", help="Search scope filter")
50
+ def search(query, limit, min_similarity, scope):
51
+ """Semantically search stored memories."""
52
+ client = AxonHttpClient()
53
+ if not client.api_key:
54
+ raise click.ClickException("No API key configured. Run 'axon init' or 'axon agent register'.")
55
+
56
+ payload = {
57
+ "query": query,
58
+ "limit": limit,
59
+ "min_similarity": min_similarity,
60
+ }
61
+ if scope:
62
+ payload["scope"] = scope
63
+
64
+ print_info(f"Searching memories for '{query}'...")
65
+ res = client.post("/v1/memory/search", json=payload)
66
+ results = res.get("results", [])
67
+
68
+ if not results:
69
+ print_info("No matching memories found.")
70
+ return
71
+
72
+ headers = ["Similarity", "Content", "Tags", "Scope", "Created At"]
73
+ rows = []
74
+ for r in results:
75
+ tags_str = ", ".join(f"{k}={v}" for k, v in r.get("tags", {}).items()) or "None"
76
+ rows.append([
77
+ f"{r['similarity']:.4f}",
78
+ r["content"],
79
+ tags_str,
80
+ r["scope"],
81
+ r["created_at"][:19]
82
+ ])
83
+
84
+ print_table(f"Search Results for '{query}'", headers, rows)
85
+
86
+ @memory.command("list")
87
+ @click.option("-l", "--limit", type=int, default=50, help="Max number of results (default: 50)")
88
+ def list_memories(limit):
89
+ """List recently stored memories in the current project."""
90
+ client = AxonHttpClient()
91
+ if not client.api_key:
92
+ raise click.ClickException("No API key configured. Run 'axon init' or 'axon agent register'.")
93
+
94
+ print_info("Listing memories...")
95
+ res = client.get("/v1/memory/list", params={"limit": limit})
96
+ memories = res.get("memories", [])
97
+
98
+ if not memories:
99
+ print_info("No memories found.")
100
+ return
101
+
102
+ headers = ["ID", "Content", "Tags", "Scope", "Created At"]
103
+ rows = []
104
+ for m in memories:
105
+ tags_str = ", ".join(f"{k}={v}" for k, v in m.get("tags", {}).items()) or "None"
106
+ rows.append([
107
+ m["id"],
108
+ m["content"][:60] + ("..." if len(m["content"]) > 60 else ""),
109
+ tags_str,
110
+ m["scope"],
111
+ m["created_at"][:19]
112
+ ])
113
+
114
+ print_table("Recent Memories", headers, rows)
115
+
116
+ @memory.command("delete")
117
+ @click.argument("memory_id")
118
+ def delete(memory_id):
119
+ """Delete a memory permanently by its ID."""
120
+ client = AxonHttpClient()
121
+ if not client.api_key:
122
+ raise click.ClickException("No API key configured. Run 'axon init' or 'axon agent register'.")
123
+
124
+ print_info(f"Deleting memory {memory_id}...")
125
+ res = client.delete(f"/v1/memory/{memory_id}")
126
+ if res.get("deleted"):
127
+ print_success(f"Memory {memory_id} deleted successfully.")
128
+ else:
129
+ print_error(f"Failed to delete memory {memory_id}.")
@@ -0,0 +1,100 @@
1
+ import click
2
+ import json
3
+ from axon_cli.http import AxonHttpClient
4
+ from axon_cli.display import print_success, print_info, print_error, print_table
5
+
6
+ @click.group()
7
+ def message():
8
+ """Manage agent-to-agent messaging."""
9
+ pass
10
+
11
+ @message.command("send")
12
+ @click.argument("recipient_id", required=False)
13
+ @click.argument("text", required=False)
14
+ @click.option("--topic", help="Topic to publish the message to")
15
+ @click.option("--json", "json_payload", help="JSON string payload instead of plain text")
16
+ def send(recipient_id, text, topic, json_payload):
17
+ """Send a message to an agent or publish to a topic."""
18
+ if not recipient_id and not topic:
19
+ raise click.ClickException("Must specify either recipient_id or --topic.")
20
+
21
+ client = AxonHttpClient()
22
+ if not client.api_key:
23
+ raise click.ClickException("No API key configured. Run 'axon init'.")
24
+
25
+ # Build payload
26
+ payload_dict = {}
27
+ if json_payload:
28
+ try:
29
+ payload_dict = json.loads(json_payload)
30
+ except json.JSONDecodeError as e:
31
+ raise click.ClickException(f"Invalid JSON payload: {str(e)}")
32
+ elif text:
33
+ payload_dict = {"text": text}
34
+ else:
35
+ payload_dict = {}
36
+
37
+ req_body = {}
38
+ if recipient_id:
39
+ req_body["recipient_id"] = recipient_id
40
+ if topic:
41
+ req_body["topic"] = topic
42
+ req_body["payload"] = payload_dict
43
+
44
+ print_info("Sending message...")
45
+ res = client.post("/v1/messages/send", json=req_body)
46
+
47
+ print_success(f"Message sent successfully! ID: {res['message_id']}, Status: {res['status']}")
48
+
49
+ @message.command("inbox")
50
+ @click.option("--topic", help="Filter inbox by topic")
51
+ @click.option("--limit", default=50, help="Limit the number of messages to fetch")
52
+ def inbox(topic, limit):
53
+ """Fetch and view unread messages in the inbox."""
54
+ client = AxonHttpClient()
55
+ if not client.api_key:
56
+ raise click.ClickException("No API key configured. Run 'axon init'.")
57
+
58
+ params = {"limit": limit}
59
+ if topic:
60
+ params["topic"] = topic
61
+
62
+ print_info("Fetching messages from inbox...")
63
+ res = client.get("/v1/messages/inbox", params=params)
64
+ messages = res.get("messages", [])
65
+
66
+ if not messages:
67
+ print_info("Inbox is empty.")
68
+ return
69
+
70
+ headers = ["ID", "Sender ID", "Topic", "Payload", "Status", "Created At"]
71
+ rows = []
72
+ for m in messages:
73
+ payload_str = str(m.get("payload", {}))
74
+ if len(payload_str) > 40:
75
+ payload_str = payload_str[:37] + "..."
76
+ rows.append([
77
+ m["id"],
78
+ m["sender_id"],
79
+ m.get("topic") or "N/A",
80
+ payload_str,
81
+ m["status"],
82
+ m["created_at"]
83
+ ])
84
+
85
+ print_table("Agent Inbox", headers, rows)
86
+
87
+ @message.command("ack")
88
+ @click.argument("message_id")
89
+ def ack(message_id):
90
+ """Acknowledge a received message."""
91
+ client = AxonHttpClient()
92
+ if not client.api_key:
93
+ raise click.ClickException("No API key configured. Run 'axon init'.")
94
+
95
+ print_info(f"Acknowledging message {message_id}...")
96
+ res = client.post("/v1/messages/ack", params={"message_id": message_id})
97
+ if res.get("acknowledged"):
98
+ print_success(f"Message {message_id} acknowledged successfully.")
99
+ else:
100
+ print_error(f"Failed to acknowledge message {message_id}.")