memoryhub-cli 0.4.0__tar.gz → 0.11.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.
- {memoryhub_cli-0.4.0 → memoryhub_cli-0.11.0}/.gitignore +14 -2
- memoryhub_cli-0.11.0/CHANGELOG.md +115 -0
- {memoryhub_cli-0.4.0 → memoryhub_cli-0.11.0}/PKG-INFO +55 -4
- {memoryhub_cli-0.4.0 → memoryhub_cli-0.11.0}/README.md +53 -2
- {memoryhub_cli-0.4.0 → memoryhub_cli-0.11.0}/pyproject.toml +2 -2
- {memoryhub_cli-0.4.0 → memoryhub_cli-0.11.0}/src/memoryhub_cli/__init__.py +1 -1
- {memoryhub_cli-0.4.0 → memoryhub_cli-0.11.0}/src/memoryhub_cli/admin.py +116 -48
- {memoryhub_cli-0.4.0 → memoryhub_cli-0.11.0}/src/memoryhub_cli/config.py +31 -5
- memoryhub_cli-0.11.0/src/memoryhub_cli/export.py +151 -0
- memoryhub_cli-0.11.0/src/memoryhub_cli/main.py +2256 -0
- memoryhub_cli-0.11.0/src/memoryhub_cli/output.py +76 -0
- {memoryhub_cli-0.4.0 → memoryhub_cli-0.11.0}/src/memoryhub_cli/project_config.py +196 -27
- {memoryhub_cli-0.4.0 → memoryhub_cli-0.11.0}/tests/test_admin.py +12 -12
- memoryhub_cli-0.11.0/tests/test_client_creation.py +62 -0
- memoryhub_cli-0.11.0/tests/test_compact_output.py +86 -0
- memoryhub_cli-0.11.0/tests/test_config.py +102 -0
- memoryhub_cli-0.11.0/tests/test_entity_commands.py +218 -0
- memoryhub_cli-0.11.0/tests/test_parity_commands.py +536 -0
- {memoryhub_cli-0.4.0 → memoryhub_cli-0.11.0}/tests/test_project_config.py +140 -3
- memoryhub_cli-0.4.0/CHANGELOG.md +0 -36
- memoryhub_cli-0.4.0/src/memoryhub_cli/main.py +0 -604
- {memoryhub_cli-0.4.0 → memoryhub_cli-0.11.0}/tests/__init__.py +0 -0
|
@@ -32,6 +32,18 @@ seed-clients.json
|
|
|
32
32
|
.cache_ggshield
|
|
33
33
|
.mcp.json
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
NEXT_SESSION.md
|
|
35
|
+
NEXT_SESSION*.md
|
|
37
36
|
planning/rfe_draft.md
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Research drafts not ready for public commit
|
|
40
|
+
research/agent-memory-benchmarks/
|
|
41
|
+
|
|
42
|
+
# Claude Code worktrees
|
|
43
|
+
.claude/worktrees/
|
|
44
|
+
|
|
45
|
+
# Local DB backups (created by scripts/backup-db.sh)
|
|
46
|
+
backups/
|
|
47
|
+
|
|
48
|
+
medium_article_1.pdf
|
|
49
|
+
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Changelog — memoryhub-cli
|
|
2
|
+
|
|
3
|
+
All notable changes to the `memoryhub-cli` package.
|
|
4
|
+
|
|
5
|
+
## [0.11.0] — 2026-06-12
|
|
6
|
+
|
|
7
|
+
- **Hook scaffolding**: `memoryhub config init` now generates the full
|
|
8
|
+
hook integration -- `.claude/hooks/load-memories.sh` (portable,
|
|
9
|
+
executable) and `.claude/settings.json` SessionStart entries.
|
|
10
|
+
- **Hook script portability**: CLI discovery checks PATH first (pipx/global)
|
|
11
|
+
before project venv paths. JSON parsing uses jq/python3/grep fallback chain.
|
|
12
|
+
- **Loading rule refinement**: Rule templates frame hooks as the expected
|
|
13
|
+
path, with manual MCP calls as a degraded fallback.
|
|
14
|
+
- **`memoryhub reconstruct`**: Retrieve behavioral memories sorted by
|
|
15
|
+
weight. Supports table, json, quiet, and compact output.
|
|
16
|
+
- **`memoryhub admin backfill-entities`**: Run entity extraction on
|
|
17
|
+
memories without extraction_status. Accepts `--limit` and
|
|
18
|
+
`--include-failed`.
|
|
19
|
+
- **Thread commands** (from unreleased 0.10.0): `memoryhub thread`
|
|
20
|
+
subgroup with create, append, get, list, archive, extract, fork,
|
|
21
|
+
share, and delete commands.
|
|
22
|
+
- **Entity commands** (from unreleased 0.10.0): `memoryhub entity`
|
|
23
|
+
subgroup with list, merge, and rename commands.
|
|
24
|
+
- **Session commands** (from unreleased 0.10.0): `memoryhub session`
|
|
25
|
+
subgroup with status, focus, and focus-history commands.
|
|
26
|
+
- **Graph commands** (from unreleased 0.10.0): `memoryhub graph`
|
|
27
|
+
subgroup with relate, list, and similar commands.
|
|
28
|
+
- **Curation commands** (from unreleased 0.10.0): `memoryhub curation`
|
|
29
|
+
subgroup with report, resolve, and rule commands.
|
|
30
|
+
- **Project commands** (from unreleased 0.10.0): `memoryhub project`
|
|
31
|
+
subgroup with list, create, add-member, remove-member, and describe.
|
|
32
|
+
- **SDK dependency**: Requires `memoryhub>=0.14.0` (was `>=0.3.0`).
|
|
33
|
+
|
|
34
|
+
## [0.9.0] — 2026-06-08
|
|
35
|
+
|
|
36
|
+
- **CLI/SDK parity (#257)**: `memoryhub promote`, `memoryhub graduate`,
|
|
37
|
+
`memoryhub checkpoint`, and `memoryhub project describe` complete
|
|
38
|
+
feature parity with the MCP server.
|
|
39
|
+
- **Fix**: Search command `--output` help text now lists `compact` format.
|
|
40
|
+
- **Skill**: Added `memoryhub-manage` Claude Code skill for cold-path
|
|
41
|
+
operations (#203 prototype).
|
|
42
|
+
|
|
43
|
+
## [0.8.0] — 2026-06-03
|
|
44
|
+
|
|
45
|
+
- **Obsidian export (#245)**: `memoryhub export --format obsidian` generates
|
|
46
|
+
Obsidian-compatible markdown with wikilinks and frontmatter.
|
|
47
|
+
- **Hook-aware rule templates**: `memoryhub config init` and
|
|
48
|
+
`memoryhub config regenerate` now generate rule files that check for a
|
|
49
|
+
`<memoryhub-context>` block from SessionStart hooks before falling back
|
|
50
|
+
to the manual `register_session` + `search_memory` flow.
|
|
51
|
+
|
|
52
|
+
## [0.7.0] — 2026-05-19
|
|
53
|
+
|
|
54
|
+
- **Content type support (#237)**: Write and search commands accept
|
|
55
|
+
`--content-type` for behavioral memory classification.
|
|
56
|
+
|
|
57
|
+
## [0.6.0] — 2026-05-07
|
|
58
|
+
|
|
59
|
+
- **API key authentication (#256)**: The CLI now supports API key auth
|
|
60
|
+
via `MEMORYHUB_API_KEY` env var, `--api-key` flag, or the file at
|
|
61
|
+
`~/.config/memoryhub/api-key`. This enables non-interactive use cases
|
|
62
|
+
like SessionStart hooks.
|
|
63
|
+
- **Compact output (#255)**: `--output compact` on search and list commands
|
|
64
|
+
produces content-only text wrapped in `<memoryhub-context>` tags for
|
|
65
|
+
zero-overhead LLM injection.
|
|
66
|
+
- **List command**: `memoryhub list` enumerates memories without semantic
|
|
67
|
+
ranking, with cursor-based pagination.
|
|
68
|
+
- **Server URL prompt**: `memoryhub config init` now prompts for the
|
|
69
|
+
server URL and saves it to `~/.config/memoryhub/config.json`.
|
|
70
|
+
|
|
71
|
+
## [0.5.0] — 2026-04-22
|
|
72
|
+
|
|
73
|
+
- **Structured output (#200)**: Replaced `--json` flag with `--output`
|
|
74
|
+
accepting `table`, `json`, `quiet`, and `compact` formats. All commands
|
|
75
|
+
now return structured JSON envelopes with `--output json`.
|
|
76
|
+
- **New sub-apps**: `memoryhub graph` (relate, list, similar),
|
|
77
|
+
`memoryhub curation` (report, resolve), `memoryhub session` (status,
|
|
78
|
+
focus), `memoryhub project` (list, describe, join, leave).
|
|
79
|
+
- **Update command**: `memoryhub update` for modifying existing memories
|
|
80
|
+
with version history preservation.
|
|
81
|
+
- **Config enhancements**: `--project` and `--non-interactive` flags on
|
|
82
|
+
`memoryhub config init`.
|
|
83
|
+
|
|
84
|
+
## [0.4.0] — 2026-04-14
|
|
85
|
+
|
|
86
|
+
- **Admin subcommands (#186)**: Added `memoryhub admin` command group with
|
|
87
|
+
`create-agent`, `list-agents`, `rotate-secret`, and `disable-agent` for
|
|
88
|
+
self-serve agent provisioning via the auth service REST API.
|
|
89
|
+
- **`--version` flag**: `memoryhub --version` now prints the installed version.
|
|
90
|
+
|
|
91
|
+
## [0.3.0] — 2026-04-09
|
|
92
|
+
|
|
93
|
+
- **Campaign & domain parameter support (#164)**: Added `--project-id` flag to
|
|
94
|
+
search, read, write, delete, and history commands. Added `--domain` flag to
|
|
95
|
+
search and write. When `.memoryhub.yaml` has campaigns configured, `project_id`
|
|
96
|
+
is auto-loaded from the config so the flag can be omitted.
|
|
97
|
+
|
|
98
|
+
## [0.2.0] — 2026-04-09
|
|
99
|
+
|
|
100
|
+
- Added campaign enrollment prompt to `memoryhub config init` (#160).
|
|
101
|
+
- API key check after config init (#153).
|
|
102
|
+
|
|
103
|
+
## [0.1.1] — 2026-04-09
|
|
104
|
+
|
|
105
|
+
- Fix ruff lint errors (import sorting, `Optional` → `X | Y` annotations,
|
|
106
|
+
line length). No functional changes from 0.1.0.
|
|
107
|
+
|
|
108
|
+
## [0.1.0] — 2026-04-09
|
|
109
|
+
|
|
110
|
+
- Initial release. Terminal client for MemoryHub with search, read, write,
|
|
111
|
+
delete, and history commands.
|
|
112
|
+
- `memoryhub config init` — interactive wizard for generating
|
|
113
|
+
`.memoryhub.yaml` and `.claude/rules/memoryhub-loading.md`.
|
|
114
|
+
- `memoryhub config regenerate` — re-render rule file after editing YAML.
|
|
115
|
+
- `memoryhub login` — one-time credential setup.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: memoryhub-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.0
|
|
4
4
|
Summary: CLI client for MemoryHub — centralized, governed memory for AI agents
|
|
5
5
|
Project-URL: Homepage, https://github.com/redhat-ai-americas/memory-hub
|
|
6
6
|
Project-URL: Repository, https://github.com/redhat-ai-americas/memory-hub
|
|
@@ -19,7 +19,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.13
|
|
20
20
|
Requires-Python: >=3.10
|
|
21
21
|
Requires-Dist: httpx>=0.27
|
|
22
|
-
Requires-Dist: memoryhub>=0.
|
|
22
|
+
Requires-Dist: memoryhub>=0.14.0
|
|
23
23
|
Requires-Dist: pyyaml>=6.0
|
|
24
24
|
Requires-Dist: rich>=13.0
|
|
25
25
|
Requires-Dist: typer[all]>=0.15
|
|
@@ -38,15 +38,54 @@ Command-line client for MemoryHub — centralized, governed memory for AI agents
|
|
|
38
38
|
pip install memoryhub-cli
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
##
|
|
41
|
+
## Authentication
|
|
42
|
+
|
|
43
|
+
The CLI supports two authentication modes:
|
|
44
|
+
|
|
45
|
+
**API key (recommended for non-interactive use):**
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Via environment variable
|
|
49
|
+
export MEMORYHUB_API_KEY=mh-dev-abc123
|
|
50
|
+
|
|
51
|
+
# Or place your key at ~/.config/memoryhub/api-key (mode 0600)
|
|
52
|
+
echo "mh-dev-abc123" > ~/.config/memoryhub/api-key
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
API key resolution order: `MEMORYHUB_API_KEY` env var > `~/.config/memoryhub/api-key` file > `api_key` in config.json.
|
|
56
|
+
|
|
57
|
+
**Server URL:**
|
|
58
|
+
|
|
59
|
+
The CLI needs to know where the MemoryHub server is. The SessionStart hook for Claude Code also reads from this config.
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# Via environment variable
|
|
63
|
+
export MEMORYHUB_URL=https://memoryhub.example.com/mcp/
|
|
64
|
+
|
|
65
|
+
# Or save to ~/.config/memoryhub/config.json (also set by memoryhub config init)
|
|
66
|
+
echo '{"url": "https://memoryhub.example.com/mcp/"}' > ~/.config/memoryhub/config.json
|
|
67
|
+
chmod 600 ~/.config/memoryhub/config.json
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
URL resolution: `MEMORYHUB_URL` env var > `url` in `~/.config/memoryhub/config.json`.
|
|
71
|
+
|
|
72
|
+
**OAuth (interactive setup):**
|
|
42
73
|
|
|
43
74
|
```bash
|
|
44
|
-
# Authenticate to a MemoryHub instance
|
|
45
75
|
memoryhub login
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
When both are available, API key takes precedence.
|
|
79
|
+
|
|
80
|
+
## Usage
|
|
46
81
|
|
|
82
|
+
```bash
|
|
47
83
|
# Search for memories
|
|
48
84
|
memoryhub search "deployment patterns"
|
|
49
85
|
|
|
86
|
+
# List memories by creation time (no semantic search)
|
|
87
|
+
memoryhub list --project-id my-project --max 20
|
|
88
|
+
|
|
50
89
|
# Read a specific memory
|
|
51
90
|
memoryhub read <memory-id>
|
|
52
91
|
|
|
@@ -57,6 +96,9 @@ memoryhub write "Use Podman, not Docker" --scope user --weight 0.9
|
|
|
57
96
|
memoryhub search "shared patterns" --project-id my-project --domain React
|
|
58
97
|
memoryhub write "Use vLLM for embeddings" --project-id my-project --domain ML
|
|
59
98
|
|
|
99
|
+
# Compact output for LLM context injection (content only, no metadata)
|
|
100
|
+
memoryhub search "project conventions" --output compact
|
|
101
|
+
|
|
60
102
|
# Set up project-level memory loading
|
|
61
103
|
memoryhub config init
|
|
62
104
|
memoryhub config regenerate
|
|
@@ -70,6 +112,15 @@ memoryhub admin disable-agent my-agent
|
|
|
70
112
|
|
|
71
113
|
The `--project-id` flag enables campaign-scoped memory access. When your project is enrolled in campaigns via `.memoryhub.yaml`, the CLI auto-loads the project identifier from config, so you can omit the flag in most cases. Use `--domain` to tag writes or boost domain-matching results in search.
|
|
72
114
|
|
|
115
|
+
## Output formats
|
|
116
|
+
|
|
117
|
+
The `--output` / `-o` flag controls output format on most commands:
|
|
118
|
+
|
|
119
|
+
- `table` (default) -- Rich-formatted tables for interactive use
|
|
120
|
+
- `json` -- Machine-readable JSON envelope (`{"status": "ok", "data": {...}}`)
|
|
121
|
+
- `quiet` -- No output (exit code only)
|
|
122
|
+
- `compact` -- Content-only text for LLM context injection, no IDs or metadata
|
|
123
|
+
|
|
73
124
|
## Project configuration
|
|
74
125
|
|
|
75
126
|
`memoryhub config` generates a project-local `.memoryhub.yaml` and a companion `.claude/rules/memoryhub-loading.md` rule file. Both files are meant to be committed so every contributor's agent inherits the same loading policy.
|
|
@@ -8,15 +8,54 @@ Command-line client for MemoryHub — centralized, governed memory for AI agents
|
|
|
8
8
|
pip install memoryhub-cli
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Authentication
|
|
12
|
+
|
|
13
|
+
The CLI supports two authentication modes:
|
|
14
|
+
|
|
15
|
+
**API key (recommended for non-interactive use):**
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Via environment variable
|
|
19
|
+
export MEMORYHUB_API_KEY=mh-dev-abc123
|
|
20
|
+
|
|
21
|
+
# Or place your key at ~/.config/memoryhub/api-key (mode 0600)
|
|
22
|
+
echo "mh-dev-abc123" > ~/.config/memoryhub/api-key
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
API key resolution order: `MEMORYHUB_API_KEY` env var > `~/.config/memoryhub/api-key` file > `api_key` in config.json.
|
|
26
|
+
|
|
27
|
+
**Server URL:**
|
|
28
|
+
|
|
29
|
+
The CLI needs to know where the MemoryHub server is. The SessionStart hook for Claude Code also reads from this config.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Via environment variable
|
|
33
|
+
export MEMORYHUB_URL=https://memoryhub.example.com/mcp/
|
|
34
|
+
|
|
35
|
+
# Or save to ~/.config/memoryhub/config.json (also set by memoryhub config init)
|
|
36
|
+
echo '{"url": "https://memoryhub.example.com/mcp/"}' > ~/.config/memoryhub/config.json
|
|
37
|
+
chmod 600 ~/.config/memoryhub/config.json
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
URL resolution: `MEMORYHUB_URL` env var > `url` in `~/.config/memoryhub/config.json`.
|
|
41
|
+
|
|
42
|
+
**OAuth (interactive setup):**
|
|
12
43
|
|
|
13
44
|
```bash
|
|
14
|
-
# Authenticate to a MemoryHub instance
|
|
15
45
|
memoryhub login
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
When both are available, API key takes precedence.
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
16
51
|
|
|
52
|
+
```bash
|
|
17
53
|
# Search for memories
|
|
18
54
|
memoryhub search "deployment patterns"
|
|
19
55
|
|
|
56
|
+
# List memories by creation time (no semantic search)
|
|
57
|
+
memoryhub list --project-id my-project --max 20
|
|
58
|
+
|
|
20
59
|
# Read a specific memory
|
|
21
60
|
memoryhub read <memory-id>
|
|
22
61
|
|
|
@@ -27,6 +66,9 @@ memoryhub write "Use Podman, not Docker" --scope user --weight 0.9
|
|
|
27
66
|
memoryhub search "shared patterns" --project-id my-project --domain React
|
|
28
67
|
memoryhub write "Use vLLM for embeddings" --project-id my-project --domain ML
|
|
29
68
|
|
|
69
|
+
# Compact output for LLM context injection (content only, no metadata)
|
|
70
|
+
memoryhub search "project conventions" --output compact
|
|
71
|
+
|
|
30
72
|
# Set up project-level memory loading
|
|
31
73
|
memoryhub config init
|
|
32
74
|
memoryhub config regenerate
|
|
@@ -40,6 +82,15 @@ memoryhub admin disable-agent my-agent
|
|
|
40
82
|
|
|
41
83
|
The `--project-id` flag enables campaign-scoped memory access. When your project is enrolled in campaigns via `.memoryhub.yaml`, the CLI auto-loads the project identifier from config, so you can omit the flag in most cases. Use `--domain` to tag writes or boost domain-matching results in search.
|
|
42
84
|
|
|
85
|
+
## Output formats
|
|
86
|
+
|
|
87
|
+
The `--output` / `-o` flag controls output format on most commands:
|
|
88
|
+
|
|
89
|
+
- `table` (default) -- Rich-formatted tables for interactive use
|
|
90
|
+
- `json` -- Machine-readable JSON envelope (`{"status": "ok", "data": {...}}`)
|
|
91
|
+
- `quiet` -- No output (exit code only)
|
|
92
|
+
- `compact` -- Content-only text for LLM context injection, no IDs or metadata
|
|
93
|
+
|
|
43
94
|
## Project configuration
|
|
44
95
|
|
|
45
96
|
`memoryhub config` generates a project-local `.memoryhub.yaml` and a companion `.claude/rules/memoryhub-loading.md` rule file. Both files are meant to be committed so every contributor's agent inherits the same loading policy.
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "memoryhub-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.11.0"
|
|
8
8
|
description = "CLI client for MemoryHub — centralized, governed memory for AI agents"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -14,7 +14,7 @@ authors = [
|
|
|
14
14
|
]
|
|
15
15
|
keywords = ["ai", "agents", "memory", "cli", "mcp"]
|
|
16
16
|
dependencies = [
|
|
17
|
-
"memoryhub>=0.
|
|
17
|
+
"memoryhub>=0.14.0",
|
|
18
18
|
"typer[all]>=0.15",
|
|
19
19
|
"rich>=13.0",
|
|
20
20
|
"pyyaml>=6.0",
|
|
@@ -3,23 +3,28 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
-
import json
|
|
7
6
|
import os
|
|
8
7
|
|
|
9
8
|
import httpx
|
|
10
9
|
import typer
|
|
11
|
-
from rich.console import Console
|
|
12
10
|
from rich.table import Table
|
|
13
11
|
|
|
14
12
|
from memoryhub_cli.config import CONFIG_DIR, load_config
|
|
13
|
+
from memoryhub_cli.output import (
|
|
14
|
+
EXIT_AUTH_ERROR,
|
|
15
|
+
EXIT_CLIENT_ERROR,
|
|
16
|
+
EXIT_SERVER_ERROR,
|
|
17
|
+
OutputFormat,
|
|
18
|
+
console,
|
|
19
|
+
handle_error,
|
|
20
|
+
json_success,
|
|
21
|
+
)
|
|
15
22
|
|
|
16
23
|
admin_app = typer.Typer(
|
|
17
24
|
name="admin",
|
|
18
25
|
help="Manage agents and OAuth clients.",
|
|
19
26
|
no_args_is_help=True,
|
|
20
27
|
)
|
|
21
|
-
console = Console()
|
|
22
|
-
err_console = Console(stderr=True)
|
|
23
28
|
|
|
24
29
|
|
|
25
30
|
def _run(coro):
|
|
@@ -27,7 +32,7 @@ def _run(coro):
|
|
|
27
32
|
return asyncio.run(coro)
|
|
28
33
|
|
|
29
34
|
|
|
30
|
-
def _get_admin_key() -> str:
|
|
35
|
+
def _get_admin_key(output: OutputFormat) -> str:
|
|
31
36
|
"""Resolve the admin key from env var or config file."""
|
|
32
37
|
key = os.environ.get("MEMORYHUB_ADMIN_KEY")
|
|
33
38
|
if key:
|
|
@@ -38,15 +43,16 @@ def _get_admin_key() -> str:
|
|
|
38
43
|
if key:
|
|
39
44
|
return key
|
|
40
45
|
|
|
41
|
-
|
|
42
|
-
"
|
|
43
|
-
"Set MEMORYHUB_ADMIN_KEY or add 'admin_key' to "
|
|
44
|
-
"~/.config/memoryhub/config.json."
|
|
46
|
+
handle_error(
|
|
47
|
+
"missing_config",
|
|
48
|
+
"No admin key found. Set MEMORYHUB_ADMIN_KEY or add 'admin_key' to "
|
|
49
|
+
"~/.config/memoryhub/config.json.",
|
|
50
|
+
output,
|
|
51
|
+
EXIT_CLIENT_ERROR,
|
|
45
52
|
)
|
|
46
|
-
raise typer.Exit(1)
|
|
47
53
|
|
|
48
54
|
|
|
49
|
-
def _get_auth_url() -> str:
|
|
55
|
+
def _get_auth_url(output: OutputFormat) -> str:
|
|
50
56
|
"""Resolve the auth service base URL (without trailing slash)."""
|
|
51
57
|
url = os.environ.get("MEMORYHUB_AUTH_URL")
|
|
52
58
|
if url:
|
|
@@ -59,16 +65,15 @@ def _get_auth_url() -> str:
|
|
|
59
65
|
|
|
60
66
|
url = config.get("url")
|
|
61
67
|
if url:
|
|
62
|
-
# Fall back to base MCP URL with /admin stripped — assume auth
|
|
63
|
-
# is co-located at the base URL.
|
|
64
68
|
return url.rstrip("/")
|
|
65
69
|
|
|
66
|
-
|
|
67
|
-
"
|
|
68
|
-
"Set MEMORYHUB_AUTH_URL, or add 'auth_url' to "
|
|
69
|
-
"~/.config/memoryhub/config.json."
|
|
70
|
+
handle_error(
|
|
71
|
+
"missing_config",
|
|
72
|
+
"No auth URL found. Set MEMORYHUB_AUTH_URL, or add 'auth_url' to "
|
|
73
|
+
"~/.config/memoryhub/config.json.",
|
|
74
|
+
output,
|
|
75
|
+
EXIT_CLIENT_ERROR,
|
|
70
76
|
)
|
|
71
|
-
raise typer.Exit(1)
|
|
72
77
|
|
|
73
78
|
|
|
74
79
|
def _admin_headers(admin_key: str) -> dict[str, str]:
|
|
@@ -97,14 +102,16 @@ def create_agent(
|
|
|
97
102
|
"--write-config",
|
|
98
103
|
help="Write the client_secret to ~/.config/memoryhub/api-key",
|
|
99
104
|
),
|
|
100
|
-
|
|
105
|
+
output: OutputFormat = typer.Option(
|
|
106
|
+
OutputFormat.table, "--output", "-o", help="Output format: table, json, quiet",
|
|
107
|
+
),
|
|
101
108
|
):
|
|
102
109
|
"""Create a new agent (OAuth client).
|
|
103
110
|
|
|
104
111
|
The client_secret is shown only once. Save it immediately.
|
|
105
112
|
"""
|
|
106
|
-
admin_key = _get_admin_key()
|
|
107
|
-
auth_url = _get_auth_url()
|
|
113
|
+
admin_key = _get_admin_key(output)
|
|
114
|
+
auth_url = _get_auth_url(output)
|
|
108
115
|
scope_list = [s.strip() for s in scopes.split(",") if s.strip()]
|
|
109
116
|
|
|
110
117
|
body = {
|
|
@@ -128,11 +135,13 @@ def create_agent(
|
|
|
128
135
|
try:
|
|
129
136
|
data = _run(_do())
|
|
130
137
|
except httpx.HTTPStatusError as exc:
|
|
131
|
-
_handle_http_error(exc)
|
|
138
|
+
_handle_http_error(exc, output)
|
|
132
139
|
return
|
|
133
140
|
|
|
134
|
-
if
|
|
135
|
-
|
|
141
|
+
if output == OutputFormat.json:
|
|
142
|
+
json_success(data)
|
|
143
|
+
return
|
|
144
|
+
if output == OutputFormat.quiet:
|
|
136
145
|
return
|
|
137
146
|
|
|
138
147
|
console.print("[green]Agent created successfully.[/green]\n")
|
|
@@ -159,11 +168,13 @@ def create_agent(
|
|
|
159
168
|
|
|
160
169
|
@admin_app.command("list-agents")
|
|
161
170
|
def list_agents(
|
|
162
|
-
|
|
171
|
+
output: OutputFormat = typer.Option(
|
|
172
|
+
OutputFormat.table, "--output", "-o", help="Output format: table, json, quiet",
|
|
173
|
+
),
|
|
163
174
|
):
|
|
164
175
|
"""List all registered agents (OAuth clients)."""
|
|
165
|
-
admin_key = _get_admin_key()
|
|
166
|
-
auth_url = _get_auth_url()
|
|
176
|
+
admin_key = _get_admin_key(output)
|
|
177
|
+
auth_url = _get_auth_url(output)
|
|
167
178
|
|
|
168
179
|
async def _do():
|
|
169
180
|
async with httpx.AsyncClient() as client:
|
|
@@ -178,11 +189,13 @@ def list_agents(
|
|
|
178
189
|
try:
|
|
179
190
|
data = _run(_do())
|
|
180
191
|
except httpx.HTTPStatusError as exc:
|
|
181
|
-
_handle_http_error(exc)
|
|
192
|
+
_handle_http_error(exc, output)
|
|
182
193
|
return
|
|
183
194
|
|
|
184
|
-
if
|
|
185
|
-
|
|
195
|
+
if output == OutputFormat.json:
|
|
196
|
+
json_success(data)
|
|
197
|
+
return
|
|
198
|
+
if output == OutputFormat.quiet:
|
|
186
199
|
return
|
|
187
200
|
|
|
188
201
|
if not data:
|
|
@@ -214,14 +227,16 @@ def list_agents(
|
|
|
214
227
|
@admin_app.command("rotate-secret")
|
|
215
228
|
def rotate_secret(
|
|
216
229
|
client_id: str = typer.Argument(..., help="Client ID of the agent"),
|
|
217
|
-
|
|
230
|
+
output: OutputFormat = typer.Option(
|
|
231
|
+
OutputFormat.table, "--output", "-o", help="Output format: table, json, quiet",
|
|
232
|
+
),
|
|
218
233
|
):
|
|
219
234
|
"""Rotate the client secret for an agent.
|
|
220
235
|
|
|
221
236
|
The new secret is shown only once. Save it immediately.
|
|
222
237
|
"""
|
|
223
|
-
admin_key = _get_admin_key()
|
|
224
|
-
auth_url = _get_auth_url()
|
|
238
|
+
admin_key = _get_admin_key(output)
|
|
239
|
+
auth_url = _get_auth_url(output)
|
|
225
240
|
|
|
226
241
|
async def _do():
|
|
227
242
|
async with httpx.AsyncClient() as client:
|
|
@@ -236,11 +251,13 @@ def rotate_secret(
|
|
|
236
251
|
try:
|
|
237
252
|
data = _run(_do())
|
|
238
253
|
except httpx.HTTPStatusError as exc:
|
|
239
|
-
_handle_http_error(exc)
|
|
254
|
+
_handle_http_error(exc, output)
|
|
240
255
|
return
|
|
241
256
|
|
|
242
|
-
if
|
|
243
|
-
|
|
257
|
+
if output == OutputFormat.json:
|
|
258
|
+
json_success(data)
|
|
259
|
+
return
|
|
260
|
+
if output == OutputFormat.quiet:
|
|
244
261
|
return
|
|
245
262
|
|
|
246
263
|
console.print(f"[green]Secret rotated for {data['client_id']}.[/green]\n")
|
|
@@ -255,10 +272,13 @@ def rotate_secret(
|
|
|
255
272
|
@admin_app.command("disable-agent")
|
|
256
273
|
def disable_agent(
|
|
257
274
|
client_id: str = typer.Argument(..., help="Client ID of the agent to disable"),
|
|
275
|
+
output: OutputFormat = typer.Option(
|
|
276
|
+
OutputFormat.table, "--output", "-o", help="Output format: table, json, quiet",
|
|
277
|
+
),
|
|
258
278
|
):
|
|
259
279
|
"""Disable an agent (set active=false)."""
|
|
260
|
-
admin_key = _get_admin_key()
|
|
261
|
-
auth_url = _get_auth_url()
|
|
280
|
+
admin_key = _get_admin_key(output)
|
|
281
|
+
auth_url = _get_auth_url(output)
|
|
262
282
|
|
|
263
283
|
async def _do():
|
|
264
284
|
async with httpx.AsyncClient() as client:
|
|
@@ -274,7 +294,13 @@ def disable_agent(
|
|
|
274
294
|
try:
|
|
275
295
|
data = _run(_do())
|
|
276
296
|
except httpx.HTTPStatusError as exc:
|
|
277
|
-
_handle_http_error(exc)
|
|
297
|
+
_handle_http_error(exc, output)
|
|
298
|
+
return
|
|
299
|
+
|
|
300
|
+
if output == OutputFormat.json:
|
|
301
|
+
json_success(data)
|
|
302
|
+
return
|
|
303
|
+
if output == OutputFormat.quiet:
|
|
278
304
|
return
|
|
279
305
|
|
|
280
306
|
console.print(
|
|
@@ -282,11 +308,50 @@ def disable_agent(
|
|
|
282
308
|
)
|
|
283
309
|
|
|
284
310
|
|
|
311
|
+
# ── Entity backfill (via MCP, not REST) ─────────────────────────────────────
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
@admin_app.command("backfill-entities")
|
|
315
|
+
def backfill_entities(
|
|
316
|
+
limit: int = typer.Option(50, "--limit", "-n", help="Max memories to process"),
|
|
317
|
+
include_failed: bool = typer.Option(
|
|
318
|
+
False, "--include-failed", help="Reprocess previously failed memories",
|
|
319
|
+
),
|
|
320
|
+
output: OutputFormat = typer.Option(
|
|
321
|
+
OutputFormat.table, "--output", "-o", help="Output format: table, json, quiet",
|
|
322
|
+
),
|
|
323
|
+
):
|
|
324
|
+
"""Run entity extraction on memories without extraction_status."""
|
|
325
|
+
from memoryhub_cli.main import _get_client, _run_command
|
|
326
|
+
|
|
327
|
+
client = _get_client(output)
|
|
328
|
+
|
|
329
|
+
async def _do():
|
|
330
|
+
async with client:
|
|
331
|
+
return await client.backfill_entities(
|
|
332
|
+
limit=limit, include_failed=include_failed,
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
result = _run_command(_do(), output)
|
|
336
|
+
|
|
337
|
+
if output == OutputFormat.json:
|
|
338
|
+
json_success(result)
|
|
339
|
+
return
|
|
340
|
+
if output == OutputFormat.quiet:
|
|
341
|
+
return
|
|
342
|
+
|
|
343
|
+
console.print("[bold]Entity Backfill Results[/bold]\n")
|
|
344
|
+
console.print(f" Candidates: {result.get('candidates', '-')}")
|
|
345
|
+
console.print(f" Processed: {result.get('processed', '-')}")
|
|
346
|
+
console.print(f" Succeeded: {result.get('succeeded', '-')}")
|
|
347
|
+
console.print(f" Failed: {result.get('failed', '-')}")
|
|
348
|
+
|
|
349
|
+
|
|
285
350
|
# ── Helpers ──────────────────────────────────────────────────────────────────
|
|
286
351
|
|
|
287
352
|
|
|
288
|
-
def _handle_http_error(exc: httpx.HTTPStatusError) -> None:
|
|
289
|
-
"""
|
|
353
|
+
def _handle_http_error(exc: httpx.HTTPStatusError, output: OutputFormat) -> None:
|
|
354
|
+
"""Emit a structured error for HTTP failures and exit."""
|
|
290
355
|
status = exc.response.status_code
|
|
291
356
|
try:
|
|
292
357
|
detail = exc.response.json().get("detail", exc.response.text)
|
|
@@ -294,14 +359,17 @@ def _handle_http_error(exc: httpx.HTTPStatusError) -> None:
|
|
|
294
359
|
detail = exc.response.text
|
|
295
360
|
|
|
296
361
|
if status == 401:
|
|
297
|
-
|
|
298
|
-
"
|
|
362
|
+
handle_error(
|
|
363
|
+
"auth_failed",
|
|
364
|
+
"Authentication failed. Check your admin key.",
|
|
365
|
+
output,
|
|
366
|
+
EXIT_AUTH_ERROR,
|
|
299
367
|
)
|
|
300
368
|
elif status == 404:
|
|
301
|
-
|
|
369
|
+
handle_error("not_found", str(detail), output, EXIT_CLIENT_ERROR)
|
|
302
370
|
elif status == 409:
|
|
303
|
-
|
|
371
|
+
handle_error("conflict", str(detail), output, EXIT_CLIENT_ERROR)
|
|
372
|
+
elif status >= 500:
|
|
373
|
+
handle_error("server_error", f"HTTP {status}: {detail}", output, EXIT_SERVER_ERROR)
|
|
304
374
|
else:
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
raise typer.Exit(1)
|
|
375
|
+
handle_error("http_error", f"HTTP {status}: {detail}", output, EXIT_CLIENT_ERROR)
|