mnem-suite 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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Carl Joakim Damsleth
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,153 @@
1
+ Metadata-Version: 2.4
2
+ Name: mnem-suite
3
+ Version: 0.1.0
4
+ Summary: Meta-CLI and suite hub for the mnem memory suite (yaams, cognitive-ledger, owa-piggy, owa-tools)
5
+ Author: Carl Joakim Damsleth
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 Carl Joakim Damsleth
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/damsleth/mnem
29
+ Project-URL: Source, https://github.com/damsleth/mnem
30
+ Keywords: mnem,memory,cli,agent
31
+ Classifier: License :: OSI Approved :: MIT License
32
+ Classifier: Operating System :: MacOS
33
+ Classifier: Operating System :: POSIX :: Linux
34
+ Classifier: Programming Language :: Python :: 3
35
+ Classifier: Environment :: Console
36
+ Classifier: Development Status :: 4 - Beta
37
+ Requires-Python: >=3.11
38
+ Description-Content-Type: text/markdown
39
+ License-File: LICENSE
40
+ Requires-Dist: click>=8.1
41
+ Provides-Extra: dev
42
+ Requires-Dist: pytest>=7; extra == "dev"
43
+ Dynamic: license-file
44
+
45
+ # mnem
46
+
47
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
48
+ ![status](https://img.shields.io/badge/status-pre--release-orange)
49
+
50
+ **A local-first memory suite for AI agents.** One install gets you a
51
+ two-tier memory store, an M365 read/write surface, and a single CLI
52
+ that ties them together. Your data stays on your machine.
53
+
54
+ `mnem` is the umbrella over four independent tools that already work
55
+ on their own. The umbrella adds one verb surface, one install command,
56
+ one place to find what is in the box.
57
+
58
+ ```
59
+ mnem (meta-CLI + suite hub)
60
+ |
61
+ +------------------------+------------------------+
62
+ | | | |
63
+ YAAMS cognitive-ledger owa-piggy owa-tools
64
+ (Tier 1 raw) (Tier 2 curated) (M365 auth) (M365 read/write)
65
+ ```
66
+
67
+ ## What's in the box
68
+
69
+ | Tool | Purpose | Binaries |
70
+ | --- | --- | --- |
71
+ | [**YAAMS**](https://github.com/damsleth/yaams) | Tier 1 raw memory store - every iMessage, mail, calendar event, GitHub issue, ingested and queryable from a single SQLite file. | `yaams` |
72
+ | [**cognitive-ledger**](https://github.com/damsleth/cognitive-ledger) | Tier 2 curated atomic notes engine - the gems you promote out of YAAMS and keep forever as markdown with frontmatter. | `ledger`, `ledger-obsidian`, `sheep` |
73
+ | [**owa-piggy**](https://github.com/damsleth/owa-piggy) | Microsoft 365 auth broker - turns your existing Outlook Web session into a reusable token. No app registration. | `owa-piggy` |
74
+ | [**owa-tools**](https://github.com/damsleth/owa-tools) | M365 read/write CLI suite - calendar, mail, Graph, OneDrive, scheduling, people lookup, all JSON-by-default. | `owa`, `owa-cal`, `owa-mail`, `owa-graph`, `owa-doctor`, `owa-people`, `owa-sched`, `owa-drive` |
75
+
76
+ `mnem` itself adds one more binary that routes the verbs above into
77
+ a single user-facing surface.
78
+
79
+ ## Install
80
+
81
+ ```bash
82
+ brew install damsleth/tap/mnem
83
+ ```
84
+
85
+ The Homebrew formula pulls the whole suite via dependencies. On
86
+ PyPI the package is `mnem-suite` (both `mnem` and `mnem-cli` were
87
+ already taken on PyPI by unrelated projects); the installed binary
88
+ is still `mnem`:
89
+
90
+ ```bash
91
+ pipx install mnem-suite
92
+ ```
93
+
94
+ Then:
95
+
96
+ ```bash
97
+ mnem init # detect sources, write config, run a dry-run
98
+ mnem hello # one-screen tour of the verbs
99
+ mnem doctor # health check across every tool
100
+ ```
101
+
102
+ `mnem init` is idempotent and never edits your dotfiles.
103
+
104
+ ## What can it do
105
+
106
+ ```bash
107
+ mnem query "what did we decide at the brand kickoff?"
108
+ mnem ingest # all configured sources, partial-success tolerant
109
+ mnem promote review # interactive: promote YAAMS gems to the ledger
110
+ mnem mail send --to ... # owa-mail wrapper
111
+ mnem calendar today # owa-cal wrapper
112
+ mnem ledger init # bootstrap a new ledger
113
+ mnem auth status # owa-piggy wrapper
114
+ mnem doctor # aggregate health check
115
+ mnem version # own version + observed component versions
116
+ ```
117
+
118
+ Every JSON-capable command accepts `--json` (machine mode) and
119
+ `--pretty` (human rendering). Exit codes are predictable per
120
+ [CONVENTIONS.md](CONVENTIONS.md): 0 ok, 1 user error, 2 transient,
121
+ 3 auth, 4 not found, 5 partial success.
122
+
123
+ ## First day
124
+
125
+ 1. `brew install damsleth/tap/mnem`
126
+ 2. `mnem init` - the wizard probes for iMessage, Apple Mail, Signal,
127
+ GitHub, owa-piggy, Obsidian, and an existing cognitive-ledger. It
128
+ enables what it finds and writes `enabled: false` with a hint for
129
+ what it doesn't.
130
+ 3. `mnem ingest` - first run downloads embedding models (~2 GB) with
131
+ a prompt before any download.
132
+ 4. `mnem query "..."` - ask the suite anything.
133
+
134
+ See [SUITE.md](SUITE.md) for the full data flow and architecture, and
135
+ [CONVENTIONS.md](CONVENTIONS.md) for the CLI contract every tool in
136
+ the suite conforms to.
137
+
138
+ ## Skills
139
+
140
+ Two agent skill repos sit on top of `mnem`:
141
+
142
+ - [`damsleth/SKILLS`](https://github.com/damsleth/SKILLS) - public,
143
+ reusable agent skills. Includes `/memory`, which routes through
144
+ `mnem`.
145
+ - `damsleth/SKILLS-private` - personal-infra `cj-*` skills (timereg,
146
+ did, weekly review). Same installer pattern; private repo.
147
+
148
+ Skills wrap `mnem`. `mnem` does not call skills. One direction, no
149
+ circular dependencies.
150
+
151
+ ## License
152
+
153
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,109 @@
1
+ # mnem
2
+
3
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
4
+ ![status](https://img.shields.io/badge/status-pre--release-orange)
5
+
6
+ **A local-first memory suite for AI agents.** One install gets you a
7
+ two-tier memory store, an M365 read/write surface, and a single CLI
8
+ that ties them together. Your data stays on your machine.
9
+
10
+ `mnem` is the umbrella over four independent tools that already work
11
+ on their own. The umbrella adds one verb surface, one install command,
12
+ one place to find what is in the box.
13
+
14
+ ```
15
+ mnem (meta-CLI + suite hub)
16
+ |
17
+ +------------------------+------------------------+
18
+ | | | |
19
+ YAAMS cognitive-ledger owa-piggy owa-tools
20
+ (Tier 1 raw) (Tier 2 curated) (M365 auth) (M365 read/write)
21
+ ```
22
+
23
+ ## What's in the box
24
+
25
+ | Tool | Purpose | Binaries |
26
+ | --- | --- | --- |
27
+ | [**YAAMS**](https://github.com/damsleth/yaams) | Tier 1 raw memory store - every iMessage, mail, calendar event, GitHub issue, ingested and queryable from a single SQLite file. | `yaams` |
28
+ | [**cognitive-ledger**](https://github.com/damsleth/cognitive-ledger) | Tier 2 curated atomic notes engine - the gems you promote out of YAAMS and keep forever as markdown with frontmatter. | `ledger`, `ledger-obsidian`, `sheep` |
29
+ | [**owa-piggy**](https://github.com/damsleth/owa-piggy) | Microsoft 365 auth broker - turns your existing Outlook Web session into a reusable token. No app registration. | `owa-piggy` |
30
+ | [**owa-tools**](https://github.com/damsleth/owa-tools) | M365 read/write CLI suite - calendar, mail, Graph, OneDrive, scheduling, people lookup, all JSON-by-default. | `owa`, `owa-cal`, `owa-mail`, `owa-graph`, `owa-doctor`, `owa-people`, `owa-sched`, `owa-drive` |
31
+
32
+ `mnem` itself adds one more binary that routes the verbs above into
33
+ a single user-facing surface.
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ brew install damsleth/tap/mnem
39
+ ```
40
+
41
+ The Homebrew formula pulls the whole suite via dependencies. On
42
+ PyPI the package is `mnem-suite` (both `mnem` and `mnem-cli` were
43
+ already taken on PyPI by unrelated projects); the installed binary
44
+ is still `mnem`:
45
+
46
+ ```bash
47
+ pipx install mnem-suite
48
+ ```
49
+
50
+ Then:
51
+
52
+ ```bash
53
+ mnem init # detect sources, write config, run a dry-run
54
+ mnem hello # one-screen tour of the verbs
55
+ mnem doctor # health check across every tool
56
+ ```
57
+
58
+ `mnem init` is idempotent and never edits your dotfiles.
59
+
60
+ ## What can it do
61
+
62
+ ```bash
63
+ mnem query "what did we decide at the brand kickoff?"
64
+ mnem ingest # all configured sources, partial-success tolerant
65
+ mnem promote review # interactive: promote YAAMS gems to the ledger
66
+ mnem mail send --to ... # owa-mail wrapper
67
+ mnem calendar today # owa-cal wrapper
68
+ mnem ledger init # bootstrap a new ledger
69
+ mnem auth status # owa-piggy wrapper
70
+ mnem doctor # aggregate health check
71
+ mnem version # own version + observed component versions
72
+ ```
73
+
74
+ Every JSON-capable command accepts `--json` (machine mode) and
75
+ `--pretty` (human rendering). Exit codes are predictable per
76
+ [CONVENTIONS.md](CONVENTIONS.md): 0 ok, 1 user error, 2 transient,
77
+ 3 auth, 4 not found, 5 partial success.
78
+
79
+ ## First day
80
+
81
+ 1. `brew install damsleth/tap/mnem`
82
+ 2. `mnem init` - the wizard probes for iMessage, Apple Mail, Signal,
83
+ GitHub, owa-piggy, Obsidian, and an existing cognitive-ledger. It
84
+ enables what it finds and writes `enabled: false` with a hint for
85
+ what it doesn't.
86
+ 3. `mnem ingest` - first run downloads embedding models (~2 GB) with
87
+ a prompt before any download.
88
+ 4. `mnem query "..."` - ask the suite anything.
89
+
90
+ See [SUITE.md](SUITE.md) for the full data flow and architecture, and
91
+ [CONVENTIONS.md](CONVENTIONS.md) for the CLI contract every tool in
92
+ the suite conforms to.
93
+
94
+ ## Skills
95
+
96
+ Two agent skill repos sit on top of `mnem`:
97
+
98
+ - [`damsleth/SKILLS`](https://github.com/damsleth/SKILLS) - public,
99
+ reusable agent skills. Includes `/memory`, which routes through
100
+ `mnem`.
101
+ - `damsleth/SKILLS-private` - personal-infra `cj-*` skills (timereg,
102
+ did, weekly review). Same installer pattern; private repo.
103
+
104
+ Skills wrap `mnem`. `mnem` does not call skills. One direction, no
105
+ circular dependencies.
106
+
107
+ ## License
108
+
109
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,43 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "mnem-suite"
7
+ version = "0.1.0"
8
+ description = "Meta-CLI and suite hub for the mnem memory suite (yaams, cognitive-ledger, owa-piggy, owa-tools)"
9
+ readme = "README.md"
10
+ license = { file = "LICENSE" }
11
+ requires-python = ">=3.11"
12
+ authors = [{ name = "Carl Joakim Damsleth" }]
13
+ keywords = ["mnem", "memory", "cli", "agent"]
14
+ classifiers = [
15
+ "License :: OSI Approved :: MIT License",
16
+ "Operating System :: MacOS",
17
+ "Operating System :: POSIX :: Linux",
18
+ "Programming Language :: Python :: 3",
19
+ "Environment :: Console",
20
+ "Development Status :: 4 - Beta",
21
+ ]
22
+ dependencies = [
23
+ "click>=8.1",
24
+ ]
25
+
26
+ [project.urls]
27
+ Homepage = "https://github.com/damsleth/mnem"
28
+ Source = "https://github.com/damsleth/mnem"
29
+
30
+ [project.scripts]
31
+ mnem = "mnem.cli:main"
32
+
33
+ [project.optional-dependencies]
34
+ dev = [
35
+ "pytest>=7",
36
+ ]
37
+
38
+ [tool.pytest.ini_options]
39
+ testpaths = ["tests"]
40
+
41
+ [tool.setuptools.packages.find]
42
+ where = ["src"]
43
+ include = ["mnem*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """mnem - meta-CLI for the mnem memory suite."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,4 @@
1
+ from mnem.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ raise SystemExit(main())
@@ -0,0 +1,114 @@
1
+ """mnem Click root.
2
+
3
+ Subcommands:
4
+ - hello - one-screen elevator pitch
5
+ - version - mnem version + observed component versions
6
+ - doctor - aggregate health check across the suite
7
+ - query - passthrough to `yaams query` (with --tier aliasing)
8
+ - ingest - passthrough to `yaams ingest`
9
+ - promote, ledger, mail, calendar, auth: Phase 3b
10
+
11
+ Top-level flags: --version (Click default), --doctor, --json,
12
+ --verbose. The doctor flag is wired so `mnem --doctor` works for
13
+ parity with other binaries in the suite.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import sys
19
+
20
+ import click
21
+
22
+ from mnem import __version__
23
+
24
+
25
+ @click.group(
26
+ invoke_without_command=True,
27
+ context_settings={"help_option_names": ["-h", "--help"]},
28
+ )
29
+ @click.version_option(__version__, prog_name="mnem")
30
+ @click.option(
31
+ "--doctor",
32
+ is_flag=True,
33
+ default=False,
34
+ help="Run health check across the suite and exit.",
35
+ )
36
+ @click.option(
37
+ "--json",
38
+ "as_json_top",
39
+ is_flag=True,
40
+ default=False,
41
+ help="Machine mode (JSON output) for top-level commands.",
42
+ )
43
+ @click.option(
44
+ "-v",
45
+ "--verbose",
46
+ is_flag=True,
47
+ default=False,
48
+ help="Verbose mode: dump captured stderr from subprocess failures.",
49
+ )
50
+ @click.pass_context
51
+ def cli(ctx: click.Context, doctor: bool, as_json_top: bool, verbose: bool) -> None:
52
+ ctx.ensure_object(dict)
53
+ ctx.obj["json"] = as_json_top
54
+ ctx.obj["verbose"] = verbose
55
+ if doctor:
56
+ from mnem.commands.doctor import run as doctor_run
57
+ ctx.exit(doctor_run(as_json_top))
58
+ if ctx.invoked_subcommand is None:
59
+ from mnem.commands.hello import run as hello_run
60
+ ctx.exit(hello_run(as_json_top))
61
+
62
+
63
+ @cli.command("hello")
64
+ @click.option("--json", "as_json", is_flag=True, default=False)
65
+ @click.pass_context
66
+ def hello_cmd(ctx: click.Context, as_json: bool) -> None:
67
+ from mnem.commands.hello import run
68
+ ctx.exit(run(as_json or ctx.obj.get("json", False)))
69
+
70
+
71
+ @cli.command("version")
72
+ @click.option("--json", "as_json", is_flag=True, default=False)
73
+ @click.pass_context
74
+ def version_cmd(ctx: click.Context, as_json: bool) -> None:
75
+ from mnem.commands.version import run
76
+ ctx.exit(run(as_json or ctx.obj.get("json", False)))
77
+
78
+
79
+ @cli.command("doctor")
80
+ @click.option("--json", "as_json", is_flag=True, default=False)
81
+ @click.pass_context
82
+ def doctor_cmd(ctx: click.Context, as_json: bool) -> None:
83
+ from mnem.commands.doctor import run
84
+ ctx.exit(run(as_json or ctx.obj.get("json", False)))
85
+
86
+
87
+ @cli.command(
88
+ "query",
89
+ context_settings={"ignore_unknown_options": True, "allow_extra_args": True},
90
+ )
91
+ @click.argument("args", nargs=-1, type=click.UNPROCESSED)
92
+ @click.pass_context
93
+ def query_cmd(ctx: click.Context, args: tuple[str, ...]) -> None:
94
+ from mnem.commands.passthrough import run
95
+ ctx.exit(run(["query", *args], verbose=ctx.obj.get("verbose", False)))
96
+
97
+
98
+ @cli.command(
99
+ "ingest",
100
+ context_settings={"ignore_unknown_options": True, "allow_extra_args": True},
101
+ )
102
+ @click.argument("args", nargs=-1, type=click.UNPROCESSED)
103
+ @click.pass_context
104
+ def ingest_cmd(ctx: click.Context, args: tuple[str, ...]) -> None:
105
+ from mnem.commands.passthrough import run
106
+ ctx.exit(run(["ingest", *args], verbose=ctx.obj.get("verbose", False)))
107
+
108
+
109
+ def main() -> int:
110
+ return cli(standalone_mode=False) or 0
111
+
112
+
113
+ if __name__ == "__main__":
114
+ sys.exit(main())
@@ -0,0 +1 @@
1
+ """mnem subcommand implementations."""
@@ -0,0 +1,122 @@
1
+ """``mnem doctor`` - aggregate health check across the suite.
2
+
3
+ Output class: data. Fans out to each `<tool> --doctor --json` and
4
+ collects findings into one document.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import sys
11
+ from typing import TextIO
12
+
13
+ from mnem import __version__
14
+ from mnem.failure import run_subprocess
15
+
16
+
17
+ # Order is the report order; mnem first, then tiers, then M365.
18
+ _FANOUT = [
19
+ "yaams",
20
+ "ledger",
21
+ "sheep",
22
+ "ledger-obsidian",
23
+ "owa-piggy",
24
+ "owa-cal",
25
+ "owa-mail",
26
+ "owa-graph",
27
+ "owa-people",
28
+ "owa-sched",
29
+ "owa-drive",
30
+ "owa",
31
+ ]
32
+
33
+
34
+ def _probe(binary: str) -> dict:
35
+ """Probe `<binary> --doctor --json` and return the parsed payload.
36
+
37
+ On crash or non-JSON output, synthesises a stub payload that
38
+ preserves the doctor-schema invariants (tool name, findings list).
39
+ """
40
+ result = run_subprocess([binary, "--doctor"], tool=binary, inject_json=True)
41
+ if result.crashed:
42
+ return {
43
+ "tool": binary,
44
+ "version": None,
45
+ "installed": False,
46
+ "findings": [
47
+ {
48
+ "id": "binary_missing",
49
+ "severity": "error",
50
+ "message": "binary not on PATH or crashed before emitting JSON",
51
+ "hint": f"brew install damsleth/tap/{binary} (or check PATH)",
52
+ }
53
+ ],
54
+ }
55
+ env = result.stdout_envelope or {}
56
+ env["installed"] = True
57
+ # Each binary's doctor exit code influenced findings already.
58
+ # Track the raw exit_code for the aggregator's severity rollup.
59
+ env["exit_code"] = result.returncode
60
+ return env
61
+
62
+
63
+ def _aggregate() -> dict:
64
+ components = []
65
+ worst_exit = 0
66
+ for binary in _FANOUT:
67
+ payload = _probe(binary)
68
+ components.append(payload)
69
+ # Aggregate severity from two sources:
70
+ # (1) the subprocess returncode (clamped to the standard set), and
71
+ # (2) the findings list - an error-severity finding always bumps
72
+ # exit to at least 1, even if the binary itself returned 0.
73
+ sub_exit = int(payload.get("exit_code") or 0)
74
+ if not payload.get("installed", False):
75
+ # Missing binary -> user-fixable (install or PATH); not the raw
76
+ # FileNotFoundError exit (127).
77
+ sub_exit = 1
78
+ severities = {f.get("severity") for f in (payload.get("findings") or [])}
79
+ if "error" in severities:
80
+ sub_exit = max(sub_exit, 1)
81
+ worst_exit = max(worst_exit, sub_exit)
82
+ return {
83
+ "tool": "mnem",
84
+ "version": __version__,
85
+ "components": components,
86
+ "_exit_code": worst_exit,
87
+ }
88
+
89
+
90
+ def run(as_json: bool, stream: TextIO | None = None) -> int:
91
+ if stream is None:
92
+ stream = sys.stdout
93
+ doc = _aggregate()
94
+ exit_code = int(doc.pop("_exit_code", 0))
95
+ if as_json:
96
+ stream.write(json.dumps(doc, ensure_ascii=False) + "\n")
97
+ stream.flush()
98
+ return exit_code
99
+
100
+ stream.write(f"mnem doctor (v{doc['version']})\n")
101
+ for comp in doc["components"]:
102
+ name = comp["tool"]
103
+ if not comp.get("installed"):
104
+ stream.write(f" {name:<18} - not installed\n")
105
+ continue
106
+ findings = comp.get("findings") or []
107
+ if not findings:
108
+ stream.write(f" {name:<18} ok\n")
109
+ continue
110
+ severities = {f["severity"] for f in findings}
111
+ if "error" in severities:
112
+ mark = "x"
113
+ elif "warning" in severities:
114
+ mark = "!"
115
+ else:
116
+ mark = "."
117
+ stream.write(f" {name:<18} {mark} {len(findings)} finding(s)\n")
118
+ for f in findings:
119
+ hint = f" hint: {f['hint']}" if f.get("hint") else ""
120
+ stream.write(f" - [{f['severity']}] {f['id']}: {f['message']}{hint}\n")
121
+ stream.flush()
122
+ return exit_code
@@ -0,0 +1,67 @@
1
+ """``mnem hello`` - the one-screen elevator pitch.
2
+
3
+ Output class: data. Runs without any config; safe on a fresh
4
+ install before anything is wired up. Emits JSON on stdout under
5
+ --json, a human banner otherwise.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import sys
12
+ from typing import TextIO
13
+
14
+ from mnem import __version__
15
+ from mnem.router import verbs
16
+
17
+
18
+ # Static verbs that ship in 3a beyond the translation table.
19
+ _BUILTIN_VERBS = [
20
+ ("hello", "mnem", "Show this elevator pitch"),
21
+ ("version", "mnem", "Show mnem version and observed component versions"),
22
+ ("doctor", "mnem", "Run health checks across the whole suite"),
23
+ ]
24
+
25
+
26
+ def _all_verbs() -> list[tuple[str, str, str]]:
27
+ return _BUILTIN_VERBS + verbs()
28
+
29
+
30
+ def _data_doc() -> dict:
31
+ return {
32
+ "tool": "mnem",
33
+ "version": __version__,
34
+ "tagline": "Local-first memory suite for AI agents.",
35
+ "verbs": [
36
+ {"verb": verb, "binary": binary, "description": desc}
37
+ for (verb, binary, desc) in _all_verbs()
38
+ ],
39
+ "next_steps": [
40
+ "mnem doctor",
41
+ "mnem query \"<question>\"",
42
+ "mnem ingest",
43
+ ],
44
+ }
45
+
46
+
47
+ def run(as_json: bool, stream: TextIO | None = None) -> int:
48
+ if stream is None:
49
+ stream = sys.stdout
50
+ if as_json:
51
+ stream.write(json.dumps(_data_doc(), ensure_ascii=False) + "\n")
52
+ stream.flush()
53
+ return 0
54
+
55
+ doc = _data_doc()
56
+ stream.write(f"mnem v{doc['version']} - {doc['tagline']}\n\n")
57
+ stream.write("Verbs:\n")
58
+ width = max(len(v) for (v, _, _) in doc["verbs"] if isinstance(v, str)) if doc["verbs"] else 0
59
+ for entry in doc["verbs"]:
60
+ verb = entry["verb"]
61
+ desc = entry["description"]
62
+ stream.write(f" {verb:<{width}} {desc}\n")
63
+ stream.write("\nNext steps:\n")
64
+ for cmd in doc["next_steps"]:
65
+ stream.write(f" $ {cmd}\n")
66
+ stream.flush()
67
+ return 0