grokbook 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.
grokbook-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Marco Jeffrey Pansa
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,191 @@
1
+ Metadata-Version: 2.4
2
+ Name: grokbook
3
+ Version: 0.1.0
4
+ Summary: Interactive notebook server for learning computer science
5
+ Author-email: Marco Jeffrey Pansa <marco-jeffrey@users.noreply.github.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/marco-jeffrey/grokbook
8
+ Project-URL: Repository, https://github.com/marco-jeffrey/grokbook
9
+ Project-URL: Issues, https://github.com/marco-jeffrey/grokbook/issues
10
+ Keywords: notebook,jupyter,ipython,datastar,sse,reactive,education
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.14
14
+ Classifier: Topic :: Education
15
+ Classifier: Topic :: Scientific/Engineering
16
+ Classifier: Intended Audience :: Education
17
+ Classifier: Intended Audience :: Developers
18
+ Classifier: Framework :: Jupyter
19
+ Classifier: Environment :: Web Environment
20
+ Classifier: Operating System :: OS Independent
21
+ Requires-Python: >=3.14
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: stario==2.3.0
25
+ Requires-Dist: jupyter-client>=8.0.0
26
+ Requires-Dist: ipykernel>=6.25.0
27
+ Requires-Dist: aiosqlite>=0.20.0
28
+ Requires-Dist: watchfiles>=1.1.1
29
+ Requires-Dist: markdown-it-py[linkify,plugins]>=4.0.0
30
+ Requires-Dist: fastmcp>=3.1.1
31
+ Requires-Dist: httpx>=0.28.1
32
+ Requires-Dist: tqdm>=4.67.3
33
+ Requires-Dist: typer>=0.15.0
34
+ Dynamic: license-file
35
+
36
+ # grokbook
37
+
38
+ Interactive notebook server for learning computer science. Works like Jupyter — code cells, markdown, persistent IPython kernels — with a built-in MCP server so AI tutors can create and manage notebooks for you.
39
+
40
+ ## Install
41
+
42
+ Requires **Python 3.14+** and [uv](https://docs.astral.sh/uv/).
43
+
44
+ ```bash
45
+ git clone https://github.com/marco-jeffrey/grokbook.git
46
+ cd grokbook
47
+ uv sync
48
+ ```
49
+
50
+ ## Usage
51
+
52
+ ```bash
53
+ grokbook
54
+ ```
55
+
56
+ That's it. Opens the notebook UI on [localhost:8080](http://localhost:8080) and the MCP server on port 8081. A welcome notebook is created on first run.
57
+
58
+ ### Custom kernel environment
59
+
60
+ By default, grokbook uses its own Python for the kernel. To use a separate environment with your libraries:
61
+
62
+ ```bash
63
+ # Create an env with your packages
64
+ mkdir /tmp/my-env && cd /tmp/my-env
65
+ uv init && uv add pandas numpy matplotlib ipykernel
66
+
67
+ # Start grokbook with that env
68
+ grokbook serve --python /tmp/my-env/.venv/bin/python
69
+ ```
70
+
71
+ ### Remote access (Tailscale / LAN)
72
+
73
+ By default, grokbook binds to `127.0.0.1` (localhost only). To access from other machines:
74
+
75
+ ```bash
76
+ grokbook serve --host 0.0.0.0
77
+ ```
78
+
79
+ Both the notebook server and MCP server bind to all interfaces. Access from another machine at `http://<ip>:8080`.
80
+
81
+ > **Warning**: Grokbook executes arbitrary Python code. Do not expose it to untrusted networks.
82
+
83
+ ### CLI reference
84
+
85
+ ```
86
+ grokbook # Start everything (default)
87
+ grokbook serve [OPTIONS] # Start notebook + MCP servers
88
+ --host TEXT # Bind address (default: 127.0.0.1)
89
+ --port, -p INT # Notebook server port (default: 8080)
90
+ --mcp-port INT # MCP server port (default: 8081)
91
+ --python PATH # Python interpreter for kernels
92
+ --db PATH # Database file (default: ~/.grokbook/grokbook.db)
93
+ --allow-code-execution # Enable execute/kernel tools in MCP
94
+
95
+ grokbook mcp [OPTIONS] # MCP server standalone (stdio, for Claude Desktop)
96
+ --allow-code-execution # Enable execute/kernel tools
97
+ ```
98
+
99
+ ## MCP Integration
100
+
101
+ On startup, grokbook prints an MCP config block you can paste directly into Claude Desktop or LM Studio:
102
+
103
+ ```json
104
+ {
105
+ "mcpServers": {
106
+ "grokbook": {
107
+ "command": "grokbook",
108
+ "args": ["mcp", "--allow-code-execution"],
109
+ "env": {
110
+ "GROKBOOK_API_URL": "http://localhost:8080/api"
111
+ }
112
+ }
113
+ }
114
+ }
115
+ ```
116
+
117
+ Omit `--allow-code-execution` to restrict the MCP server to read/write operations only (no code execution).
118
+
119
+ The `grokbook mcp` command runs in stdio mode for Claude Desktop. For HTTP-based MCP clients (LM Studio, remote agents), the built-in MCP server on port 8081 is already running when you start `grokbook serve`.
120
+
121
+ **Always available**: `list_notebooks`, `get_notebook`, `create_notebook`, `rename_notebook`, `duplicate_notebook`, `list_projects`, `create_project`, `rename_project`, `move_notebook`, `create_cell`, `insert_cell`, `read_cell`, `write_cell`, `delete_cell`, `move_cell`, `duplicate_cell`, `change_cell_type`, `clear_output`, `clear_all_outputs`
122
+
123
+ **With `--allow-code-execution`**: `execute_cell`, `run_all_cells`, `kernel_status`, `restart_kernel`, `get_variables`, `interrupt_kernel`
124
+
125
+ To enable code execution via MCP:
126
+
127
+ ```bash
128
+ grokbook serve --allow-code-execution
129
+ ```
130
+
131
+ ## Features
132
+
133
+ - **Code cells** with streaming execution, rich output (images, HTML, SVG, pandas tables)
134
+ - **Markdown cells** with GitHub-flavored rendering
135
+ - **Persistent IPython kernels** — one per notebook, variables carry over between cells
136
+ - **Keyboard-driven** — Vim-like command/edit modes (j/k, a/b, dd, Shift+Enter)
137
+ - **Import/export** Jupyter `.ipynb` files
138
+ - **Variables inspector** panel
139
+ - **Dark/light theme**, wide mode, autocomplete, signature tooltips
140
+ - **Live sync** across browser tabs via SSE
141
+
142
+ ## Keyboard Shortcuts
143
+
144
+ Grokbook uses two modes, inspired by Vim:
145
+
146
+ **Command mode** (press `Escape` to enter):
147
+
148
+ | Key | Action |
149
+ |-----|--------|
150
+ | `j` / `k` | Navigate between cells |
151
+ | `Enter` | Edit selected cell |
152
+ | `a` / `b` | Insert cell above / below |
153
+ | `m` | Convert to markdown |
154
+ | `y` | Convert to code |
155
+ | `dd` | Delete cell |
156
+ | `Cmd+Shift+Up/Down` | Move cell up / down |
157
+
158
+ **Edit mode** (press `Enter` or click a cell):
159
+
160
+ | Key | Action |
161
+ |-----|--------|
162
+ | `Shift+Enter` | Execute cell, move to next |
163
+ | `Cmd+Enter` / `Ctrl+Enter` | Execute cell, stay in place |
164
+ | `Escape` | Back to command mode |
165
+ | `Tab` / `Shift+Tab` | Indent / dedent |
166
+
167
+ ### Vim mode
168
+
169
+ Enable Vim keybindings from the editor settings panel (gear icon). When active:
170
+
171
+ - Full Vim motions in code cells (normal, insert, visual modes)
172
+ - `jk` is mapped to `Escape` in insert mode for quick mode switching
173
+ - Block cursor in normal mode, line cursor in insert mode
174
+
175
+ ## Architecture
176
+
177
+ ```
178
+ Browser ──SSE──▶ Stario server (:8080) ──ZMQ──▶ IPython kernel
179
+ │ │
180
+ │ Datastar │ SQLite (~/.grokbook/grokbook.db)
181
+ │ (reactive │
182
+ │ signals) ├── REST API (/api)
183
+ │ │
184
+ ▼ ▼
185
+ DOM patches MCP server (:8081)
186
+ via SSE (FastMCP, for LLM agents)
187
+ ```
188
+
189
+ ## License
190
+
191
+ MIT
@@ -0,0 +1,156 @@
1
+ # grokbook
2
+
3
+ Interactive notebook server for learning computer science. Works like Jupyter — code cells, markdown, persistent IPython kernels — with a built-in MCP server so AI tutors can create and manage notebooks for you.
4
+
5
+ ## Install
6
+
7
+ Requires **Python 3.14+** and [uv](https://docs.astral.sh/uv/).
8
+
9
+ ```bash
10
+ git clone https://github.com/marco-jeffrey/grokbook.git
11
+ cd grokbook
12
+ uv sync
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```bash
18
+ grokbook
19
+ ```
20
+
21
+ That's it. Opens the notebook UI on [localhost:8080](http://localhost:8080) and the MCP server on port 8081. A welcome notebook is created on first run.
22
+
23
+ ### Custom kernel environment
24
+
25
+ By default, grokbook uses its own Python for the kernel. To use a separate environment with your libraries:
26
+
27
+ ```bash
28
+ # Create an env with your packages
29
+ mkdir /tmp/my-env && cd /tmp/my-env
30
+ uv init && uv add pandas numpy matplotlib ipykernel
31
+
32
+ # Start grokbook with that env
33
+ grokbook serve --python /tmp/my-env/.venv/bin/python
34
+ ```
35
+
36
+ ### Remote access (Tailscale / LAN)
37
+
38
+ By default, grokbook binds to `127.0.0.1` (localhost only). To access from other machines:
39
+
40
+ ```bash
41
+ grokbook serve --host 0.0.0.0
42
+ ```
43
+
44
+ Both the notebook server and MCP server bind to all interfaces. Access from another machine at `http://<ip>:8080`.
45
+
46
+ > **Warning**: Grokbook executes arbitrary Python code. Do not expose it to untrusted networks.
47
+
48
+ ### CLI reference
49
+
50
+ ```
51
+ grokbook # Start everything (default)
52
+ grokbook serve [OPTIONS] # Start notebook + MCP servers
53
+ --host TEXT # Bind address (default: 127.0.0.1)
54
+ --port, -p INT # Notebook server port (default: 8080)
55
+ --mcp-port INT # MCP server port (default: 8081)
56
+ --python PATH # Python interpreter for kernels
57
+ --db PATH # Database file (default: ~/.grokbook/grokbook.db)
58
+ --allow-code-execution # Enable execute/kernel tools in MCP
59
+
60
+ grokbook mcp [OPTIONS] # MCP server standalone (stdio, for Claude Desktop)
61
+ --allow-code-execution # Enable execute/kernel tools
62
+ ```
63
+
64
+ ## MCP Integration
65
+
66
+ On startup, grokbook prints an MCP config block you can paste directly into Claude Desktop or LM Studio:
67
+
68
+ ```json
69
+ {
70
+ "mcpServers": {
71
+ "grokbook": {
72
+ "command": "grokbook",
73
+ "args": ["mcp", "--allow-code-execution"],
74
+ "env": {
75
+ "GROKBOOK_API_URL": "http://localhost:8080/api"
76
+ }
77
+ }
78
+ }
79
+ }
80
+ ```
81
+
82
+ Omit `--allow-code-execution` to restrict the MCP server to read/write operations only (no code execution).
83
+
84
+ The `grokbook mcp` command runs in stdio mode for Claude Desktop. For HTTP-based MCP clients (LM Studio, remote agents), the built-in MCP server on port 8081 is already running when you start `grokbook serve`.
85
+
86
+ **Always available**: `list_notebooks`, `get_notebook`, `create_notebook`, `rename_notebook`, `duplicate_notebook`, `list_projects`, `create_project`, `rename_project`, `move_notebook`, `create_cell`, `insert_cell`, `read_cell`, `write_cell`, `delete_cell`, `move_cell`, `duplicate_cell`, `change_cell_type`, `clear_output`, `clear_all_outputs`
87
+
88
+ **With `--allow-code-execution`**: `execute_cell`, `run_all_cells`, `kernel_status`, `restart_kernel`, `get_variables`, `interrupt_kernel`
89
+
90
+ To enable code execution via MCP:
91
+
92
+ ```bash
93
+ grokbook serve --allow-code-execution
94
+ ```
95
+
96
+ ## Features
97
+
98
+ - **Code cells** with streaming execution, rich output (images, HTML, SVG, pandas tables)
99
+ - **Markdown cells** with GitHub-flavored rendering
100
+ - **Persistent IPython kernels** — one per notebook, variables carry over between cells
101
+ - **Keyboard-driven** — Vim-like command/edit modes (j/k, a/b, dd, Shift+Enter)
102
+ - **Import/export** Jupyter `.ipynb` files
103
+ - **Variables inspector** panel
104
+ - **Dark/light theme**, wide mode, autocomplete, signature tooltips
105
+ - **Live sync** across browser tabs via SSE
106
+
107
+ ## Keyboard Shortcuts
108
+
109
+ Grokbook uses two modes, inspired by Vim:
110
+
111
+ **Command mode** (press `Escape` to enter):
112
+
113
+ | Key | Action |
114
+ |-----|--------|
115
+ | `j` / `k` | Navigate between cells |
116
+ | `Enter` | Edit selected cell |
117
+ | `a` / `b` | Insert cell above / below |
118
+ | `m` | Convert to markdown |
119
+ | `y` | Convert to code |
120
+ | `dd` | Delete cell |
121
+ | `Cmd+Shift+Up/Down` | Move cell up / down |
122
+
123
+ **Edit mode** (press `Enter` or click a cell):
124
+
125
+ | Key | Action |
126
+ |-----|--------|
127
+ | `Shift+Enter` | Execute cell, move to next |
128
+ | `Cmd+Enter` / `Ctrl+Enter` | Execute cell, stay in place |
129
+ | `Escape` | Back to command mode |
130
+ | `Tab` / `Shift+Tab` | Indent / dedent |
131
+
132
+ ### Vim mode
133
+
134
+ Enable Vim keybindings from the editor settings panel (gear icon). When active:
135
+
136
+ - Full Vim motions in code cells (normal, insert, visual modes)
137
+ - `jk` is mapped to `Escape` in insert mode for quick mode switching
138
+ - Block cursor in normal mode, line cursor in insert mode
139
+
140
+ ## Architecture
141
+
142
+ ```
143
+ Browser ──SSE──▶ Stario server (:8080) ──ZMQ──▶ IPython kernel
144
+ │ │
145
+ │ Datastar │ SQLite (~/.grokbook/grokbook.db)
146
+ │ (reactive │
147
+ │ signals) ├── REST API (/api)
148
+ │ │
149
+ ▼ ▼
150
+ DOM patches MCP server (:8081)
151
+ via SSE (FastMCP, for LLM agents)
152
+ ```
153
+
154
+ ## License
155
+
156
+ MIT
File without changes
@@ -0,0 +1,5 @@
1
+ """Allow running as `python -m grokbook`."""
2
+
3
+ from grokbook.cli import app
4
+
5
+ app()
@@ -0,0 +1,109 @@
1
+ """Server bootstrap and runner — shared by main.py (dev) and cli.py (production)."""
2
+
3
+ import asyncio
4
+ import os
5
+ from contextlib import asynccontextmanager
6
+ from pathlib import Path
7
+
8
+ from stario import Relay, RichTracer, Stario
9
+ from stario.http.server import Server
10
+ from stario.http.writer import CompressionConfig
11
+ from stario.telemetry.core import Span
12
+
13
+ from grokbook.api import api_router
14
+ from grokbook.db import Database
15
+ from grokbook import envs
16
+ from grokbook.handlers import app_router
17
+ from grokbook.kernel import KernelPool
18
+
19
+
20
+ def make_bootstrap(db_path: Path, python_path: str | None = None):
21
+ @asynccontextmanager
22
+ async def bootstrap(app: Stario, span: Span):
23
+ nonlocal python_path
24
+ db = await Database.connect(str(db_path))
25
+
26
+ # Create welcome notebook on first run
27
+ from grokbook.welcome import ensure_welcome_notebook
28
+
29
+ await ensure_welcome_notebook(db)
30
+
31
+ # Discover available Python environments (uv + kernelspecs + cwd .venv)
32
+ await envs.refresh()
33
+
34
+ # Auto-pick a default interpreter if --python wasn't supplied
35
+ if python_path is None:
36
+ default_env = await envs.pick_default(cwd=Path.cwd())
37
+ if default_env is not None:
38
+ python_path = default_env.path
39
+ if not default_env.has_ipykernel:
40
+ print(f"Installing ipykernel into {default_env.name}…")
41
+ ok = False
42
+ async for kind, line in envs.install_ipykernel(default_env.path):
43
+ if kind == "done":
44
+ ok = line == "ok"
45
+ if not ok:
46
+ print(f" WARNING: {line}")
47
+ else:
48
+ print(f" {line}")
49
+ if ok:
50
+ await envs.refresh()
51
+ else:
52
+ print(" Kernel will start, but ipykernel import may fail.")
53
+ print(f"Using kernel: {default_env.name} ({default_env.path})")
54
+
55
+ pool = KernelPool(default_python_path=python_path)
56
+ relay: Relay[str] = Relay()
57
+
58
+ static_dir = Path(__file__).parent / "static"
59
+ app.assets("/static", static_dir, name="static")
60
+
61
+ app.mount("/api", api_router(db, pool, relay))
62
+ app.mount("/", app_router(db, pool, relay))
63
+
64
+ try:
65
+ yield
66
+ finally:
67
+ await pool.shutdown_all()
68
+ await db.close()
69
+
70
+ return bootstrap
71
+
72
+
73
+ def run_server(
74
+ host: str = "127.0.0.1",
75
+ port: int = 8080,
76
+ db_path: Path = Path("nb.db"),
77
+ python_path: str | None = None,
78
+ mcp_host: str | None = None,
79
+ mcp_port: int = 8081,
80
+ ) -> None:
81
+ """Start the grokbook server, optionally with MCP server. Blocks until Ctrl+C."""
82
+
83
+ async def _run() -> None:
84
+ server = Server(
85
+ make_bootstrap(db_path, python_path),
86
+ RichTracer(),
87
+ host=host,
88
+ port=port,
89
+ compression=CompressionConfig(zstd_level=-1),
90
+ )
91
+
92
+ if mcp_host is not None:
93
+ # Run both notebook server and MCP HTTP server concurrently
94
+ os.environ["GROKBOOK_API_URL"] = f"http://127.0.0.1:{port}/api"
95
+ from grokbook.mcp_server import mcp
96
+
97
+ await asyncio.gather(
98
+ server.run(),
99
+ mcp.run_async(
100
+ "streamable-http",
101
+ host=mcp_host,
102
+ port=mcp_port,
103
+ show_banner=False,
104
+ ),
105
+ )
106
+ else:
107
+ await server.run()
108
+
109
+ asyncio.run(_run())
@@ -0,0 +1,80 @@
1
+ """Convert ANSI escape sequences to HTML spans."""
2
+
3
+ import re
4
+
5
+ _ANSI_RE = re.compile(r"\x1b\[([0-9;]*)m")
6
+
7
+ _FG_COLORS = {
8
+ 30: "#6e7681", 31: "#ff7b72", 32: "#7ee787", 33: "#d29922",
9
+ 34: "#79c0ff", 35: "#d2a8ff", 36: "#a5d6ff", 37: "#c9d1d9",
10
+ 90: "#8b949e", 91: "#ffa198", 92: "#9be9a8", 93: "#e3b341",
11
+ 94: "#a5d6ff", 95: "#d2a8ff", 96: "#b6e3ff", 97: "#f0f6fc",
12
+ }
13
+
14
+ _BG_COLORS = {
15
+ 40: "#6e7681", 41: "#ff7b72", 42: "#7ee787", 43: "#d29922",
16
+ 44: "#79c0ff", 45: "#d2a8ff", 46: "#a5d6ff", 47: "#c9d1d9",
17
+ 100: "#8b949e", 101: "#ffa198", 102: "#9be9a8", 103: "#e3b341",
18
+ 104: "#a5d6ff", 105: "#d2a8ff", 106: "#b6e3ff", 107: "#f0f6fc",
19
+ }
20
+
21
+
22
+ def ansi_to_html(text: str) -> str:
23
+ """Convert ANSI escape sequences in text to HTML with inline styles.
24
+
25
+ Returns HTML string with <span style="..."> tags. The output is
26
+ intended to be wrapped in SafeString() for rendering.
27
+ All non-ANSI text is HTML-escaped.
28
+ """
29
+ import html as _html
30
+
31
+ result = []
32
+ pos = 0
33
+ span_open = False
34
+ current_styles: dict[str, str] = {}
35
+
36
+ for match in _ANSI_RE.finditer(text):
37
+ # Append text before this escape sequence (HTML-escaped)
38
+ if match.start() > pos:
39
+ result.append(_html.escape(text[pos:match.start()]))
40
+ pos = match.end()
41
+
42
+ codes_str = match.group(1)
43
+ if not codes_str:
44
+ codes = [0]
45
+ else:
46
+ codes = [int(c) for c in codes_str.split(";") if c]
47
+
48
+ for code in codes:
49
+ if code == 0:
50
+ current_styles.clear()
51
+ elif code == 1:
52
+ current_styles["font-weight"] = "bold"
53
+ elif code == 3:
54
+ current_styles["font-style"] = "italic"
55
+ elif code == 4:
56
+ current_styles["text-decoration"] = "underline"
57
+ elif code in _FG_COLORS:
58
+ current_styles["color"] = _FG_COLORS[code]
59
+ elif code in _BG_COLORS:
60
+ current_styles["background-color"] = _BG_COLORS[code]
61
+
62
+ # Close previous span if open
63
+ if span_open:
64
+ result.append("</span>")
65
+ span_open = False
66
+
67
+ # Open new span if we have styles
68
+ if current_styles:
69
+ style = ";".join(f"{k}:{v}" for k, v in current_styles.items())
70
+ result.append(f'<span style="{style}">')
71
+ span_open = True
72
+
73
+ # Append remaining text
74
+ if pos < len(text):
75
+ result.append(_html.escape(text[pos:]))
76
+
77
+ if span_open:
78
+ result.append("</span>")
79
+
80
+ return "".join(result)