nmem-cli 0.8.8__tar.gz → 0.9.2__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 (30) hide show
  1. {nmem_cli-0.8.8 → nmem_cli-0.9.2}/PKG-INFO +1 -1
  2. {nmem_cli-0.8.8 → nmem_cli-0.9.2}/pyproject.toml +1 -1
  3. {nmem_cli-0.8.8 → nmem_cli-0.9.2}/src/nmem_cli/__init__.py +1 -1
  4. nmem_cli-0.9.2/src/nmem_cli/agent_profiles.py +298 -0
  5. {nmem_cli-0.8.8 → nmem_cli-0.9.2}/src/nmem_cli/cli.py +602 -27
  6. nmem_cli-0.9.2/src/nmem_cli/data_transfer_paths.py +45 -0
  7. nmem_cli-0.9.2/src/nmem_cli/guidance_rules.py +296 -0
  8. {nmem_cli-0.8.8 → nmem_cli-0.9.2}/src/nmem_cli/kfs_cli.py +254 -24
  9. nmem_cli-0.9.2/src/nmem_cli/memories_reclassify.py +173 -0
  10. nmem_cli-0.9.2/src/nmem_cli/memory_relations_cli.py +214 -0
  11. {nmem_cli-0.8.8 → nmem_cli-0.9.2}/.gitignore +0 -0
  12. {nmem_cli-0.8.8 → nmem_cli-0.9.2}/README.md +0 -0
  13. {nmem_cli-0.8.8 → nmem_cli-0.9.2}/src/nmem_cli/claude_paths.py +0 -0
  14. {nmem_cli-0.8.8 → nmem_cli-0.9.2}/src/nmem_cli/license_payload.py +0 -0
  15. {nmem_cli-0.8.8 → nmem_cli-0.9.2}/src/nmem_cli/py.typed +0 -0
  16. {nmem_cli-0.8.8 → nmem_cli-0.9.2}/src/nmem_cli/session_import.py +0 -0
  17. {nmem_cli-0.8.8 → nmem_cli-0.9.2}/src/nmem_cli/tui/__init__.py +0 -0
  18. {nmem_cli-0.8.8 → nmem_cli-0.9.2}/src/nmem_cli/tui/__main__.py +0 -0
  19. {nmem_cli-0.8.8 → nmem_cli-0.9.2}/src/nmem_cli/tui/api_client.py +0 -0
  20. {nmem_cli-0.8.8 → nmem_cli-0.9.2}/src/nmem_cli/tui/app.py +0 -0
  21. {nmem_cli-0.8.8 → nmem_cli-0.9.2}/src/nmem_cli/tui/screens/__init__.py +0 -0
  22. {nmem_cli-0.8.8 → nmem_cli-0.9.2}/src/nmem_cli/tui/screens/dashboard.py +0 -0
  23. {nmem_cli-0.8.8 → nmem_cli-0.9.2}/src/nmem_cli/tui/screens/graph.py +0 -0
  24. {nmem_cli-0.8.8 → nmem_cli-0.9.2}/src/nmem_cli/tui/screens/help.py +0 -0
  25. {nmem_cli-0.8.8 → nmem_cli-0.9.2}/src/nmem_cli/tui/screens/memories.py +0 -0
  26. {nmem_cli-0.8.8 → nmem_cli-0.9.2}/src/nmem_cli/tui/screens/memory_detail.py +0 -0
  27. {nmem_cli-0.8.8 → nmem_cli-0.9.2}/src/nmem_cli/tui/screens/settings.py +0 -0
  28. {nmem_cli-0.8.8 → nmem_cli-0.9.2}/src/nmem_cli/tui/screens/thread_detail.py +0 -0
  29. {nmem_cli-0.8.8 → nmem_cli-0.9.2}/src/nmem_cli/tui/screens/threads.py +0 -0
  30. {nmem_cli-0.8.8 → nmem_cli-0.9.2}/src/nmem_cli/tui/widgets/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nmem-cli
3
- Version: 0.8.8
3
+ Version: 0.9.2
4
4
  Summary: CLI and TUI for Nowledge Mem - AI memory management
5
5
  Project-URL: Homepage, https://mem.nowledge.co/
6
6
  Project-URL: Repository, https://github.com/nowledge-co/
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nmem-cli"
3
- version = "0.8.8"
3
+ version = "0.9.2"
4
4
  description = "CLI and TUI for Nowledge Mem - AI memory management"
5
5
  authors = [
6
6
  {name = "Nowledge Labs"}
@@ -20,7 +20,7 @@ Environment (overrides config file):
20
20
  NMEM_API_KEY Optional API key (Bearer auth)
21
21
  """
22
22
 
23
- __version__ = "0.8.8"
23
+ __version__ = "0.9.2"
24
24
  __author__ = "Nowledge Labs"
25
25
 
26
26
  from .cli import main
@@ -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