nmem-cli 0.8.8__tar.gz → 0.9.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.
- {nmem_cli-0.8.8 → nmem_cli-0.9.0}/PKG-INFO +1 -1
- {nmem_cli-0.8.8 → nmem_cli-0.9.0}/pyproject.toml +1 -1
- {nmem_cli-0.8.8 → nmem_cli-0.9.0}/src/nmem_cli/__init__.py +1 -1
- nmem_cli-0.9.0/src/nmem_cli/agent_profiles.py +298 -0
- {nmem_cli-0.8.8 → nmem_cli-0.9.0}/src/nmem_cli/cli.py +602 -27
- nmem_cli-0.9.0/src/nmem_cli/data_transfer_paths.py +45 -0
- nmem_cli-0.9.0/src/nmem_cli/guidance_rules.py +296 -0
- {nmem_cli-0.8.8 → nmem_cli-0.9.0}/src/nmem_cli/kfs_cli.py +254 -24
- nmem_cli-0.9.0/src/nmem_cli/memories_reclassify.py +173 -0
- nmem_cli-0.9.0/src/nmem_cli/memory_relations_cli.py +214 -0
- {nmem_cli-0.8.8 → nmem_cli-0.9.0}/.gitignore +0 -0
- {nmem_cli-0.8.8 → nmem_cli-0.9.0}/README.md +0 -0
- {nmem_cli-0.8.8 → nmem_cli-0.9.0}/src/nmem_cli/claude_paths.py +0 -0
- {nmem_cli-0.8.8 → nmem_cli-0.9.0}/src/nmem_cli/license_payload.py +0 -0
- {nmem_cli-0.8.8 → nmem_cli-0.9.0}/src/nmem_cli/py.typed +0 -0
- {nmem_cli-0.8.8 → nmem_cli-0.9.0}/src/nmem_cli/session_import.py +0 -0
- {nmem_cli-0.8.8 → nmem_cli-0.9.0}/src/nmem_cli/tui/__init__.py +0 -0
- {nmem_cli-0.8.8 → nmem_cli-0.9.0}/src/nmem_cli/tui/__main__.py +0 -0
- {nmem_cli-0.8.8 → nmem_cli-0.9.0}/src/nmem_cli/tui/api_client.py +0 -0
- {nmem_cli-0.8.8 → nmem_cli-0.9.0}/src/nmem_cli/tui/app.py +0 -0
- {nmem_cli-0.8.8 → nmem_cli-0.9.0}/src/nmem_cli/tui/screens/__init__.py +0 -0
- {nmem_cli-0.8.8 → nmem_cli-0.9.0}/src/nmem_cli/tui/screens/dashboard.py +0 -0
- {nmem_cli-0.8.8 → nmem_cli-0.9.0}/src/nmem_cli/tui/screens/graph.py +0 -0
- {nmem_cli-0.8.8 → nmem_cli-0.9.0}/src/nmem_cli/tui/screens/help.py +0 -0
- {nmem_cli-0.8.8 → nmem_cli-0.9.0}/src/nmem_cli/tui/screens/memories.py +0 -0
- {nmem_cli-0.8.8 → nmem_cli-0.9.0}/src/nmem_cli/tui/screens/memory_detail.py +0 -0
- {nmem_cli-0.8.8 → nmem_cli-0.9.0}/src/nmem_cli/tui/screens/settings.py +0 -0
- {nmem_cli-0.8.8 → nmem_cli-0.9.0}/src/nmem_cli/tui/screens/thread_detail.py +0 -0
- {nmem_cli-0.8.8 → nmem_cli-0.9.0}/src/nmem_cli/tui/screens/threads.py +0 -0
- {nmem_cli-0.8.8 → nmem_cli-0.9.0}/src/nmem_cli/tui/widgets/__init__.py +0 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
"""Agent identity commands for the nmem CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Any
|
|
9
|
+
from urllib.parse import quote
|
|
10
|
+
|
|
11
|
+
from rich import box
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
from rich.prompt import Confirm
|
|
14
|
+
from rich.table import Table
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class AgentProfileCommandDeps:
|
|
19
|
+
api_get: Callable[..., dict[str, Any]]
|
|
20
|
+
api_post: Callable[..., dict[str, Any]]
|
|
21
|
+
api_put: Callable[..., dict[str, Any]]
|
|
22
|
+
api_delete: Callable[..., dict[str, Any]]
|
|
23
|
+
is_json_mode: Callable[[], bool]
|
|
24
|
+
output_json: Callable[[Any], None]
|
|
25
|
+
print_success: Callable[[str, str | None], None]
|
|
26
|
+
print_error: Callable[[str, str, str | None], None]
|
|
27
|
+
console: Console
|
|
28
|
+
confirm: Callable[[str], bool] = Confirm.ask
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def register_agents_parser(subparsers: argparse._SubParsersAction) -> None:
|
|
32
|
+
"""Register `nmem agents` commands."""
|
|
33
|
+
parser = subparsers.add_parser(
|
|
34
|
+
"agents",
|
|
35
|
+
help="Named agent identities consumed by Context Bundle",
|
|
36
|
+
epilog="""examples:
|
|
37
|
+
nmem agents
|
|
38
|
+
nmem agents show designer
|
|
39
|
+
nmem agents upsert designer --name "Design Agent" --default-space design
|
|
40
|
+
nmem agents delete designer --yes
|
|
41
|
+
nmem --json agents list""",
|
|
42
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
43
|
+
)
|
|
44
|
+
agent_subs = parser.add_subparsers(dest="action")
|
|
45
|
+
agent_subs.add_parser("list", help="List agent identities (default)")
|
|
46
|
+
|
|
47
|
+
show = agent_subs.add_parser("show", help="Show one agent identity")
|
|
48
|
+
show.add_argument("id", help="Agent identity id")
|
|
49
|
+
|
|
50
|
+
upsert = agent_subs.add_parser(
|
|
51
|
+
"upsert",
|
|
52
|
+
help="Create or update one agent identity",
|
|
53
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
54
|
+
)
|
|
55
|
+
upsert.add_argument("id", help="Stable agent identity id")
|
|
56
|
+
upsert.add_argument("--name", dest="display_name", help="Display name")
|
|
57
|
+
upsert.add_argument("--role", help="Role, e.g. engineer, designer")
|
|
58
|
+
upsert.add_argument("--default-space", help="Default memory space id")
|
|
59
|
+
upsert.add_argument("--source-app", help="Calling app, e.g. codex, hermes")
|
|
60
|
+
upsert.add_argument("--host-agent-id", help="Host-local agent id, if known")
|
|
61
|
+
upsert.add_argument("--description", help="Short description of ownership")
|
|
62
|
+
upsert.add_argument(
|
|
63
|
+
"--rules",
|
|
64
|
+
"--instructions",
|
|
65
|
+
dest="instructions",
|
|
66
|
+
help="Agent-specific rules",
|
|
67
|
+
)
|
|
68
|
+
upsert.add_argument(
|
|
69
|
+
"--tag",
|
|
70
|
+
dest="tags",
|
|
71
|
+
action="append",
|
|
72
|
+
default=[],
|
|
73
|
+
help="Tag for this agent identity; can be repeated",
|
|
74
|
+
)
|
|
75
|
+
upsert.add_argument(
|
|
76
|
+
"--tags",
|
|
77
|
+
dest="tags_csv",
|
|
78
|
+
help="Comma-separated tags for this agent identity",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
delete = agent_subs.add_parser("delete", help="Delete one agent identity")
|
|
82
|
+
delete.add_argument("id", help="Agent identity id")
|
|
83
|
+
delete.add_argument(
|
|
84
|
+
"-y",
|
|
85
|
+
"--yes",
|
|
86
|
+
"--force",
|
|
87
|
+
action="store_true",
|
|
88
|
+
dest="yes",
|
|
89
|
+
help="Delete without interactive confirmation",
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def handle_agents_command(args: argparse.Namespace, deps: AgentProfileCommandDeps) -> int:
|
|
94
|
+
"""Dispatch `nmem agents` commands."""
|
|
95
|
+
action = getattr(args, "action", None) or "list"
|
|
96
|
+
if action == "list":
|
|
97
|
+
return cmd_agents_list(deps)
|
|
98
|
+
if action == "show":
|
|
99
|
+
return cmd_agents_show(getattr(args, "id"), deps)
|
|
100
|
+
if action == "upsert":
|
|
101
|
+
return cmd_agents_upsert(args, deps)
|
|
102
|
+
if action == "delete":
|
|
103
|
+
return cmd_agents_delete(getattr(args, "id"), getattr(args, "yes", False), deps)
|
|
104
|
+
|
|
105
|
+
deps.print_error(
|
|
106
|
+
"Unknown Subcommand",
|
|
107
|
+
f"'nmem agents {action}' is not a recognized command.",
|
|
108
|
+
"Available: nmem agents list|show|upsert|delete",
|
|
109
|
+
)
|
|
110
|
+
return 1
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def cmd_agents_list(deps: AgentProfileCommandDeps) -> int:
|
|
114
|
+
data = deps.api_get("/settings/agent-profiles")
|
|
115
|
+
profiles = _profiles_from_response(data)
|
|
116
|
+
if deps.is_json_mode():
|
|
117
|
+
deps.output_json(data)
|
|
118
|
+
return 0
|
|
119
|
+
|
|
120
|
+
if not profiles:
|
|
121
|
+
deps.console.print("[dim]No named agent identities yet.[/dim]")
|
|
122
|
+
deps.console.print(
|
|
123
|
+
"[dim]Connected tools still receive a derived default identity through Context Bundle.[/dim]"
|
|
124
|
+
)
|
|
125
|
+
return 0
|
|
126
|
+
|
|
127
|
+
table = Table(box=box.SIMPLE, header_style="bold", show_edge=False)
|
|
128
|
+
table.add_column("ID", style="cyan")
|
|
129
|
+
table.add_column("Name")
|
|
130
|
+
table.add_column("Role")
|
|
131
|
+
table.add_column("Default Space")
|
|
132
|
+
table.add_column("Source")
|
|
133
|
+
table.add_column("Host ID")
|
|
134
|
+
table.add_column("Tags")
|
|
135
|
+
for profile in profiles:
|
|
136
|
+
table.add_row(
|
|
137
|
+
str(profile.get("id", "")),
|
|
138
|
+
str(profile.get("displayName", "")),
|
|
139
|
+
str(profile.get("role") or "general"),
|
|
140
|
+
str(profile.get("defaultSpaceId") or "default"),
|
|
141
|
+
str(profile.get("sourceApp") or ""),
|
|
142
|
+
str(profile.get("hostAgentId") or ""),
|
|
143
|
+
", ".join(_list_of_strings(profile.get("tags"))),
|
|
144
|
+
)
|
|
145
|
+
deps.console.print(table)
|
|
146
|
+
return 0
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def cmd_agents_show(agent_id: str, deps: AgentProfileCommandDeps) -> int:
|
|
150
|
+
profile = _find_profile(agent_id, deps)
|
|
151
|
+
if profile is None:
|
|
152
|
+
return _not_found(agent_id, deps)
|
|
153
|
+
if deps.is_json_mode():
|
|
154
|
+
deps.output_json(profile)
|
|
155
|
+
return 0
|
|
156
|
+
|
|
157
|
+
deps.console.print()
|
|
158
|
+
deps.console.print(f"[bold]{profile.get('displayName') or profile.get('id')}[/bold]")
|
|
159
|
+
deps.console.print(f" ID: {profile.get('id', '')}")
|
|
160
|
+
deps.console.print(f" Role: {profile.get('role') or 'general'}")
|
|
161
|
+
deps.console.print(f" Default Space: {profile.get('defaultSpaceId') or 'default'}")
|
|
162
|
+
if profile.get("sourceApp"):
|
|
163
|
+
deps.console.print(f" Source App: {profile.get('sourceApp')}")
|
|
164
|
+
if profile.get("hostAgentId"):
|
|
165
|
+
deps.console.print(f" Host Agent ID: {profile.get('hostAgentId')}")
|
|
166
|
+
tags = ", ".join(_list_of_strings(profile.get("tags")))
|
|
167
|
+
if tags:
|
|
168
|
+
deps.console.print(f" Tags: {tags}")
|
|
169
|
+
if profile.get("description"):
|
|
170
|
+
deps.console.print()
|
|
171
|
+
deps.console.print(str(profile.get("description")))
|
|
172
|
+
if profile.get("instructions"):
|
|
173
|
+
deps.console.print()
|
|
174
|
+
deps.console.print("[bold]Agent Rules[/bold]")
|
|
175
|
+
deps.console.print(str(profile.get("instructions")))
|
|
176
|
+
deps.console.print()
|
|
177
|
+
return 0
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def cmd_agents_upsert(args: argparse.Namespace, deps: AgentProfileCommandDeps) -> int:
|
|
181
|
+
agent_id = str(getattr(args, "id", "")).strip()
|
|
182
|
+
payload = _payload_from_args(args)
|
|
183
|
+
exists = _find_profile(agent_id, deps) is not None
|
|
184
|
+
if exists:
|
|
185
|
+
saved = deps.api_put(
|
|
186
|
+
f"/settings/agent-profiles/{quote(agent_id, safe='')}",
|
|
187
|
+
payload,
|
|
188
|
+
)
|
|
189
|
+
action = "Updated"
|
|
190
|
+
else:
|
|
191
|
+
saved = deps.api_post("/settings/agent-profiles", payload)
|
|
192
|
+
action = "Created"
|
|
193
|
+
|
|
194
|
+
if deps.is_json_mode():
|
|
195
|
+
deps.output_json(saved)
|
|
196
|
+
else:
|
|
197
|
+
deps.print_success(action, f"{saved.get('id', agent_id)}")
|
|
198
|
+
return 0
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def cmd_agents_delete(
|
|
202
|
+
agent_id: str,
|
|
203
|
+
yes: bool,
|
|
204
|
+
deps: AgentProfileCommandDeps,
|
|
205
|
+
) -> int:
|
|
206
|
+
if not yes:
|
|
207
|
+
if deps.is_json_mode():
|
|
208
|
+
deps.output_json(
|
|
209
|
+
{
|
|
210
|
+
"error": "confirmation_required",
|
|
211
|
+
"message": "Pass --yes to delete an agent identity in JSON mode.",
|
|
212
|
+
}
|
|
213
|
+
)
|
|
214
|
+
return 1
|
|
215
|
+
if not deps.confirm(f"Delete agent identity '{agent_id}'?"):
|
|
216
|
+
deps.console.print("[dim]Cancelled.[/dim]")
|
|
217
|
+
return 0
|
|
218
|
+
|
|
219
|
+
result = deps.api_delete(f"/settings/agent-profiles/{quote(agent_id, safe='')}")
|
|
220
|
+
if deps.is_json_mode():
|
|
221
|
+
deps.output_json(result)
|
|
222
|
+
else:
|
|
223
|
+
deps.print_success("Deleted", agent_id)
|
|
224
|
+
return 0
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _profiles_from_response(data: dict[str, Any]) -> list[dict[str, Any]]:
|
|
228
|
+
raw = data.get("agentProfiles", [])
|
|
229
|
+
if not isinstance(raw, list):
|
|
230
|
+
return []
|
|
231
|
+
return [item for item in raw if isinstance(item, dict)]
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _find_profile(agent_id: str, deps: AgentProfileCommandDeps) -> dict[str, Any] | None:
|
|
235
|
+
data = deps.api_get("/settings/agent-profiles")
|
|
236
|
+
for profile in _profiles_from_response(data):
|
|
237
|
+
if str(profile.get("id")) == agent_id:
|
|
238
|
+
return profile
|
|
239
|
+
return None
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _payload_from_args(args: argparse.Namespace) -> dict[str, Any]:
|
|
243
|
+
payload: dict[str, Any] = {"id": str(getattr(args, "id", "")).strip()}
|
|
244
|
+
field_map = {
|
|
245
|
+
"display_name": "displayName",
|
|
246
|
+
"role": "role",
|
|
247
|
+
"default_space": "defaultSpaceId",
|
|
248
|
+
"source_app": "sourceApp",
|
|
249
|
+
"host_agent_id": "hostAgentId",
|
|
250
|
+
"description": "description",
|
|
251
|
+
"instructions": "instructions",
|
|
252
|
+
}
|
|
253
|
+
for attr, key in field_map.items():
|
|
254
|
+
value = getattr(args, attr, None)
|
|
255
|
+
if isinstance(value, str) and value.strip():
|
|
256
|
+
payload[key] = value.strip()
|
|
257
|
+
tags = _normalize_tags(getattr(args, "tags", []), getattr(args, "tags_csv", None))
|
|
258
|
+
if tags:
|
|
259
|
+
payload["tags"] = tags
|
|
260
|
+
return payload
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _normalize_tags(repeated: list[str] | None, csv: str | None) -> list[str]:
|
|
264
|
+
values: list[str] = []
|
|
265
|
+
for raw in repeated or []:
|
|
266
|
+
values.extend(str(raw).split(","))
|
|
267
|
+
if csv:
|
|
268
|
+
values.extend(csv.split(","))
|
|
269
|
+
|
|
270
|
+
seen: set[str] = set()
|
|
271
|
+
tags: list[str] = []
|
|
272
|
+
for raw in values:
|
|
273
|
+
tag = raw.strip()
|
|
274
|
+
if not tag or tag in seen:
|
|
275
|
+
continue
|
|
276
|
+
seen.add(tag)
|
|
277
|
+
tags.append(tag[:80])
|
|
278
|
+
if len(tags) >= 20:
|
|
279
|
+
break
|
|
280
|
+
return tags
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _list_of_strings(value: Any) -> list[str]:
|
|
284
|
+
if not isinstance(value, list):
|
|
285
|
+
return []
|
|
286
|
+
return [str(item) for item in value if str(item).strip()]
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _not_found(agent_id: str, deps: AgentProfileCommandDeps) -> int:
|
|
290
|
+
if deps.is_json_mode():
|
|
291
|
+
deps.output_json({"error": "not_found", "id": agent_id})
|
|
292
|
+
else:
|
|
293
|
+
deps.print_error(
|
|
294
|
+
"Agent Not Found",
|
|
295
|
+
f"No agent identity named '{agent_id}'.",
|
|
296
|
+
"Run `nmem agents list` to see configured identities.",
|
|
297
|
+
)
|
|
298
|
+
return 1
|