hindsight-zed 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.
- hindsight_zed-0.1.0/.gitignore +3 -0
- hindsight_zed-0.1.0/PKG-INFO +120 -0
- hindsight_zed-0.1.0/README.md +99 -0
- hindsight_zed-0.1.0/hindsight_zed/__init__.py +3 -0
- hindsight_zed-0.1.0/hindsight_zed/cli.py +173 -0
- hindsight_zed-0.1.0/hindsight_zed/config.py +79 -0
- hindsight_zed-0.1.0/hindsight_zed/rules_file.py +97 -0
- hindsight_zed-0.1.0/hindsight_zed/zed_settings.py +126 -0
- hindsight_zed-0.1.0/pyproject.toml +59 -0
- hindsight_zed-0.1.0/settings.json +5 -0
- hindsight_zed-0.1.0/tests/__init__.py +0 -0
- hindsight_zed-0.1.0/tests/test_cli.py +67 -0
- hindsight_zed-0.1.0/tests/test_config.py +39 -0
- hindsight_zed-0.1.0/tests/test_e2e.py +53 -0
- hindsight_zed-0.1.0/tests/test_rules_file.py +64 -0
- hindsight_zed-0.1.0/tests/test_zed_settings.py +102 -0
- hindsight_zed-0.1.0/uv.lock +185 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hindsight-zed
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Automatic long-term memory for the Zed editor's AI assistant via Hindsight
|
|
5
|
+
Project-URL: Homepage, https://github.com/vectorize-io/hindsight
|
|
6
|
+
Project-URL: Documentation, https://github.com/vectorize-io/hindsight/tree/main/hindsight-integrations/zed
|
|
7
|
+
Project-URL: Repository, https://github.com/vectorize-io/hindsight
|
|
8
|
+
Author-email: Vectorize <support@vectorize.io>
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: agents,ai,coding,hindsight,memory,zed
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# hindsight-zed
|
|
23
|
+
|
|
24
|
+
Long-term memory for the [Zed](https://zed.dev) editor's AI assistant, powered by
|
|
25
|
+
[Hindsight](https://github.com/vectorize-io/hindsight).
|
|
26
|
+
|
|
27
|
+
`hindsight-zed init` wires Zed's Agent Panel to the Hindsight **MCP server** and
|
|
28
|
+
adds a rule telling the agent to use it — so it recalls relevant memory at the
|
|
29
|
+
start of a task and retains durable facts as it goes. Recall happens at query
|
|
30
|
+
time against your actual message (no lag), and from your seat it's automatic.
|
|
31
|
+
|
|
32
|
+
## How it works
|
|
33
|
+
|
|
34
|
+
Zed has no pre-prompt hook, but it does support two things this integration uses:
|
|
35
|
+
|
|
36
|
+
- **MCP context servers** — Zed runs MCP servers configured under
|
|
37
|
+
`context_servers` in `settings.json` and exposes their tools in the Agent
|
|
38
|
+
Panel. We register the Hindsight MCP server there, giving the agent
|
|
39
|
+
`recall` / `retain` / `reflect` tools.
|
|
40
|
+
- **A global instructions file** (`~/.config/zed/AGENTS.md`) that Zed includes in
|
|
41
|
+
every conversation. We add a small rule there telling the agent to recall
|
|
42
|
+
first and retain what it learns.
|
|
43
|
+
|
|
44
|
+
Zed doesn't yet have native HTTP-MCP transport, so the server is connected
|
|
45
|
+
through the [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) stdio bridge
|
|
46
|
+
(run via `npx`) — that means you need Node.js installed.
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install hindsight-zed
|
|
52
|
+
hindsight-zed init --api-token YOUR_HINDSIGHT_API_KEY --bank-id my-memory
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`init` adds the `hindsight` MCP server to `~/.config/zed/settings.json` and the
|
|
56
|
+
recall/retain rule to `~/.config/zed/AGENTS.md`. Restart Zed, open the Agent
|
|
57
|
+
Panel, and the `hindsight` server should show a green dot.
|
|
58
|
+
|
|
59
|
+
Use a [Hindsight Cloud](https://hindsight.vectorize.io) key, or point at a
|
|
60
|
+
self-hosted server with `--api-url http://localhost:8888` (no token needed for an
|
|
61
|
+
open local server).
|
|
62
|
+
|
|
63
|
+
> If your `settings.json` contains comments (JSONC), `init` won't rewrite it —
|
|
64
|
+
> it prints the exact `context_servers` entry for you to paste instead. Use
|
|
65
|
+
> `hindsight-zed init --print-only` any time to see the snippet without writing.
|
|
66
|
+
|
|
67
|
+
## Commands
|
|
68
|
+
|
|
69
|
+
| Command | Description |
|
|
70
|
+
| --- | --- |
|
|
71
|
+
| `hindsight-zed init` | Add the MCP server + recall/retain rule |
|
|
72
|
+
| `hindsight-zed status` | Show whether the server + rule are configured |
|
|
73
|
+
| `hindsight-zed uninstall` | Remove the server + rule |
|
|
74
|
+
| `hindsight-zed init --print-only` | Print the config to add manually |
|
|
75
|
+
|
|
76
|
+
## What gets written
|
|
77
|
+
|
|
78
|
+
`~/.config/zed/settings.json`:
|
|
79
|
+
|
|
80
|
+
```jsonc
|
|
81
|
+
{
|
|
82
|
+
"context_servers": {
|
|
83
|
+
"hindsight": {
|
|
84
|
+
"source": "custom",
|
|
85
|
+
"command": "npx",
|
|
86
|
+
"args": [
|
|
87
|
+
"-y", "mcp-remote",
|
|
88
|
+
"https://api.hindsight.vectorize.io/mcp/my-memory/",
|
|
89
|
+
"--header", "Authorization: Bearer YOUR_HINDSIGHT_API_KEY"
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`~/.config/zed/AGENTS.md` (inside a fenced `<!-- HINDSIGHT -->` block that leaves
|
|
97
|
+
the rest of the file untouched): a short rule telling the agent to `recall` at
|
|
98
|
+
the start of each task and `retain` durable facts.
|
|
99
|
+
|
|
100
|
+
## Configuration
|
|
101
|
+
|
|
102
|
+
| Setting | Env var | Default |
|
|
103
|
+
| --- | --- | --- |
|
|
104
|
+
| API URL | `HINDSIGHT_API_URL` | `https://api.hindsight.vectorize.io` |
|
|
105
|
+
| API token | `HINDSIGHT_API_TOKEN` | _(none; required for Cloud)_ |
|
|
106
|
+
| Bank id | `HINDSIGHT_ZED_BANK_ID` | `zed` |
|
|
107
|
+
|
|
108
|
+
These can also live in `~/.hindsight/zed.json` (written by `init`).
|
|
109
|
+
|
|
110
|
+
## Development
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
uv sync
|
|
114
|
+
uv run pytest tests -v -m 'not requires_real_llm' # deterministic suite
|
|
115
|
+
uv run pytest tests -v -m requires_real_llm # gated MCP-endpoint check
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
MIT
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# hindsight-zed
|
|
2
|
+
|
|
3
|
+
Long-term memory for the [Zed](https://zed.dev) editor's AI assistant, powered by
|
|
4
|
+
[Hindsight](https://github.com/vectorize-io/hindsight).
|
|
5
|
+
|
|
6
|
+
`hindsight-zed init` wires Zed's Agent Panel to the Hindsight **MCP server** and
|
|
7
|
+
adds a rule telling the agent to use it — so it recalls relevant memory at the
|
|
8
|
+
start of a task and retains durable facts as it goes. Recall happens at query
|
|
9
|
+
time against your actual message (no lag), and from your seat it's automatic.
|
|
10
|
+
|
|
11
|
+
## How it works
|
|
12
|
+
|
|
13
|
+
Zed has no pre-prompt hook, but it does support two things this integration uses:
|
|
14
|
+
|
|
15
|
+
- **MCP context servers** — Zed runs MCP servers configured under
|
|
16
|
+
`context_servers` in `settings.json` and exposes their tools in the Agent
|
|
17
|
+
Panel. We register the Hindsight MCP server there, giving the agent
|
|
18
|
+
`recall` / `retain` / `reflect` tools.
|
|
19
|
+
- **A global instructions file** (`~/.config/zed/AGENTS.md`) that Zed includes in
|
|
20
|
+
every conversation. We add a small rule there telling the agent to recall
|
|
21
|
+
first and retain what it learns.
|
|
22
|
+
|
|
23
|
+
Zed doesn't yet have native HTTP-MCP transport, so the server is connected
|
|
24
|
+
through the [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) stdio bridge
|
|
25
|
+
(run via `npx`) — that means you need Node.js installed.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install hindsight-zed
|
|
31
|
+
hindsight-zed init --api-token YOUR_HINDSIGHT_API_KEY --bank-id my-memory
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`init` adds the `hindsight` MCP server to `~/.config/zed/settings.json` and the
|
|
35
|
+
recall/retain rule to `~/.config/zed/AGENTS.md`. Restart Zed, open the Agent
|
|
36
|
+
Panel, and the `hindsight` server should show a green dot.
|
|
37
|
+
|
|
38
|
+
Use a [Hindsight Cloud](https://hindsight.vectorize.io) key, or point at a
|
|
39
|
+
self-hosted server with `--api-url http://localhost:8888` (no token needed for an
|
|
40
|
+
open local server).
|
|
41
|
+
|
|
42
|
+
> If your `settings.json` contains comments (JSONC), `init` won't rewrite it —
|
|
43
|
+
> it prints the exact `context_servers` entry for you to paste instead. Use
|
|
44
|
+
> `hindsight-zed init --print-only` any time to see the snippet without writing.
|
|
45
|
+
|
|
46
|
+
## Commands
|
|
47
|
+
|
|
48
|
+
| Command | Description |
|
|
49
|
+
| --- | --- |
|
|
50
|
+
| `hindsight-zed init` | Add the MCP server + recall/retain rule |
|
|
51
|
+
| `hindsight-zed status` | Show whether the server + rule are configured |
|
|
52
|
+
| `hindsight-zed uninstall` | Remove the server + rule |
|
|
53
|
+
| `hindsight-zed init --print-only` | Print the config to add manually |
|
|
54
|
+
|
|
55
|
+
## What gets written
|
|
56
|
+
|
|
57
|
+
`~/.config/zed/settings.json`:
|
|
58
|
+
|
|
59
|
+
```jsonc
|
|
60
|
+
{
|
|
61
|
+
"context_servers": {
|
|
62
|
+
"hindsight": {
|
|
63
|
+
"source": "custom",
|
|
64
|
+
"command": "npx",
|
|
65
|
+
"args": [
|
|
66
|
+
"-y", "mcp-remote",
|
|
67
|
+
"https://api.hindsight.vectorize.io/mcp/my-memory/",
|
|
68
|
+
"--header", "Authorization: Bearer YOUR_HINDSIGHT_API_KEY"
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`~/.config/zed/AGENTS.md` (inside a fenced `<!-- HINDSIGHT -->` block that leaves
|
|
76
|
+
the rest of the file untouched): a short rule telling the agent to `recall` at
|
|
77
|
+
the start of each task and `retain` durable facts.
|
|
78
|
+
|
|
79
|
+
## Configuration
|
|
80
|
+
|
|
81
|
+
| Setting | Env var | Default |
|
|
82
|
+
| --- | --- | --- |
|
|
83
|
+
| API URL | `HINDSIGHT_API_URL` | `https://api.hindsight.vectorize.io` |
|
|
84
|
+
| API token | `HINDSIGHT_API_TOKEN` | _(none; required for Cloud)_ |
|
|
85
|
+
| Bank id | `HINDSIGHT_ZED_BANK_ID` | `zed` |
|
|
86
|
+
|
|
87
|
+
These can also live in `~/.hindsight/zed.json` (written by `init`).
|
|
88
|
+
|
|
89
|
+
## Development
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
uv sync
|
|
93
|
+
uv run pytest tests -v -m 'not requires_real_llm' # deterministic suite
|
|
94
|
+
uv run pytest tests -v -m requires_real_llm # gated MCP-endpoint check
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
MIT
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""CLI for the Hindsight Zed integration.
|
|
2
|
+
|
|
3
|
+
``hindsight-zed init`` wires Zed's MCP ``context_servers`` to the Hindsight MCP
|
|
4
|
+
endpoint and writes a recall/retain rule into Zed's global instructions file.
|
|
5
|
+
After that, Zed's Agent Panel has ``recall``/``retain``/``reflect`` tools and is
|
|
6
|
+
told (via the rule) to use them automatically. There is no background process.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import json
|
|
13
|
+
import shutil
|
|
14
|
+
import sys
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Optional
|
|
18
|
+
|
|
19
|
+
from . import __version__
|
|
20
|
+
from .config import USER_CONFIG_FILE, ZedConfig, load_config
|
|
21
|
+
from .rules_file import RULE_TEXT, clear_rule, default_rules_path, write_rule
|
|
22
|
+
from .rules_file import is_installed as rule_installed
|
|
23
|
+
from .zed_settings import (
|
|
24
|
+
SettingsResult,
|
|
25
|
+
apply_to_settings,
|
|
26
|
+
build_context_server,
|
|
27
|
+
default_settings_path,
|
|
28
|
+
remove_from_settings,
|
|
29
|
+
render_snippet,
|
|
30
|
+
)
|
|
31
|
+
from .zed_settings import (
|
|
32
|
+
is_installed as server_installed,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class InstallOutcome:
|
|
38
|
+
"""Result of an ``init``: how the settings file changed and where the rule went."""
|
|
39
|
+
|
|
40
|
+
settings: SettingsResult
|
|
41
|
+
rules_path: Path
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def build_install(config: ZedConfig, settings_path: Path, rules_path: Path) -> InstallOutcome:
|
|
45
|
+
"""Apply the MCP server entry and the recall/retain rule (the testable core)."""
|
|
46
|
+
server = build_context_server(config.hindsight_api_url, config.hindsight_api_token, config.bank_id)
|
|
47
|
+
settings = apply_to_settings(settings_path, server)
|
|
48
|
+
write_rule(rules_path)
|
|
49
|
+
return InstallOutcome(settings=settings, rules_path=rules_path)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _config_path(args: argparse.Namespace) -> Path:
|
|
53
|
+
return Path(args.config_path) if args.config_path else USER_CONFIG_FILE
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _resolve_config(args: argparse.Namespace) -> ZedConfig:
|
|
57
|
+
"""Config from file/env, overridden by any explicitly-passed CLI flags."""
|
|
58
|
+
cfg = load_config(config_file=_config_path(args))
|
|
59
|
+
if args.api_url:
|
|
60
|
+
cfg.hindsight_api_url = args.api_url
|
|
61
|
+
if args.api_token:
|
|
62
|
+
cfg.hindsight_api_token = args.api_token
|
|
63
|
+
if args.bank_id:
|
|
64
|
+
cfg.bank_id = args.bank_id
|
|
65
|
+
return cfg
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _scaffold_config(cfg: ZedConfig, config_path: Path) -> None:
|
|
69
|
+
"""Persist the resolved connection settings so re-runs remember them."""
|
|
70
|
+
if config_path.is_file():
|
|
71
|
+
return
|
|
72
|
+
data = {"hindsightApiUrl": cfg.hindsight_api_url, "bankId": cfg.bank_id}
|
|
73
|
+
if cfg.hindsight_api_token:
|
|
74
|
+
data["hindsightApiToken"] = cfg.hindsight_api_token
|
|
75
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
76
|
+
config_path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def cmd_init(args: argparse.Namespace) -> None:
|
|
80
|
+
cfg = _resolve_config(args)
|
|
81
|
+
settings_path = Path(args.settings_path) if args.settings_path else default_settings_path()
|
|
82
|
+
rules_path = Path(args.rules_path) if args.rules_path else default_rules_path()
|
|
83
|
+
server = build_context_server(cfg.hindsight_api_url, cfg.hindsight_api_token, cfg.bank_id)
|
|
84
|
+
|
|
85
|
+
if args.print_only:
|
|
86
|
+
print("Add this to your Zed settings.json:\n")
|
|
87
|
+
print(render_snippet(server))
|
|
88
|
+
print("\nAnd add this rule to ~/.config/zed/AGENTS.md:\n")
|
|
89
|
+
print(RULE_TEXT)
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
print("Setting up Hindsight for Zed ...")
|
|
93
|
+
_scaffold_config(cfg, _config_path(args))
|
|
94
|
+
outcome = build_install(cfg, settings_path, rules_path)
|
|
95
|
+
|
|
96
|
+
if outcome.settings.action == "manual":
|
|
97
|
+
print(f" Your {outcome.settings.path} has comments, so I won't rewrite it.")
|
|
98
|
+
print(" Add this `context_servers` entry yourself:\n")
|
|
99
|
+
print(render_snippet(server))
|
|
100
|
+
else:
|
|
101
|
+
verb = {"created": "Created", "merged": "Updated", "unchanged": "Already configured in"}[
|
|
102
|
+
outcome.settings.action
|
|
103
|
+
]
|
|
104
|
+
print(f" {verb} {outcome.settings.path} (MCP server: hindsight → bank '{cfg.bank_id}')")
|
|
105
|
+
print(f" Wrote recall/retain rule to {outcome.rules_path}")
|
|
106
|
+
|
|
107
|
+
if shutil.which("npx") is None:
|
|
108
|
+
print("\n warning: `npx` (Node.js) was not found on PATH. Zed runs the MCP")
|
|
109
|
+
print(" bridge via `npx mcp-remote`, so install Node.js for the server to start.")
|
|
110
|
+
|
|
111
|
+
print("\nDone. Restart Zed, open the Agent Panel, and the `hindsight` MCP server")
|
|
112
|
+
print("should show a green dot. Memory recall/retain then happen automatically.")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def cmd_status(args: argparse.Namespace) -> None:
|
|
116
|
+
settings_path = Path(args.settings_path) if args.settings_path else default_settings_path()
|
|
117
|
+
rules_path = Path(args.rules_path) if args.rules_path else default_rules_path()
|
|
118
|
+
print(f"MCP server in {settings_path}: {'installed' if server_installed(settings_path) else 'not installed'}")
|
|
119
|
+
print(f"Recall/retain rule in {rules_path}: {'installed' if rule_installed(rules_path) else 'not installed'}")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def cmd_uninstall(args: argparse.Namespace) -> None:
|
|
123
|
+
settings_path = Path(args.settings_path) if args.settings_path else default_settings_path()
|
|
124
|
+
rules_path = Path(args.rules_path) if args.rules_path else default_rules_path()
|
|
125
|
+
result = remove_from_settings(settings_path)
|
|
126
|
+
if result.action == "manual":
|
|
127
|
+
print(f" {settings_path} has comments — remove the `hindsight` context_servers entry yourself.")
|
|
128
|
+
elif result.action == "removed":
|
|
129
|
+
print(f" Removed the hindsight MCP server from {settings_path}")
|
|
130
|
+
else:
|
|
131
|
+
print(f" No hindsight MCP server found in {settings_path}")
|
|
132
|
+
clear_rule(rules_path)
|
|
133
|
+
print(f" Removed the recall/retain rule from {rules_path}")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _add_path_overrides(parser: argparse.ArgumentParser) -> None:
|
|
137
|
+
# Hidden overrides used by tests and advanced setups.
|
|
138
|
+
parser.add_argument("--settings-path", default=None, help=argparse.SUPPRESS)
|
|
139
|
+
parser.add_argument("--rules-path", default=None, help=argparse.SUPPRESS)
|
|
140
|
+
parser.add_argument("--config-path", default=None, help=argparse.SUPPRESS)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def main(argv: Optional[list] = None) -> int:
|
|
144
|
+
parser = argparse.ArgumentParser(prog="hindsight-zed", description="Hindsight memory for Zed (via MCP)")
|
|
145
|
+
parser.add_argument("--version", action="version", version=f"hindsight-zed {__version__}")
|
|
146
|
+
sub = parser.add_subparsers(dest="command")
|
|
147
|
+
|
|
148
|
+
init_p = sub.add_parser("init", help="Configure Zed's MCP server + recall/retain rule")
|
|
149
|
+
init_p.add_argument("--api-url", default=None, help="Hindsight API URL (default: cloud)")
|
|
150
|
+
init_p.add_argument("--api-token", default=None, help="Hindsight API token (for Cloud)")
|
|
151
|
+
init_p.add_argument("--bank-id", default=None, help="Memory bank for the MCP server (default: zed)")
|
|
152
|
+
init_p.add_argument("--print-only", action="store_true", help="Print the config to add manually; write nothing")
|
|
153
|
+
_add_path_overrides(init_p)
|
|
154
|
+
init_p.set_defaults(func=cmd_init)
|
|
155
|
+
|
|
156
|
+
status_p = sub.add_parser("status", help="Show whether the MCP server + rule are configured")
|
|
157
|
+
_add_path_overrides(status_p)
|
|
158
|
+
status_p.set_defaults(func=cmd_status)
|
|
159
|
+
|
|
160
|
+
uninst_p = sub.add_parser("uninstall", help="Remove the MCP server + rule")
|
|
161
|
+
_add_path_overrides(uninst_p)
|
|
162
|
+
uninst_p.set_defaults(func=cmd_uninstall)
|
|
163
|
+
|
|
164
|
+
args = parser.parse_args(argv)
|
|
165
|
+
if not hasattr(args, "func"):
|
|
166
|
+
parser.print_help()
|
|
167
|
+
return 1
|
|
168
|
+
args.func(args)
|
|
169
|
+
return 0
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
if __name__ == "__main__":
|
|
173
|
+
sys.exit(main())
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Configuration for the Hindsight Zed integration.
|
|
2
|
+
|
|
3
|
+
Settings layer (later wins): built-in defaults → ``~/.hindsight/zed.json`` →
|
|
4
|
+
environment variables. Resolved into a typed :class:`ZedConfig`.
|
|
5
|
+
|
|
6
|
+
The integration is configuration-only: it wires Zed's MCP ``context_servers`` to
|
|
7
|
+
the Hindsight MCP endpoint and writes a recall/retain rule into Zed's global
|
|
8
|
+
instructions file. Memory operations happen through the MCP server at runtime,
|
|
9
|
+
so there is no daemon or direct API client here.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Optional
|
|
19
|
+
|
|
20
|
+
# Cross-integration cloud-default convention.
|
|
21
|
+
DEFAULT_HINDSIGHT_API_URL = "https://api.hindsight.vectorize.io"
|
|
22
|
+
DEFAULT_BANK_ID = "zed"
|
|
23
|
+
|
|
24
|
+
USER_CONFIG_FILE = Path.home() / ".hindsight" / "zed.json"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class ZedConfig:
|
|
29
|
+
"""Resolved configuration for the Zed MCP setup."""
|
|
30
|
+
|
|
31
|
+
hindsight_api_url: str = DEFAULT_HINDSIGHT_API_URL
|
|
32
|
+
hindsight_api_token: Optional[str] = None
|
|
33
|
+
# The memory bank the Zed MCP server is scoped to (it's the last path
|
|
34
|
+
# segment of the MCP endpoint URL).
|
|
35
|
+
bank_id: str = DEFAULT_BANK_ID
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# user-config file key -> attribute
|
|
39
|
+
_FILE_KEYS = {
|
|
40
|
+
"hindsightApiUrl": "hindsight_api_url",
|
|
41
|
+
"hindsightApiToken": "hindsight_api_token",
|
|
42
|
+
"bankId": "bank_id",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# env var -> attribute
|
|
46
|
+
_ENV_KEYS = {
|
|
47
|
+
"HINDSIGHT_API_URL": "hindsight_api_url",
|
|
48
|
+
"HINDSIGHT_API_TOKEN": "hindsight_api_token",
|
|
49
|
+
"HINDSIGHT_ZED_BANK_ID": "bank_id",
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def load_config(config_file: Optional[Path] = None, env: Optional[dict] = None) -> ZedConfig:
|
|
54
|
+
"""Load and resolve configuration from file then environment."""
|
|
55
|
+
cfg = ZedConfig()
|
|
56
|
+
env = os.environ if env is None else env
|
|
57
|
+
|
|
58
|
+
path = config_file if config_file is not None else USER_CONFIG_FILE
|
|
59
|
+
if path.is_file():
|
|
60
|
+
try:
|
|
61
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
62
|
+
except (json.JSONDecodeError, OSError):
|
|
63
|
+
data = {}
|
|
64
|
+
for key, attr in _FILE_KEYS.items():
|
|
65
|
+
value = data.get(key)
|
|
66
|
+
if value:
|
|
67
|
+
setattr(cfg, attr, str(value))
|
|
68
|
+
|
|
69
|
+
for key, attr in _ENV_KEYS.items():
|
|
70
|
+
value = env.get(key)
|
|
71
|
+
if value:
|
|
72
|
+
setattr(cfg, attr, str(value))
|
|
73
|
+
|
|
74
|
+
if not cfg.hindsight_api_url:
|
|
75
|
+
cfg.hindsight_api_url = DEFAULT_HINDSIGHT_API_URL
|
|
76
|
+
if not cfg.bank_id:
|
|
77
|
+
cfg.bank_id = DEFAULT_BANK_ID
|
|
78
|
+
|
|
79
|
+
return cfg
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Manage Hindsight's recall/retain rule inside Zed's global instructions file.
|
|
2
|
+
|
|
3
|
+
Zed includes a global instructions file (``~/.config/zed/AGENTS.md`` on macOS and
|
|
4
|
+
Linux) in **every** agent conversation. We write a static rule there telling the
|
|
5
|
+
agent to use the Hindsight MCP tools — recall relevant memory at the start of a
|
|
6
|
+
task, and retain durable facts as it learns them.
|
|
7
|
+
|
|
8
|
+
The rule lives inside a fenced ``<!-- HINDSIGHT:BEGIN -->`` … ``<!-- HINDSIGHT:END -->``
|
|
9
|
+
block so we can update or remove it without touching the user's own rules in the
|
|
10
|
+
same file.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
BEGIN_MARKER = "<!-- HINDSIGHT:BEGIN -->"
|
|
18
|
+
END_MARKER = "<!-- HINDSIGHT:END -->"
|
|
19
|
+
|
|
20
|
+
# The recall/retain instruction injected into Zed's global rules.
|
|
21
|
+
RULE_TEXT = (
|
|
22
|
+
"You have persistent long-term memory through the Hindsight MCP server "
|
|
23
|
+
"(`recall`, `retain`, and `reflect` tools).\n\n"
|
|
24
|
+
"- At the start of each task, call `recall` with the user's request to load "
|
|
25
|
+
"relevant decisions, preferences, and project context before you answer. "
|
|
26
|
+
"Use what's relevant and ignore the rest.\n"
|
|
27
|
+
"- When you learn a durable fact — an architectural decision, a user "
|
|
28
|
+
"preference, a convention, or anything worth remembering across sessions — "
|
|
29
|
+
"call `retain` to store it.\n"
|
|
30
|
+
"- Do not mention these memory operations unless the user asks about them."
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def default_rules_path() -> Path:
|
|
35
|
+
"""Zed's global instructions file (``~/.config/zed/AGENTS.md``)."""
|
|
36
|
+
return Path.home() / ".config" / "zed" / "AGENTS.md"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _strip_block(text: str) -> str:
|
|
40
|
+
"""Remove an existing HINDSIGHT block (and its surrounding blank lines)."""
|
|
41
|
+
start = text.find(BEGIN_MARKER)
|
|
42
|
+
if start == -1:
|
|
43
|
+
return text
|
|
44
|
+
end = text.find(END_MARKER, start)
|
|
45
|
+
if end == -1:
|
|
46
|
+
# Malformed (begin without end) — drop from the marker onward.
|
|
47
|
+
return text[:start].rstrip() + "\n"
|
|
48
|
+
end += len(END_MARKER)
|
|
49
|
+
before = text[:start].rstrip()
|
|
50
|
+
after = text[end:].lstrip()
|
|
51
|
+
if before and after:
|
|
52
|
+
return f"{before}\n\n{after}"
|
|
53
|
+
return (before or after).rstrip() + ("\n" if (before or after) else "")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def render_block(rule_text: str = RULE_TEXT) -> str:
|
|
57
|
+
"""Render the fenced HINDSIGHT rule block (no trailing newline)."""
|
|
58
|
+
return f"{BEGIN_MARKER}\n{rule_text.strip()}\n{END_MARKER}"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def write_rule(path: Path, rule_text: str = RULE_TEXT) -> Path:
|
|
62
|
+
"""Write/replace Hindsight's rule block in the instructions file at ``path``.
|
|
63
|
+
|
|
64
|
+
Preserves any user-authored content and only rewrites our fenced block,
|
|
65
|
+
placing it at the top so the memory rule leads the instructions.
|
|
66
|
+
"""
|
|
67
|
+
existing = path.read_text(encoding="utf-8") if path.is_file() else ""
|
|
68
|
+
base = _strip_block(existing).rstrip()
|
|
69
|
+
block = render_block(rule_text)
|
|
70
|
+
new_text = f"{block}\n\n{base}\n" if base else f"{block}\n"
|
|
71
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
72
|
+
path.write_text(new_text, encoding="utf-8")
|
|
73
|
+
return path
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def clear_rule(path: Path) -> Path:
|
|
77
|
+
"""Remove Hindsight's rule block from the instructions file, if present.
|
|
78
|
+
|
|
79
|
+
Leaves the rest of the file intact. If removing the block empties a file that
|
|
80
|
+
held nothing else, the file is deleted.
|
|
81
|
+
"""
|
|
82
|
+
if not path.is_file():
|
|
83
|
+
return path
|
|
84
|
+
existing = path.read_text(encoding="utf-8")
|
|
85
|
+
if BEGIN_MARKER not in existing:
|
|
86
|
+
return path
|
|
87
|
+
stripped = _strip_block(existing).strip()
|
|
88
|
+
if not stripped:
|
|
89
|
+
path.unlink()
|
|
90
|
+
return path
|
|
91
|
+
path.write_text(stripped + "\n", encoding="utf-8")
|
|
92
|
+
return path
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def is_installed(path: Path) -> bool:
|
|
96
|
+
"""Whether our rule block is present in the instructions file at ``path``."""
|
|
97
|
+
return path.is_file() and BEGIN_MARKER in path.read_text(encoding="utf-8")
|