meridian-server 0.1.0.dev1__py3-none-any.whl
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.
- meridian/MERIDIAN.md +79 -0
- meridian/__init__.py +3 -0
- meridian/__main__.py +137 -0
- meridian/__main__entry.py +54 -0
- meridian/_deps.py +552 -0
- meridian/dashboard.py +122 -0
- meridian/db/__init__.py +7038 -0
- meridian/db/migrations.py +1471 -0
- meridian/demo_seed.py +305 -0
- meridian/enqueue.py +214 -0
- meridian/error_alerting.py +182 -0
- meridian/executor_config.py +86 -0
- meridian/git_md.py +88 -0
- meridian/goal_md.py +433 -0
- meridian/handoff.py +980 -0
- meridian/hosted.py +2456 -0
- meridian/integrations/__init__.py +1 -0
- meridian/integrations/langgraph.py +166 -0
- meridian/limits.py +116 -0
- meridian/mcp/__init__.py +3 -0
- meridian/mcp/handler.py +1689 -0
- meridian/mcp/stdio_handler.py +1601 -0
- meridian/mcp_tools.py +622 -0
- meridian/md_anchors.py +352 -0
- meridian/models.py +345 -0
- meridian/pg_adapter.py +1591 -0
- meridian/roles.py +107 -0
- meridian/routes/__init__.py +1 -0
- meridian/routes/admin.py +227 -0
- meridian/routes/auth.py +134 -0
- meridian/routes/billing.py +459 -0
- meridian/routes/blog.py +229 -0
- meridian/routes/decisions.py +226 -0
- meridian/routes/export.py +84 -0
- meridian/routes/files.py +360 -0
- meridian/routes/github.py +343 -0
- meridian/routes/handoff.py +81 -0
- meridian/routes/hitl.py +129 -0
- meridian/routes/hooks.py +188 -0
- meridian/routes/notes.py +82 -0
- meridian/routes/projects.py +1094 -0
- meridian/routes/sessions.py +115 -0
- meridian/routes/sprint.py +311 -0
- meridian/routes/tasks.py +221 -0
- meridian/routes/workspace.py +165 -0
- meridian/sdk.py +196 -0
- meridian/server.py +5443 -0
- meridian/static/dashboard-demo.js +393 -0
- meridian/static/dashboard-files.js +208 -0
- meridian/static/dashboard-mcp.js +32 -0
- meridian/static/dashboard-notes.js +185 -0
- meridian/static/dashboard-rewind.js +726 -0
- meridian/static/dashboard-settings.js +3996 -0
- meridian/static/dashboard-sprint.js +987 -0
- meridian/static/dashboard-timeline.js +1397 -0
- meridian/static/dashboard-utils.js +98 -0
- meridian/static/dashboard.bundle.js +11385 -0
- meridian/static/dashboard.css +846 -0
- meridian/static/dashboard.js +11119 -0
- meridian/static/logo-wordmark.svg +13 -0
- meridian/static/logo.svg +15 -0
- meridian/static/meridian_logo.png +0 -0
- meridian/static/the-solution-dashboard-card.png +0 -0
- meridian/static/the-solution-dashboard.png +0 -0
- meridian/symbols.py +271 -0
- meridian/templates/changelog.html +109 -0
- meridian/templates/dashboard.html +143 -0
- meridian/templates/handoff.md.j2 +127 -0
- meridian/templates/install_mcp.html +311 -0
- meridian/templates/landing.html +916 -0
- meridian/templates/onboarding.html +224 -0
- meridian/templates/pricing.html +441 -0
- meridian/templates/privacy.html +84 -0
- meridian/templates/terms.html +66 -0
- meridian/toml_config.py +162 -0
- meridian_server-0.1.0.dev1.dist-info/METADATA +33 -0
- meridian_server-0.1.0.dev1.dist-info/RECORD +79 -0
- meridian_server-0.1.0.dev1.dist-info/WHEEL +4 -0
- meridian_server-0.1.0.dev1.dist-info/licenses/LICENSE +184 -0
meridian/MERIDIAN.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Meridian Session Instructions
|
|
2
|
+
|
|
3
|
+
You are connected to Meridian — a shared project memory across AI coding sessions.
|
|
4
|
+
Multiple sessions (Claude Code tabs, Cursor, Windsurf, etc.) can be coordinating
|
|
5
|
+
on this project concurrently. Treat the Meridian state as the source of truth
|
|
6
|
+
for "what has been decided, claimed, and shipped."
|
|
7
|
+
|
|
8
|
+
## ALWAYS
|
|
9
|
+
|
|
10
|
+
- **`log_task` after every meaningful action** so parallel sessions can see your work.
|
|
11
|
+
One line per task. Mark `status` honestly: `done`, `pending`, `failed`.
|
|
12
|
+
- **`set_decision`** for architectural, irreversible, or surprising choices.
|
|
13
|
+
These appear in the decisions log (newest first) and persist across sessions.
|
|
14
|
+
- **`generate_handoff`** when context is filling up (~80k tokens) or before you stop.
|
|
15
|
+
A clean handoff lets a fresh session resume without re-deriving everything.
|
|
16
|
+
- **`claim_task`** before starting work on something a parallel session might pick up.
|
|
17
|
+
**`release_task`** if you bail — don't leave it claimed.
|
|
18
|
+
- **`claim_file(session_id, file_path)`** before editing shared files when parallel
|
|
19
|
+
sessions are active. **`release_file(session_id, file_path)`** when done.
|
|
20
|
+
Use **`idle_until_session_done(watching_session_id)`** to wait before taking
|
|
21
|
+
over a file another session holds. Locks auto-expire after 2 hours. For a big
|
|
22
|
+
shared file, claim a single symbol instead so another session can edit a
|
|
23
|
+
different one: pass **`symbol`** (e.g. `"AuthRouter"` or `"AuthRouter.login"`)
|
|
24
|
+
**and `content`** (the file's full source) — Meridian parses it and blocks only
|
|
25
|
+
on an overlapping line range, telling you which symbols are still safe.
|
|
26
|
+
- **After completing each sprint item**, call `get_sprint_progress(project_id,
|
|
27
|
+
session_id)` before claiming the next one. Its `board_change` field reports any
|
|
28
|
+
items a planner injected since this session started (also echoed on
|
|
29
|
+
`complete_sprint_item`/`claim_sprint_item`), so single-terminal runs pick up
|
|
30
|
+
mid-run additions at the item boundary without interrupting the current task.
|
|
31
|
+
- **`update_md_section`** proposes a replacement for an anchored CLAUDE.md/AGENTS.md
|
|
32
|
+
section. Autonomous executor sessions leave it gated behind a dashboard HITL.
|
|
33
|
+
Human planning sessions (claude.ai) may pass **`force=true`** to skip the HITL and
|
|
34
|
+
apply the change directly — only use `force` when a human is driving the session.
|
|
35
|
+
- **Mid-run corrections**: after each `complete_sprint_item`, call
|
|
36
|
+
`list_hitl_requests(status='pending')` and handle any request with
|
|
37
|
+
`kind='correction'` before the next item: apply the correction, `log_task` it,
|
|
38
|
+
and `answer_hitl(request_id, "acknowledged")`. Corrections never block an
|
|
39
|
+
unattended run — fail open, log, and keep going. (Plain `kind='question'`
|
|
40
|
+
requests stay blocking/auto-answerable as before.)
|
|
41
|
+
|
|
42
|
+
## ON SESSION START
|
|
43
|
+
|
|
44
|
+
Call `start_session(project_id, session_name)` once. That single call:
|
|
45
|
+
- registers this session
|
|
46
|
+
- returns the current goal (north star + version goal + sprint)
|
|
47
|
+
- returns the last 10 tasks (ambient context)
|
|
48
|
+
- returns the list of other active sessions
|
|
49
|
+
- tells you whether a handoff file exists from a prior session
|
|
50
|
+
|
|
51
|
+
If a handoff exists, read it before doing anything else.
|
|
52
|
+
|
|
53
|
+
## ON SESSION END
|
|
54
|
+
|
|
55
|
+
Before you stop, call `generate_handoff` so the next session can resume cleanly.
|
|
56
|
+
Ensure any tasks you logged as `pending` are either completed or released.
|
|
57
|
+
|
|
58
|
+
## DOCS FILES
|
|
59
|
+
|
|
60
|
+
- **Never hand-write `docs/mcp-tools.md`** — it is auto-generated. Regenerate it
|
|
61
|
+
with `pixi run python -c "import asyncio; from meridian import server as srv; doc = asyncio.run(srv.mcp_tools_doc()); open('docs/mcp-tools.md','w').write(doc)"` whenever MCP tool definitions change.
|
|
62
|
+
- **Never auto-write other `docs/*.md` files** unless the sprint item explicitly
|
|
63
|
+
says to update them. Executor sessions writing docs speculatively cause the docs
|
|
64
|
+
to go backwards (replacing current content with stale or hallucinated content).
|
|
65
|
+
If a docs update is needed, file it as a separate sprint item for human review.
|
|
66
|
+
|
|
67
|
+
## DESIGN PRINCIPLES
|
|
68
|
+
|
|
69
|
+
- The version goal is **stable** — only humans (or you when explicitly directed)
|
|
70
|
+
should change it. Auto-summaries and sprint changes do not bump it.
|
|
71
|
+
- The sprint changes per cycle; the north star changes rarely.
|
|
72
|
+
- Decisions are append-only — never edit prior entries. If a decision is reversed,
|
|
73
|
+
log a new decision describing the reversal.
|
|
74
|
+
- Tasks are immutable once logged. If a task was wrong, log a corrective task.
|
|
75
|
+
|
|
76
|
+
## OVERRIDE
|
|
77
|
+
|
|
78
|
+
Drop a `MERIDIAN.md` file at your project root to replace these defaults.
|
|
79
|
+
The project-root file wins over Meridian's built-in.
|
meridian/__init__.py
ADDED
meridian/__main__.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Entrypoint for ``python -m meridian``.
|
|
2
|
+
|
|
3
|
+
Default: run the FastAPI HTTP server on port 7878. Useful for local
|
|
4
|
+
development, the demo script, and any HTTP client.
|
|
5
|
+
|
|
6
|
+
With ``--mcp``: run the MCP stdio server. This is the mode you wire into
|
|
7
|
+
Claude Desktop / Claude Code via an ``mcpServers`` config block, e.g.
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"mcpServers": {
|
|
12
|
+
"meridian": {
|
|
13
|
+
"command": "pixi",
|
|
14
|
+
"args": ["run", "python", "-m", "meridian", "--mcp"],
|
|
15
|
+
"cwd": "/absolute/path/to/meridian/repository"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import argparse
|
|
25
|
+
import asyncio
|
|
26
|
+
import os
|
|
27
|
+
import signal
|
|
28
|
+
import socket
|
|
29
|
+
import subprocess
|
|
30
|
+
import sys
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _kill_port(port: int) -> None:
|
|
34
|
+
"""Kill any process listening on the given port before starting."""
|
|
35
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
36
|
+
if s.connect_ex(("127.0.0.1", port)) != 0:
|
|
37
|
+
return # port is free
|
|
38
|
+
if sys.platform == "win32":
|
|
39
|
+
result = subprocess.run(
|
|
40
|
+
f"netstat -ano | findstr :{port}",
|
|
41
|
+
shell=True, capture_output=True, text=True,
|
|
42
|
+
)
|
|
43
|
+
for line in result.stdout.splitlines():
|
|
44
|
+
parts = line.split()
|
|
45
|
+
if parts and parts[-1].isdigit():
|
|
46
|
+
pid = int(parts[-1])
|
|
47
|
+
try:
|
|
48
|
+
os.kill(pid, signal.SIGTERM)
|
|
49
|
+
except OSError:
|
|
50
|
+
pass
|
|
51
|
+
break
|
|
52
|
+
else:
|
|
53
|
+
result = subprocess.run(
|
|
54
|
+
["lsof", "-ti", f":{port}"], capture_output=True, text=True
|
|
55
|
+
)
|
|
56
|
+
for pid_str in result.stdout.split():
|
|
57
|
+
if pid_str.isdigit():
|
|
58
|
+
try:
|
|
59
|
+
os.kill(int(pid_str), signal.SIGTERM)
|
|
60
|
+
except OSError:
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
# psycopg3 requires SelectorEventLoop on Windows (ProactorEventLoop not supported).
|
|
64
|
+
# Must be set before any asyncio.run() or uvicorn.run() call.
|
|
65
|
+
if sys.platform == "win32":
|
|
66
|
+
import selectors
|
|
67
|
+
# psycopg3 requires SelectorEventLoop — override Windows default ProactorEventLoop
|
|
68
|
+
asyncio.set_event_loop_policy(asyncio.DefaultEventLoopPolicy())
|
|
69
|
+
loop = asyncio.SelectorEventLoop(selectors.SelectSelector())
|
|
70
|
+
asyncio.set_event_loop(loop)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def main(argv: list[str] | None = None) -> int:
|
|
74
|
+
"""CLI dispatch: HTTP server by default, MCP stdio with ``--mcp``."""
|
|
75
|
+
parser = argparse.ArgumentParser(
|
|
76
|
+
prog="meridian",
|
|
77
|
+
description="Multi-session Claude coordinator.",
|
|
78
|
+
)
|
|
79
|
+
parser.add_argument(
|
|
80
|
+
"--mcp",
|
|
81
|
+
action="store_true",
|
|
82
|
+
help="Run the MCP server over stdio (for Claude Desktop / Code).",
|
|
83
|
+
)
|
|
84
|
+
parser.add_argument(
|
|
85
|
+
"--host",
|
|
86
|
+
default=os.environ.get("MERIDIAN_HOST", "127.0.0.1"),
|
|
87
|
+
help="HTTP bind host (default 127.0.0.1, override with MERIDIAN_HOST).",
|
|
88
|
+
)
|
|
89
|
+
parser.add_argument(
|
|
90
|
+
"--port",
|
|
91
|
+
type=int,
|
|
92
|
+
default=int(os.environ.get("MERIDIAN_PORT", "7878")),
|
|
93
|
+
help="HTTP port (default 7878, override with MERIDIAN_PORT).",
|
|
94
|
+
)
|
|
95
|
+
args = parser.parse_args(argv)
|
|
96
|
+
|
|
97
|
+
if args.mcp:
|
|
98
|
+
from .server import build_mcp_server
|
|
99
|
+
|
|
100
|
+
_, run_stdio = build_mcp_server()
|
|
101
|
+
# On Windows asyncio.run() creates a new ProactorEventLoop, breaking psycopg3.
|
|
102
|
+
# Use the SelectorEventLoop we set at module scope instead.
|
|
103
|
+
loop = asyncio.get_event_loop()
|
|
104
|
+
loop.run_until_complete(run_stdio())
|
|
105
|
+
return 0
|
|
106
|
+
|
|
107
|
+
import uvicorn
|
|
108
|
+
import sys as _sys
|
|
109
|
+
|
|
110
|
+
_kill_port(args.port)
|
|
111
|
+
|
|
112
|
+
if _sys.platform == "win32":
|
|
113
|
+
# psycopg3 requires SelectorEventLoop. On Windows, asyncio.run()
|
|
114
|
+
# (used by uvicorn.run()) creates ProactorEventLoop by default.
|
|
115
|
+
# Bypass asyncio.run() entirely: run uvicorn.Server on our SelectorEventLoop.
|
|
116
|
+
config = uvicorn.Config(
|
|
117
|
+
"meridian.server:app",
|
|
118
|
+
host=args.host,
|
|
119
|
+
port=args.port,
|
|
120
|
+
reload=False,
|
|
121
|
+
loop="none",
|
|
122
|
+
)
|
|
123
|
+
server = uvicorn.Server(config)
|
|
124
|
+
loop = asyncio.get_event_loop() # SelectorEventLoop set above in module scope
|
|
125
|
+
loop.run_until_complete(server.serve())
|
|
126
|
+
else:
|
|
127
|
+
uvicorn.run(
|
|
128
|
+
"meridian.server:app",
|
|
129
|
+
host=args.host,
|
|
130
|
+
port=args.port,
|
|
131
|
+
reload=False,
|
|
132
|
+
)
|
|
133
|
+
return 0
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
if __name__ == "__main__":
|
|
137
|
+
sys.exit(main())
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""PyInstaller entry point for the Meridian desktop executable.
|
|
2
|
+
|
|
3
|
+
When run as a frozen exe:
|
|
4
|
+
- DB lives in ~/.meridian/meridian.db (NOT next to the exe)
|
|
5
|
+
- Starts uvicorn on MERIDIAN_PORT (default 7700)
|
|
6
|
+
- Opens the browser after a short delay
|
|
7
|
+
- Blocks until killed
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
import time
|
|
14
|
+
import threading
|
|
15
|
+
import webbrowser
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _set_frozen_defaults() -> None:
|
|
20
|
+
"""Override default DB/data paths so they land in ~/.meridian, not next to the exe."""
|
|
21
|
+
if getattr(sys, "frozen", False):
|
|
22
|
+
home_dir = Path.home() / ".meridian"
|
|
23
|
+
home_dir.mkdir(parents=True, exist_ok=True)
|
|
24
|
+
os.environ.setdefault("MERIDIAN_DB", str(home_dir / "meridian.db"))
|
|
25
|
+
os.environ.setdefault("MERIDIAN_DATA_DIR", str(home_dir / "data"))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _open_browser(port: int, delay: float = 1.5) -> None:
|
|
29
|
+
"""Open the dashboard in the default browser after a short delay.
|
|
30
|
+
|
|
31
|
+
Opens /dashboard (where the first-run setup wizard lives) rather than
|
|
32
|
+
the landing page so binary users go straight into the app.
|
|
33
|
+
"""
|
|
34
|
+
def _open():
|
|
35
|
+
time.sleep(delay)
|
|
36
|
+
webbrowser.open(f"http://localhost:{port}/dashboard")
|
|
37
|
+
t = threading.Thread(target=_open, daemon=True)
|
|
38
|
+
t.start()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def main() -> None:
|
|
42
|
+
"""Start the Meridian server and open the dashboard."""
|
|
43
|
+
_set_frozen_defaults()
|
|
44
|
+
|
|
45
|
+
import uvicorn
|
|
46
|
+
from meridian.server import app
|
|
47
|
+
|
|
48
|
+
port = int(os.environ.get("MERIDIAN_PORT", 7700))
|
|
49
|
+
_open_browser(port)
|
|
50
|
+
uvicorn.run(app, host="0.0.0.0", port=port)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
if __name__ == "__main__":
|
|
54
|
+
main()
|