basemind-hermes-plugin 0.21.1__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.
- basemind_hermes_plugin-0.21.1/PKG-INFO +85 -0
- basemind_hermes_plugin-0.21.1/README.md +60 -0
- basemind_hermes_plugin-0.21.1/basemind_hermes_plugin/__init__.py +8 -0
- basemind_hermes_plugin-0.21.1/basemind_hermes_plugin/commands/bm-doctor.md +34 -0
- basemind_hermes_plugin-0.21.1/basemind_hermes_plugin/commands/bm-init.md +57 -0
- basemind_hermes_plugin-0.21.1/basemind_hermes_plugin/commands/bm-scan.md +38 -0
- basemind_hermes_plugin-0.21.1/basemind_hermes_plugin/commands/bm-stats.md +43 -0
- basemind_hermes_plugin-0.21.1/basemind_hermes_plugin/commands/bm-statusline.md +57 -0
- basemind_hermes_plugin-0.21.1/basemind_hermes_plugin/commands/bm.md +47 -0
- basemind_hermes_plugin-0.21.1/basemind_hermes_plugin/hermes.py +246 -0
- basemind_hermes_plugin-0.21.1/basemind_hermes_plugin/plugin.yaml +6 -0
- basemind_hermes_plugin-0.21.1/basemind_hermes_plugin/skills/basemind/SKILL.md +188 -0
- basemind_hermes_plugin-0.21.1/basemind_hermes_plugin/skills/basemind-cli/SKILL.md +162 -0
- basemind_hermes_plugin-0.21.1/basemind_hermes_plugin/skills/basemind-code-search/SKILL.md +77 -0
- basemind_hermes_plugin-0.21.1/basemind_hermes_plugin/skills/basemind-comms/SKILL.md +99 -0
- basemind_hermes_plugin-0.21.1/basemind_hermes_plugin/skills/basemind-doctor/SKILL.md +87 -0
- basemind_hermes_plugin-0.21.1/basemind_hermes_plugin/skills/basemind-documents/SKILL.md +79 -0
- basemind_hermes_plugin-0.21.1/basemind_hermes_plugin/skills/basemind-git-history/SKILL.md +62 -0
- basemind_hermes_plugin-0.21.1/basemind_hermes_plugin/skills/basemind-scan/SKILL.md +58 -0
- basemind_hermes_plugin-0.21.1/basemind_hermes_plugin/skills/basemind-stats/SKILL.md +56 -0
- basemind_hermes_plugin-0.21.1/basemind_hermes_plugin.egg-info/PKG-INFO +85 -0
- basemind_hermes_plugin-0.21.1/basemind_hermes_plugin.egg-info/SOURCES.txt +26 -0
- basemind_hermes_plugin-0.21.1/basemind_hermes_plugin.egg-info/dependency_links.txt +1 -0
- basemind_hermes_plugin-0.21.1/basemind_hermes_plugin.egg-info/entry_points.txt +2 -0
- basemind_hermes_plugin-0.21.1/basemind_hermes_plugin.egg-info/top_level.txt +1 -0
- basemind_hermes_plugin-0.21.1/pyproject.toml +42 -0
- basemind_hermes_plugin-0.21.1/setup.cfg +4 -0
- basemind_hermes_plugin-0.21.1/tests/test_hermes_register.py +155 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: basemind-hermes-plugin
|
|
3
|
+
Version: 0.21.1
|
|
4
|
+
Summary: Hermes Agent plugin for basemind — helper skills, slash commands, and agent-comms notifications over the basemind MCP server.
|
|
5
|
+
Author-email: Na'aman Hirschfeld <nhirschfeld@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://basemind.ai
|
|
8
|
+
Project-URL: Documentation, https://basemind.ai
|
|
9
|
+
Project-URL: Repository, https://github.com/Goldziher/basemind.git
|
|
10
|
+
Project-URL: Issues, https://github.com/Goldziher/basemind/issues
|
|
11
|
+
Keywords: mcp,hermes,agent-context,code-map,plugin
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Environment :: Console
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# basemind-hermes-plugin
|
|
27
|
+
|
|
28
|
+
The [Hermes Agent](https://github.com/NousResearch/hermes) plugin for
|
|
29
|
+
[basemind](https://github.com/Goldziher/basemind). It adds what MCP config alone cannot: basemind's
|
|
30
|
+
helper **skills**, **slash commands**, and agent-comms **notifications** (session-start context +
|
|
31
|
+
per-turn inbox deltas).
|
|
32
|
+
|
|
33
|
+
This package is pure-Python and stdlib-only. It does **not** install the `basemind` binary — install
|
|
34
|
+
that separately (see below). The plugin reaches basemind by shelling out to `basemind` on your
|
|
35
|
+
`PATH`, and every hook is fail-open: with no binary or a down comms broker it degrades to a no-op.
|
|
36
|
+
|
|
37
|
+
## Prerequisites
|
|
38
|
+
|
|
39
|
+
1. **The `basemind` binary on your `PATH`** — via any channel:
|
|
40
|
+
|
|
41
|
+
| Channel | Command |
|
|
42
|
+
|---|---|
|
|
43
|
+
| Homebrew | `brew install Goldziher/tap/basemind` |
|
|
44
|
+
| npm | `npm install -g basemind` |
|
|
45
|
+
| cargo | `cargo install basemind --features full --locked` |
|
|
46
|
+
| GitHub releases | [download a binary](https://github.com/Goldziher/basemind/releases) |
|
|
47
|
+
|
|
48
|
+
2. **The basemind MCP server wired into Hermes** — this is what gives Hermes the 60+ tools. Add to
|
|
49
|
+
`~/.hermes/config.yaml`:
|
|
50
|
+
|
|
51
|
+
```yaml
|
|
52
|
+
mcp_servers:
|
|
53
|
+
basemind:
|
|
54
|
+
command: basemind
|
|
55
|
+
args: [serve]
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Install the plugin
|
|
59
|
+
|
|
60
|
+
Install into the **same Python environment Hermes runs in** (Hermes discovers plugins through the
|
|
61
|
+
`hermes_agent.plugins` entry point):
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install basemind-hermes-plugin
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Then enable it (general plugins are opt-in):
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
hermes plugins enable basemind
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Restart your Hermes session so it re-reads the config and loads the plugin.
|
|
74
|
+
|
|
75
|
+
## What it registers
|
|
76
|
+
|
|
77
|
+
- **Skills** — `basemind`, `basemind-code-search`, `basemind-git-history`, `basemind-documents`,
|
|
78
|
+
`basemind-comms`, `basemind-cli`, `basemind-doctor`, `basemind-scan`, `basemind-stats`.
|
|
79
|
+
- **Slash commands** — `bm`, `bm-init`, `bm-scan`, `bm-doctor`, `bm-stats`, `bm-statusline`.
|
|
80
|
+
- **Hooks** — `on_session_start` (operating discipline + condensed comms inbox) and `pre_llm_call`
|
|
81
|
+
(per-turn agent-comms deltas). Best-effort; the MCP tools work regardless.
|
|
82
|
+
|
|
83
|
+
## License
|
|
84
|
+
|
|
85
|
+
MIT
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# basemind-hermes-plugin
|
|
2
|
+
|
|
3
|
+
The [Hermes Agent](https://github.com/NousResearch/hermes) plugin for
|
|
4
|
+
[basemind](https://github.com/Goldziher/basemind). It adds what MCP config alone cannot: basemind's
|
|
5
|
+
helper **skills**, **slash commands**, and agent-comms **notifications** (session-start context +
|
|
6
|
+
per-turn inbox deltas).
|
|
7
|
+
|
|
8
|
+
This package is pure-Python and stdlib-only. It does **not** install the `basemind` binary — install
|
|
9
|
+
that separately (see below). The plugin reaches basemind by shelling out to `basemind` on your
|
|
10
|
+
`PATH`, and every hook is fail-open: with no binary or a down comms broker it degrades to a no-op.
|
|
11
|
+
|
|
12
|
+
## Prerequisites
|
|
13
|
+
|
|
14
|
+
1. **The `basemind` binary on your `PATH`** — via any channel:
|
|
15
|
+
|
|
16
|
+
| Channel | Command |
|
|
17
|
+
|---|---|
|
|
18
|
+
| Homebrew | `brew install Goldziher/tap/basemind` |
|
|
19
|
+
| npm | `npm install -g basemind` |
|
|
20
|
+
| cargo | `cargo install basemind --features full --locked` |
|
|
21
|
+
| GitHub releases | [download a binary](https://github.com/Goldziher/basemind/releases) |
|
|
22
|
+
|
|
23
|
+
2. **The basemind MCP server wired into Hermes** — this is what gives Hermes the 60+ tools. Add to
|
|
24
|
+
`~/.hermes/config.yaml`:
|
|
25
|
+
|
|
26
|
+
```yaml
|
|
27
|
+
mcp_servers:
|
|
28
|
+
basemind:
|
|
29
|
+
command: basemind
|
|
30
|
+
args: [serve]
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Install the plugin
|
|
34
|
+
|
|
35
|
+
Install into the **same Python environment Hermes runs in** (Hermes discovers plugins through the
|
|
36
|
+
`hermes_agent.plugins` entry point):
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install basemind-hermes-plugin
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Then enable it (general plugins are opt-in):
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
hermes plugins enable basemind
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Restart your Hermes session so it re-reads the config and loads the plugin.
|
|
49
|
+
|
|
50
|
+
## What it registers
|
|
51
|
+
|
|
52
|
+
- **Skills** — `basemind`, `basemind-code-search`, `basemind-git-history`, `basemind-documents`,
|
|
53
|
+
`basemind-comms`, `basemind-cli`, `basemind-doctor`, `basemind-scan`, `basemind-stats`.
|
|
54
|
+
- **Slash commands** — `bm`, `bm-init`, `bm-scan`, `bm-doctor`, `bm-stats`, `bm-statusline`.
|
|
55
|
+
- **Hooks** — `on_session_start` (operating discipline + condensed comms inbox) and `pre_llm_call`
|
|
56
|
+
(per-turn agent-comms deltas). Best-effort; the MCP tools work regardless.
|
|
57
|
+
|
|
58
|
+
## License
|
|
59
|
+
|
|
60
|
+
MIT
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bm-doctor
|
|
3
|
+
description: Diagnose and recover basemind when it isn't working (MCP tools missing/erroring, "no index", dead server) — runs CLI checks and gives the client-specific way to reconnect the server.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# bm-doctor — diagnose and recover basemind
|
|
7
|
+
|
|
8
|
+
Diagnose and recover basemind using the CLI (works even with no MCP server running).
|
|
9
|
+
|
|
10
|
+
## When to use
|
|
11
|
+
|
|
12
|
+
basemind isn't behaving: MCP tools are missing or erroring, the statusline or a tool reports
|
|
13
|
+
"no index" / "no indexed files", results are empty when they shouldn't be, or the `basemind
|
|
14
|
+
serve` MCP server seems dead.
|
|
15
|
+
|
|
16
|
+
## How to use
|
|
17
|
+
|
|
18
|
+
Invoke `/bm-doctor` (optional free-text detail, e.g. `/bm-doctor tools return no indexed files`).
|
|
19
|
+
It runs the checks below in order:
|
|
20
|
+
|
|
21
|
+
1. Check the index: `basemind query status`.
|
|
22
|
+
2. Check for a lock-holding server: `cat .basemind/.lock.meta`.
|
|
23
|
+
3. Rebuild the index if needed: `basemind scan`.
|
|
24
|
+
4. Reconnect the MCP server (client-specific — this is the only way to restart it).
|
|
25
|
+
|
|
26
|
+
## Notes
|
|
27
|
+
|
|
28
|
+
- A stdio MCP server can't be restarted by an agent or by basemind itself; reconnecting it is
|
|
29
|
+
the MCP client's job. The CLI stays usable throughout.
|
|
30
|
+
|
|
31
|
+
## See also
|
|
32
|
+
|
|
33
|
+
The `basemind-doctor` skill for the full step-by-step diagnostic workflow, lock-holder detection,
|
|
34
|
+
and log-reading guidance.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bm-init
|
|
3
|
+
description: Onboard (or refresh) basemind in this repo — write basemind.toml, gitignore the cache, and inject a "prefer basemind over grep/read/git" rules block into CLAUDE.md / AGENTS.md / ai-rulez.
|
|
4
|
+
argument-hint: [capabilities…]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# bm-init — onboard basemind into this repo
|
|
8
|
+
|
|
9
|
+
Run `basemind init` so the repo has a committed `basemind.toml`, a gitignored `.basemind/` cache,
|
|
10
|
+
and a rules block that tells every agent to prefer basemind's MCP tools over grep, file reads, and
|
|
11
|
+
naked `git`. CLI and slash command share ONE implementation — this just drives `basemind init`
|
|
12
|
+
with the right non-interactive flags.
|
|
13
|
+
|
|
14
|
+
## When to use
|
|
15
|
+
|
|
16
|
+
First time setting up basemind in a repo, or to refresh the rules block after enabling new
|
|
17
|
+
capabilities (documents/RAG, agent-comms, semantic search). Safe to re-run: it's idempotent.
|
|
18
|
+
|
|
19
|
+
## How to use
|
|
20
|
+
|
|
21
|
+
1. **Ask which capabilities matter** (one short question). The options are:
|
|
22
|
+
`code-search-navigation`, `code-mapping-architecture`, `git-history`, `agent-comms`,
|
|
23
|
+
`documents-rag`, `semantic-search`. If the user has no preference, enable all.
|
|
24
|
+
|
|
25
|
+
2. **Run `basemind init` non-interactively** with the matching flags. Enable everything:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
basemind init --yes
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Narrow to a subset with repeatable `--with` (allow-list) or `--without` (subtract):
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
basemind init --yes --with code-search-navigation --with git-history
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Steer where the rules land with `--rules-target <auto|claude|agents|ai-rulez|none>` (default
|
|
38
|
+
`auto`). Preview without writing using `--print`.
|
|
39
|
+
|
|
40
|
+
3. **Report what changed** — which files were written or kept (`basemind.toml`, `.gitignore`, the
|
|
41
|
+
rules file), and whether the delimited block was created or updated in place.
|
|
42
|
+
|
|
43
|
+
## Notes
|
|
44
|
+
|
|
45
|
+
- Source-of-truth detection (auto): `.ai-rulez/config.toml` present → writes
|
|
46
|
+
`.ai-rulez/rules/basemind-usage.md` (then tell the user to run `ai-rulez generate`; do NOT run
|
|
47
|
+
it for them). Else CLAUDE.md → AGENTS.md → create CLAUDE.md, wrapping the content in an
|
|
48
|
+
idempotent `<!-- BEGIN basemind … -->` … `<!-- END basemind -->` block that is replaced in
|
|
49
|
+
place on re-run, never duplicated. Content outside the markers is never touched.
|
|
50
|
+
- An existing `basemind.toml` is kept verbatim, never clobbered.
|
|
51
|
+
- If `basemind` isn't on `PATH`: use the plugin-managed cache binary or build a dev binary with
|
|
52
|
+
`cargo build --release` and use `./target/release/basemind`.
|
|
53
|
+
|
|
54
|
+
## See also
|
|
55
|
+
|
|
56
|
+
The `bm-scan` command to build the index next, and the `basemind` skill for the full MCP tool
|
|
57
|
+
surface the rules block advertises.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bm-scan
|
|
3
|
+
description: Build or refresh the basemind index by running `basemind scan` via the CLI — works without the MCP server (use it when basemind reports "no index" / "no indexed files").
|
|
4
|
+
argument-hint: [path]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# bm-scan — build or refresh the basemind index
|
|
8
|
+
|
|
9
|
+
Run `basemind scan` via the CLI so the code map exists and is current.
|
|
10
|
+
|
|
11
|
+
## When to use
|
|
12
|
+
|
|
13
|
+
basemind (or its statusline) reports "no index" / "no indexed files", an MCP tool returns empty
|
|
14
|
+
results that shouldn't be empty, or the index is stale after large changes.
|
|
15
|
+
|
|
16
|
+
## How to use
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
basemind scan ${ARGUMENTS:-}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
- No argument → full working-tree scan.
|
|
23
|
+
- A path argument (`/bm-scan src/mcp`) → scope the scan to that path (incremental).
|
|
24
|
+
- If `basemind` isn't on `PATH`: use the plugin-managed cache
|
|
25
|
+
(`${XDG_CACHE_HOME:-~/.cache}/basemind/bin/<version>/basemind`), or build a dev binary with
|
|
26
|
+
`cargo build --release` and use `./target/release/basemind`.
|
|
27
|
+
|
|
28
|
+
## Notes
|
|
29
|
+
|
|
30
|
+
- Report files scanned / updated / skipped and elapsed time. Non-extractable files are
|
|
31
|
+
**skipped**, not failures.
|
|
32
|
+
- If a `basemind serve` MCP server already holds the store lock for this repo, `scan` errors on
|
|
33
|
+
the lock — use the `rescan` MCP tool instead, or stop the server first.
|
|
34
|
+
|
|
35
|
+
## See also
|
|
36
|
+
|
|
37
|
+
The `basemind-scan` skill for the full workflow, binary-resolution order, and `extra_roots`
|
|
38
|
+
config for indexing directories outside the repo.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bm-stats
|
|
3
|
+
description: Show the basemind dashboard — resource footprint (disk + RAM) and activity (tool calls, per-tool histogram, estimated tokens saved). Works with or without the MCP server.
|
|
4
|
+
argument-hint: [today|1h|24h|all]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# bm-stats — basemind dashboard
|
|
8
|
+
|
|
9
|
+
Show a basemind dashboard with two sections: resource footprint (on-disk size + process RAM) and
|
|
10
|
+
activity (tool calls, per-tool histogram, estimated tokens saved).
|
|
11
|
+
|
|
12
|
+
## When to use
|
|
13
|
+
|
|
14
|
+
The user asks "how much is basemind helping?", "show me basemind stats", or wants to check disk /
|
|
15
|
+
RAM usage.
|
|
16
|
+
|
|
17
|
+
## How to use
|
|
18
|
+
|
|
19
|
+
Invoke `/bm-stats` (default window `today`) or `/bm-stats <today|1h|24h|all>`. Window: $ARGUMENTS
|
|
20
|
+
|
|
21
|
+
1. **Resource footprint.** MCP tool `cache_stats`, or CLI `basemind cache stats` (add `--json` to
|
|
22
|
+
parse). Report: `total_bytes` (matches `du`), the per-component breakdown (blobs / views /
|
|
23
|
+
git-history / lance / git-cache / telemetry / other), and process RAM (`rss_bytes` +
|
|
24
|
+
`peak_rss_bytes`). If `blob_accounting_ok` is `false`, note that orphan accounting was skipped
|
|
25
|
+
(stale/unreadable index — re-scan to restore it); the sizes are still accurate.
|
|
26
|
+
|
|
27
|
+
2. **Activity.** MCP tool `telemetry_summary`, or CLI `basemind telemetry --window <today|1h|24h|all>`
|
|
28
|
+
(add `--json`). Report call count, the per-tool histogram, and estimated tokens saved for the
|
|
29
|
+
window.
|
|
30
|
+
|
|
31
|
+
3. **Render** both sections as a compact markdown dashboard.
|
|
32
|
+
|
|
33
|
+
## Notes
|
|
34
|
+
|
|
35
|
+
- Prefer the MCP tools when connected, but don't depend on them — the CLI reads the same data
|
|
36
|
+
with no server. If a step's MCP tool isn't available, run its CLI equivalent instead of giving up.
|
|
37
|
+
- If neither MCP nor CLI is reachable, say so plainly and point at `/bm-doctor`.
|
|
38
|
+
- Always end with a one-sentence disclosure that the savings number is heuristic: tools without a
|
|
39
|
+
realistic baseline (memory, document search, git wrappers) report 0 saved.
|
|
40
|
+
|
|
41
|
+
## See also
|
|
42
|
+
|
|
43
|
+
The `basemind-stats` skill for the render shape and the `--explain` breakdown.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bm-statusline
|
|
3
|
+
description: Enable the basemind status line in your Claude Code user settings (one-time setup).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# bm-statusline — enable the basemind status line
|
|
7
|
+
|
|
8
|
+
Wire the basemind status line into the user's global Claude Code settings, then confirm it
|
|
9
|
+
renders.
|
|
10
|
+
|
|
11
|
+
## When to use
|
|
12
|
+
|
|
13
|
+
Run this once per machine to enable the basemind status line in Claude Code. Re-run only if the
|
|
14
|
+
bar goes blank after an unusual settings edit.
|
|
15
|
+
|
|
16
|
+
## How to use
|
|
17
|
+
|
|
18
|
+
Invoke `/bm-statusline`. Use your tools to do this directly — do not ask the user to hand-edit
|
|
19
|
+
any files.
|
|
20
|
+
|
|
21
|
+
1. **Confirm the plugin is installed.** Check that at least one of these exists:
|
|
22
|
+
- `${CLAUDE_PLUGIN_ROOT}/.claude-plugin/statusline.sh` (if that var is set)
|
|
23
|
+
- `~/.claude/plugins/marketplaces/basemind/.claude-plugin/statusline.sh`
|
|
24
|
+
- a match of
|
|
25
|
+
`~/.claude/plugins/cache/basemind/basemind/*/.claude-plugin/statusline.sh`
|
|
26
|
+
|
|
27
|
+
If none exists, tell the user the basemind plugin isn't installed and stop.
|
|
28
|
+
|
|
29
|
+
2. **Update `~/.claude/settings.json`** (treat a missing file as `{}`). Set its
|
|
30
|
+
`statusLine` key to this **version-independent resolver** — do NOT hardcode a
|
|
31
|
+
version-pinned path, which breaks on the next basemind update when the old
|
|
32
|
+
version dir is pruned:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{ "type": "command",
|
|
36
|
+
"command": "bash -c 's=$(ls -d \"$HOME\"/.claude/plugins/cache/basemind/basemind/*/.claude-plugin/statusline.sh 2>/dev/null | sort -V | tail -1); [ -f \"$s\" ] || s=\"$HOME/.claude/plugins/marketplaces/basemind/.claude-plugin/statusline.sh\"; [ -f \"$s\" ] && exec bash \"$s\" || printf \"%s\" \"◆ basemind: run /bm-statusline\"'",
|
|
37
|
+
"refreshInterval": 5 }
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
How it resolves, in order: the **highest-versioned** cached copy (`sort -V`, not
|
|
41
|
+
mtime — an older dir touched more recently must never win; this also makes the bar
|
|
42
|
+
track the newest version the moment `/plugin update` installs it), else the
|
|
43
|
+
marketplace clone, else a one-line hint so the bar is never blank. It re-resolves
|
|
44
|
+
at every render, so version bumps never break it and script improvements land
|
|
45
|
+
automatically. Copy the `command` string
|
|
46
|
+
**verbatim** (it self-resolves — `$HOME` is expanded by the `bash -c` it runs
|
|
47
|
+
under, not the settings field). Preserve every other key and verify the file is
|
|
48
|
+
still valid JSON afterward.
|
|
49
|
+
|
|
50
|
+
3. **Confirm it renders** by running the resolver once:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
printf '{"workspace":{"current_dir":"%s"}}' "$PWD" | bash -c 's=$(ls -d "$HOME"/.claude/plugins/cache/basemind/basemind/*/.claude-plugin/statusline.sh 2>/dev/null | sort -V | tail -1); [ -f "$s" ] || s="$HOME/.claude/plugins/marketplaces/basemind/.claude-plugin/statusline.sh"; [ -f "$s" ] && exec bash "$s" || printf "%s" "◆ basemind: run /bm-statusline"'
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
4. Tell the user it's enabled, and that any other running sessions need a
|
|
57
|
+
relaunch to pick it up.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bm
|
|
3
|
+
description: Ask basemind anything about the current codebase — outlines, refs, callers, git history, blame, diffs, docs, memory.
|
|
4
|
+
argument-hint: <question about the codebase>
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# bm — ask basemind anything about this codebase
|
|
8
|
+
|
|
9
|
+
Answer the user's question using the basemind MCP server instead of reading files or shelling
|
|
10
|
+
out to grep/git.
|
|
11
|
+
|
|
12
|
+
## When to use
|
|
13
|
+
|
|
14
|
+
Invoke with a natural-language question about this repo's code, history, or documents —
|
|
15
|
+
`/bm <question>`. Use it instead of manually picking a tool when you just want an answer.
|
|
16
|
+
|
|
17
|
+
## How to use
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
/bm where is MapCache defined?
|
|
21
|
+
/bm what calls process_file?
|
|
22
|
+
/bm who last touched src/scanner.rs?
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Route the question to the tool that answers it directly:
|
|
26
|
+
|
|
27
|
+
| Example question | Tool |
|
|
28
|
+
|---|---|
|
|
29
|
+
| "Where is X defined?" | `search_symbols` |
|
|
30
|
+
| "What calls X?" | `find_references` (any name) or `find_callers` (specific def) |
|
|
31
|
+
| "What's the shape of this file?" | `outline` (add `l2: true` for calls + docs) |
|
|
32
|
+
| "What changed recently?" | `recent_changes`, `commits_touching`, `symbol_history` |
|
|
33
|
+
| "Who last touched this?" | `blame_file` / `blame_symbol` |
|
|
34
|
+
| "Where's the churn?" | `hot_files` |
|
|
35
|
+
| "Search PDFs/docs in the repo by meaning?" | `search_documents` |
|
|
36
|
+
| "Recall something remembered earlier?" | `memory_get` / `memory_list` / `memory_search` |
|
|
37
|
+
| "Remember this for later sessions?" | `memory_put` (delete with `memory_delete`) |
|
|
38
|
+
| "Refresh the index after editing code?" | `rescan` (pass `paths: [...]` to limit the scope) |
|
|
39
|
+
|
|
40
|
+
## Notes
|
|
41
|
+
|
|
42
|
+
- Answer with paths, line numbers, and signatures — read whole files only after a tool has
|
|
43
|
+
located the exact span you need.
|
|
44
|
+
|
|
45
|
+
## See also
|
|
46
|
+
|
|
47
|
+
The `basemind` skill for the full tool-routing table and context-economy discipline.
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"""Hermes Agent plugin registration for basemind.
|
|
2
|
+
|
|
3
|
+
basemind's tools reach Hermes through an MCP server declared in ``~/.hermes/config.yaml``
|
|
4
|
+
(``mcp_servers.basemind``) — a Hermes plugin cannot declare an MCP server. This module adds
|
|
5
|
+
what MCP config cannot: the basemind helper *skills*, *slash commands*, and agent-comms
|
|
6
|
+
*notifications* (parity with the Gemini/OpenCode plugin surfaces).
|
|
7
|
+
|
|
8
|
+
Design constraints (all load-bearing):
|
|
9
|
+
|
|
10
|
+
* **stdlib-only** — the same package is imported by the ``basemind`` CLI, so this module must
|
|
11
|
+
not pull in Hermes or any third-party dependency at import time.
|
|
12
|
+
* **import-cheap and side-effect-free** at module load.
|
|
13
|
+
* **fail-open** — every registration and every hook is guarded; a missing ``basemind`` binary,
|
|
14
|
+
a down comms broker, or a Hermes build that lacks a given ``ctx`` method must degrade to a
|
|
15
|
+
no-op, never raise. A raising plugin would break both Hermes startup and the CLI.
|
|
16
|
+
|
|
17
|
+
Hermes calls :func:`register` with a ``PluginContext`` once, at load. The comms hooks close over
|
|
18
|
+
that context so they can call ``ctx.inject_message(...)`` when they fire.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
import os
|
|
25
|
+
import subprocess
|
|
26
|
+
import tempfile
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
|
|
29
|
+
_PKG_DIR = Path(__file__).resolve().parent
|
|
30
|
+
_SKILLS_DIR = _PKG_DIR / "skills"
|
|
31
|
+
_COMMANDS_DIR = _PKG_DIR / "commands"
|
|
32
|
+
|
|
33
|
+
_DISCIPLINE = (
|
|
34
|
+
"basemind is available over MCP in this session — a tree-sitter code map + git context. "
|
|
35
|
+
"Prefer it over grep/read for structural and historical questions: its tools return paths, "
|
|
36
|
+
"line numbers, and signatures, not file bodies, so they cost a fraction of the tokens of "
|
|
37
|
+
"reading source. Default workflow: outline a file before opening it (then read only the span "
|
|
38
|
+
"you need); search_symbols instead of grep for a definition; find_references/find_callers "
|
|
39
|
+
"instead of grepping call sites; workspace_grep instead of shelling out to ripgrep; rescan "
|
|
40
|
+
"after edits instead of reconnecting. Do not re-read a file basemind already mapped."
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
_COMMS_TOOLS = (
|
|
44
|
+
"basemind first, shell/grep/git fallback — prefer basemind over grep, over naked git, and for "
|
|
45
|
+
"docs/RAG/NER, web crawl, and parsing. You are connected to basemind agent-comms — a shared "
|
|
46
|
+
"multi-agent chat. You have auto-joined every room scoped to this workspace. Levers: room_post "
|
|
47
|
+
"{room, subject, body, reply_to?} to send (always give a short subject; the body holds the "
|
|
48
|
+
"detail); room_history {room} and inbox_read to scan messages (these return front-matter only — "
|
|
49
|
+
"subject/from/id — never bodies, to stay token-frugal); message_get {message_id} to read one "
|
|
50
|
+
"body on demand; room_list to see rooms, room_join to join another. Prefer posting a concise "
|
|
51
|
+
"status/question over staying silent when collaborating."
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def register(ctx) -> None:
|
|
56
|
+
"""Register basemind's skills, slash commands, and comms hooks with Hermes.
|
|
57
|
+
|
|
58
|
+
Each capability group is independently guarded so a failure in one (or a ``ctx`` that
|
|
59
|
+
lacks a given ``register_*`` method) never prevents the others from registering.
|
|
60
|
+
"""
|
|
61
|
+
for step in (_register_skills, _register_commands, _register_hooks):
|
|
62
|
+
try:
|
|
63
|
+
step(ctx)
|
|
64
|
+
except Exception: # pragma: no cover - defensive: never break plugin load
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _register_skills(ctx) -> None:
|
|
69
|
+
reg = getattr(ctx, "register_skill", None)
|
|
70
|
+
if not callable(reg) or not _SKILLS_DIR.is_dir():
|
|
71
|
+
return
|
|
72
|
+
for skill_md in sorted(_SKILLS_DIR.glob("*/SKILL.md")):
|
|
73
|
+
_safe_call(reg, skill_md.parent.name, str(skill_md))
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _register_commands(ctx) -> None:
|
|
77
|
+
reg = getattr(ctx, "register_command", None)
|
|
78
|
+
if not callable(reg) or not _COMMANDS_DIR.is_dir():
|
|
79
|
+
return
|
|
80
|
+
for cmd_md in sorted(_COMMANDS_DIR.glob("*.md")):
|
|
81
|
+
body = _read_text(cmd_md)
|
|
82
|
+
if not body:
|
|
83
|
+
continue
|
|
84
|
+
name = cmd_md.stem
|
|
85
|
+
description = _front_matter_description(body) or f"basemind {name} command"
|
|
86
|
+
_safe_call(reg, name, _make_command_handler(body), description)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _make_command_handler(body: str):
|
|
90
|
+
"""A command handler returns the command's markdown (its agent instructions) verbatim.
|
|
91
|
+
|
|
92
|
+
These ``.md`` files are the same prompt definitions the Claude/Codex plugins expand, so
|
|
93
|
+
returning the body gives Hermes faithful slash-command parity without shelling anything.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def handler(_args: str = "") -> str:
|
|
97
|
+
return body
|
|
98
|
+
|
|
99
|
+
return handler
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _register_hooks(ctx) -> None:
|
|
103
|
+
reg = getattr(ctx, "register_hook", None)
|
|
104
|
+
inject = getattr(ctx, "inject_message", None)
|
|
105
|
+
if not callable(reg) or not callable(inject):
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
def _inject(text):
|
|
109
|
+
if not text:
|
|
110
|
+
return
|
|
111
|
+
if not _safe_call(inject, text):
|
|
112
|
+
_safe_call(inject, text, "user")
|
|
113
|
+
|
|
114
|
+
def on_session_start(**_kwargs):
|
|
115
|
+
_inject(_session_start_context(os.getcwd()))
|
|
116
|
+
|
|
117
|
+
def pre_llm_call(**kwargs):
|
|
118
|
+
sid = str(kwargs.get("task_id") or kwargs.get("session_id") or "default")
|
|
119
|
+
_inject(_delta_context(os.getcwd(), sid))
|
|
120
|
+
|
|
121
|
+
_safe_call(reg, "on_session_start", on_session_start)
|
|
122
|
+
_safe_call(reg, "pre_llm_call", pre_llm_call)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _session_start_context(cwd: str) -> str:
|
|
126
|
+
"""Boot context: the operating discipline, plus a condensed comms inbox if the broker responds."""
|
|
127
|
+
messages = _inbox_messages(cwd, 8)
|
|
128
|
+
if messages is None:
|
|
129
|
+
return _DISCIPLINE
|
|
130
|
+
if messages:
|
|
131
|
+
return (
|
|
132
|
+
f"{_DISCIPLINE} {_COMMS_TOOLS}\n"
|
|
133
|
+
"Recent messages (front-matter only; call message_get with an id to read a body):\n"
|
|
134
|
+
f"{_format_lines(messages)}"
|
|
135
|
+
)
|
|
136
|
+
return f"{_DISCIPLINE} {_COMMS_TOOLS} No messages in your rooms yet — post one to kick things off."
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _delta_context(cwd: str, session_id: str) -> str | None:
|
|
140
|
+
"""Per-turn delta: inject only messages newer than this session's high-water mark."""
|
|
141
|
+
messages = _inbox_messages(cwd, 30)
|
|
142
|
+
if not messages:
|
|
143
|
+
return None
|
|
144
|
+
max_ts = max((_ts(m) for m in messages), default=0)
|
|
145
|
+
hwm_file = _hwm_path(session_id)
|
|
146
|
+
if not hwm_file.exists():
|
|
147
|
+
_write_text(hwm_file, str(max_ts))
|
|
148
|
+
return None
|
|
149
|
+
hwm = _read_int(hwm_file)
|
|
150
|
+
fresh = [m for m in messages if _ts(m) > hwm]
|
|
151
|
+
if not fresh:
|
|
152
|
+
return None
|
|
153
|
+
_write_text(hwm_file, str(max_ts))
|
|
154
|
+
return (
|
|
155
|
+
"New basemind agent-comms message(s) since your last turn (front-matter only — call "
|
|
156
|
+
"message_get with an id to read a body):\n"
|
|
157
|
+
f"{_format_lines(fresh)}\n"
|
|
158
|
+
"Reply with room_post {room, subject, body, reply_to:<id>} if a response is warranted."
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _inbox_messages(cwd: str, limit: int):
|
|
163
|
+
"""Return the inbox message list (possibly empty), or ``None`` when the broker is unavailable."""
|
|
164
|
+
data = _run_basemind_json(["comms", "inbox", "--root", cwd, "--json", "--limit", str(limit)], timeout=6)
|
|
165
|
+
if data is None:
|
|
166
|
+
return None
|
|
167
|
+
messages = data.get("messages")
|
|
168
|
+
return messages if isinstance(messages, list) else []
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _run_basemind_json(args, timeout):
|
|
172
|
+
try:
|
|
173
|
+
proc = subprocess.run( # noqa: S603,S607 - fixed argv, no shell
|
|
174
|
+
["basemind", *args],
|
|
175
|
+
capture_output=True,
|
|
176
|
+
text=True,
|
|
177
|
+
timeout=timeout,
|
|
178
|
+
)
|
|
179
|
+
except Exception:
|
|
180
|
+
return None
|
|
181
|
+
if proc.returncode != 0 or not proc.stdout.strip():
|
|
182
|
+
return None
|
|
183
|
+
try:
|
|
184
|
+
return json.loads(proc.stdout)
|
|
185
|
+
except Exception:
|
|
186
|
+
return None
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _format_lines(messages) -> str:
|
|
190
|
+
return "\n".join(f" • [{m.get('subject', '')}] from {m.get('from', '')} (id: {m.get('id', '')})" for m in messages)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _ts(message) -> int:
|
|
194
|
+
try:
|
|
195
|
+
return int(message.get("ts_micros") or 0)
|
|
196
|
+
except (TypeError, ValueError):
|
|
197
|
+
return 0
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _hwm_path(session_id: str) -> Path:
|
|
201
|
+
safe = "".join(c if (c.isalnum() or c in "._-") else "_" for c in session_id) or "default"
|
|
202
|
+
return Path(tempfile.gettempdir()) / f"basemind-comms-hwm-{safe}"
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _front_matter_description(body: str) -> str | None:
|
|
206
|
+
"""Pull ``description:`` from a leading ``---`` YAML front-matter block, if present."""
|
|
207
|
+
if not body.startswith("---"):
|
|
208
|
+
return None
|
|
209
|
+
end = body.find("\n---", 3)
|
|
210
|
+
if end == -1:
|
|
211
|
+
return None
|
|
212
|
+
for line in body[3:end].splitlines():
|
|
213
|
+
stripped = line.strip()
|
|
214
|
+
if stripped.startswith("description:"):
|
|
215
|
+
return stripped[len("description:") :].strip().strip("\"'") or None
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _safe_call(fn, *args) -> bool:
|
|
220
|
+
"""Call ``fn(*args)``; return True on success, False on any exception. Never raises."""
|
|
221
|
+
try:
|
|
222
|
+
fn(*args)
|
|
223
|
+
return True
|
|
224
|
+
except Exception:
|
|
225
|
+
return False
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _read_text(path: Path) -> str:
|
|
229
|
+
try:
|
|
230
|
+
return path.read_text(encoding="utf-8", errors="replace")
|
|
231
|
+
except Exception:
|
|
232
|
+
return ""
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _write_text(path: Path, text: str) -> None:
|
|
236
|
+
try:
|
|
237
|
+
path.write_text(text, encoding="utf-8")
|
|
238
|
+
except Exception:
|
|
239
|
+
pass
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _read_int(path: Path) -> int:
|
|
243
|
+
try:
|
|
244
|
+
return int(path.read_text(encoding="utf-8").strip() or "0")
|
|
245
|
+
except Exception:
|
|
246
|
+
return 0
|