clikernel 0.2.15__tar.gz → 0.2.16__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.
- {clikernel-0.2.15 → clikernel-0.2.16}/CHANGELOG.md +9 -0
- {clikernel-0.2.15/clikernel.egg-info → clikernel-0.2.16}/PKG-INFO +5 -3
- {clikernel-0.2.15 → clikernel-0.2.16}/README.md +3 -1
- {clikernel-0.2.15 → clikernel-0.2.16}/clikernel/__init__.py +2 -2
- {clikernel-0.2.15 → clikernel-0.2.16}/clikernel/_modidx.py +5 -1
- {clikernel-0.2.15 → clikernel-0.2.16}/clikernel/core.py +17 -4
- {clikernel-0.2.15 → clikernel-0.2.16}/clikernel/mcp.py +29 -5
- {clikernel-0.2.15 → clikernel-0.2.16}/clikernel/rules.py +1 -1
- clikernel-0.2.16/clikernel/skill.py +24 -0
- {clikernel-0.2.15 → clikernel-0.2.16/clikernel.egg-info}/PKG-INFO +5 -3
- {clikernel-0.2.15 → clikernel-0.2.16}/clikernel.egg-info/entry_points.txt +1 -0
- {clikernel-0.2.15 → clikernel-0.2.16}/clikernel.egg-info/requires.txt +1 -1
- {clikernel-0.2.15 → clikernel-0.2.16}/pyproject.toml +2 -1
- {clikernel-0.2.15 → clikernel-0.2.16}/tests/test_rules.py +2 -2
- clikernel-0.2.15/clikernel/skill.py +0 -29
- {clikernel-0.2.15 → clikernel-0.2.16}/LICENSE +0 -0
- {clikernel-0.2.15 → clikernel-0.2.16}/MANIFEST.in +0 -0
- {clikernel-0.2.15 → clikernel-0.2.16}/clikernel/cli.py +0 -0
- {clikernel-0.2.15 → clikernel-0.2.16}/clikernel/stream.py +0 -0
- {clikernel-0.2.15 → clikernel-0.2.16}/clikernel.egg-info/SOURCES.txt +0 -0
- {clikernel-0.2.15 → clikernel-0.2.16}/clikernel.egg-info/dependency_links.txt +0 -0
- {clikernel-0.2.15 → clikernel-0.2.16}/clikernel.egg-info/top_level.txt +0 -0
- {clikernel-0.2.15 → clikernel-0.2.16}/setup.cfg +0 -0
- {clikernel-0.2.15 → clikernel-0.2.16}/tests/test_stream.py +0 -0
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- do not remove -->
|
|
4
4
|
|
|
5
|
+
## 0.2.16
|
|
6
|
+
|
|
7
|
+
### New Features
|
|
8
|
+
|
|
9
|
+
- Add clikernel-hook PreToolUse hook giving Claude Code subagents their own kernels ([#57](https://github.com/AnswerDotAI/clikernel/issues/57))
|
|
10
|
+
- Display only top-level expression results in Python kernels and cap total image data per tool result at 5 MB to avoid stdio disconnects ([#56](https://github.com/AnswerDotAI/clikernel/issues/56))
|
|
11
|
+
- Switch to BPL ([#55](https://github.com/AnswerDotAI/clikernel/issues/55))
|
|
12
|
+
|
|
13
|
+
|
|
5
14
|
## 0.2.15
|
|
6
15
|
|
|
7
16
|
### New Features
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clikernel
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.16
|
|
4
4
|
Summary: Serve persistent Jupyter kernels to LLMs as concise text, over MCP or a plain stream protocol
|
|
5
5
|
Author: clikernel contributors
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -15,7 +15,7 @@ Requires-Dist: fastcore>=2.2.2
|
|
|
15
15
|
Requires-Dist: jupyasyncclient>=0.2.10
|
|
16
16
|
Requires-Dist: jupywire>=0.1.9
|
|
17
17
|
Requires-Dist: mcpmini>=0.0.5
|
|
18
|
-
Requires-Dist: rustygate>=0.1.
|
|
18
|
+
Requires-Dist: rustygate>=0.1.25
|
|
19
19
|
Requires-Dist: ipymini>=0.1.23
|
|
20
20
|
Requires-Dist: httpx
|
|
21
21
|
Requires-Dist: ipython
|
|
@@ -61,7 +61,9 @@ create(kernel="py")
|
|
|
61
61
|
exec(code="6*7")
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
Use `kernel="luau"` for bundled Luau, or `kernel="
|
|
64
|
+
Use `kernel="luau"` for bundled Luau, or `kernel="bpl"` for BPL when `basedpl` is installed in the gateway’s environment. `create` accepts any kernelspec the gateway finds. In the main conversation, `exec` never creates or switches kernels. See [MCP](https://AnswerDotAI.github.io/clikernel/mcp.html) for named gateways, dialog bindings, and other tools.
|
|
65
|
+
|
|
66
|
+
In Claude Code, subagents share the current kernel unless `clikernel-hook` is registered as a `PreToolUse` hook. With the hook, each subagent’s first `exec` creates a kernel of its own. See [Subagent kernels](https://AnswerDotAI.github.io/clikernel/mcp.html#subagent-kernels) for the settings entry.
|
|
65
67
|
|
|
66
68
|
The router checks each `exec` cell against built-in rules for mistakes agents often make with this toolkit. Most rules add a note to the reply, and `--quiet` leaves the notes out. Two rules always block the cell: shell access through `subprocess`, [`os.system`](https://docs.python.org/3/library/os.html#os.system) or [`os.popen`](https://docs.python.org/3/library/os.html#os.popen), and changes to `sys.path`.
|
|
67
69
|
|
|
@@ -36,7 +36,9 @@ create(kernel="py")
|
|
|
36
36
|
exec(code="6*7")
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
Use `kernel="luau"` for bundled Luau, or `kernel="
|
|
39
|
+
Use `kernel="luau"` for bundled Luau, or `kernel="bpl"` for BPL when `basedpl` is installed in the gateway’s environment. `create` accepts any kernelspec the gateway finds. In the main conversation, `exec` never creates or switches kernels. See [MCP](https://AnswerDotAI.github.io/clikernel/mcp.html) for named gateways, dialog bindings, and other tools.
|
|
40
|
+
|
|
41
|
+
In Claude Code, subagents share the current kernel unless `clikernel-hook` is registered as a `PreToolUse` hook. With the hook, each subagent’s first `exec` creates a kernel of its own. See [Subagent kernels](https://AnswerDotAI.github.io/clikernel/mcp.html#subagent-kernels) for the settings entry.
|
|
40
42
|
|
|
41
43
|
The router checks each `exec` cell against built-in rules for mistakes agents often make with this toolkit. Most rules add a note to the reply, and `--quiet` leaves the notes out. Two rules always block the cell: shell access through `subprocess`, [`os.system`](https://docs.python.org/3/library/os.html#os.system) or [`os.popen`](https://docs.python.org/3/library/os.html#os.popen), and changes to `sys.path`.
|
|
42
44
|
|
|
@@ -5,7 +5,7 @@ Modules:
|
|
|
5
5
|
- `clikernel.cli`: The stream-protocol frontend: the service on stdin/stdout for token-reading clients
|
|
6
6
|
- `clikernel.core`: Gateway naming, startup delivery, and MCP sessions on rustygate gateways
|
|
7
7
|
- `clikernel.mcp`: Route stdio MCP requests to rustygate gateways
|
|
8
|
-
- `clikernel.skill`: Use
|
|
8
|
+
- `clikernel.skill`: Use `clikernel` MCP as the default persistent Python workspace for file/notebook reads and edits, exploration, library checks, data reshaping and debugging. Imports and variables carry across calls. Read before writing, running or debugging Python with `clikernel` connected.
|
|
9
9
|
- `clikernel.stream`: Streaming JSON-lines worker protocol: nbformat-shaped output events, and a supervisor for select-based UIs."""
|
|
10
10
|
|
|
11
|
-
__version__ = "0.2.
|
|
11
|
+
__version__ = "0.2.16"
|
|
@@ -23,7 +23,8 @@ d = { 'settings': { 'branch': 'main',
|
|
|
23
23
|
'clikernel.core.Gateway.rpc': ('core.html#gateway.rpc', 'clikernel/core.py'),
|
|
24
24
|
'clikernel.core.Gateway.text': ('core.html#gateway.text', 'clikernel/core.py'),
|
|
25
25
|
'clikernel.core.Gateway.tools': ('core.html#gateway.tools', 'clikernel/core.py'),
|
|
26
|
-
'clikernel.core.
|
|
26
|
+
'clikernel.core._top_level_display': ('core.html#_top_level_display', 'clikernel/core.py'),
|
|
27
|
+
'clikernel.core._with_file': ('core.html#_with_file', 'clikernel/core.py'),
|
|
27
28
|
'clikernel.core.cfg_dir': ('core.html#cfg_dir', 'clikernel/core.py'),
|
|
28
29
|
'clikernel.core.default_gateway': ('core.html#default_gateway', 'clikernel/core.py'),
|
|
29
30
|
'clikernel.core.gateways': ('core.html#gateways', 'clikernel/core.py'),
|
|
@@ -36,4 +37,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
36
37
|
'clikernel.mcp.Router.dispatch': ('mcp.html#router.dispatch', 'clikernel/mcp.py'),
|
|
37
38
|
'clikernel.mcp.Router.session': ('mcp.html#router.session', 'clikernel/mcp.py'),
|
|
38
39
|
'clikernel.mcp.Router.tools': ('mcp.html#router.tools', 'clikernel/mcp.py'),
|
|
40
|
+
'clikernel.mcp._cap_images': ('mcp.html#_cap_images', 'clikernel/mcp.py'),
|
|
41
|
+
'clikernel.mcp.agent_input': ('mcp.html#agent_input', 'clikernel/mcp.py'),
|
|
42
|
+
'clikernel.mcp.hook': ('mcp.html#hook', 'clikernel/mcp.py'),
|
|
39
43
|
'clikernel.mcp.main': ('mcp.html#main', 'clikernel/mcp.py')}}}
|
|
@@ -16,7 +16,7 @@ Docs: https://AnswerDotAI.github.io/clikernel/core.html.md"""
|
|
|
16
16
|
__all__ = ['DEFAULT_URL', 'cfg_dir', 'gateways', 'resolve', 'startup_src', 'session_defaults', 'Gateway', 'default_gateway']
|
|
17
17
|
|
|
18
18
|
# %% ../nbs/00_core.ipynb #2b3b7f4a
|
|
19
|
-
import os, tomllib, httpx
|
|
19
|
+
import os, inspect, tomllib, httpx
|
|
20
20
|
from fastcore.utils import *
|
|
21
21
|
from fastcore.xdg import xdg_config_home
|
|
22
22
|
from mcpmini.core import HTTPTransport, jreq
|
|
@@ -46,18 +46,31 @@ def resolve(host='', cfgdir=None):
|
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
# %% ../nbs/00_core.ipynb #0d846754
|
|
49
|
-
def
|
|
49
|
+
def _with_file(src, path):
|
|
50
50
|
"Wrap startup source with `__file__` set to its path during execution and deleted afterwards."
|
|
51
51
|
return f'''__file__ = {str(path)!r}
|
|
52
52
|
try: exec(compile({src!r}, __file__, 'exec'))
|
|
53
53
|
finally: del __file__'''
|
|
54
54
|
|
|
55
|
+
# %% ../nbs/00_core.ipynb #b0dd40c1
|
|
56
|
+
def _top_level_display(shell):
|
|
57
|
+
"Make `ast_node_interactivity='all'` display top-level expressions only."
|
|
58
|
+
import ast
|
|
59
|
+
run = shell.run_ast_nodes
|
|
60
|
+
async def run_ast_nodes(nodelist, cell_name, interactivity='last_expr', **kwargs):
|
|
61
|
+
if interactivity != 'all': return await run(nodelist, cell_name, interactivity, **kwargs)
|
|
62
|
+
for n in nodelist:
|
|
63
|
+
if await run([n], cell_name, 'last' if isinstance(n, ast.Expr) else 'none', **kwargs): return True
|
|
64
|
+
return False
|
|
65
|
+
shell.run_ast_nodes = run_ast_nodes
|
|
66
|
+
shell.ast_node_interactivity = 'all'
|
|
67
|
+
|
|
55
68
|
# %% ../nbs/00_core.ipynb #1362e6ce
|
|
56
69
|
def startup_src(cfgdir=None):
|
|
57
70
|
"Build Python defaults and user startup, in that order."
|
|
58
71
|
d = Path(cfgdir) if cfgdir else cfg_dir()
|
|
59
|
-
parts = [
|
|
60
|
-
if (p := d/'startup.py').exists(): parts.append(
|
|
72
|
+
parts = [inspect.getsource(_top_level_display) + '_top_level_display(get_ipython())\ndel _top_level_display']
|
|
73
|
+
if (p := d/'startup.py').exists(): parts.append(_with_file(p.read_text(), p))
|
|
61
74
|
return '\n'.join(parts)
|
|
62
75
|
|
|
63
76
|
def session_defaults(cfgdir=None, quiet=False, local=True):
|
|
@@ -13,10 +13,10 @@ Docs: https://AnswerDotAI.github.io/clikernel/mcp.html.md"""
|
|
|
13
13
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/01_mcp.ipynb.
|
|
14
14
|
|
|
15
15
|
# %% auto #0
|
|
16
|
-
__all__ = ['HOST_PARAM', 'HOSTED', 'Router', 'main']
|
|
16
|
+
__all__ = ['HOST_PARAM', 'HOSTED', 'Router', 'main', 'agent_input', 'hook']
|
|
17
17
|
|
|
18
18
|
# %% ../nbs/01_mcp.ipynb #28c42902
|
|
19
|
-
import asyncio, httpx
|
|
19
|
+
import asyncio, httpx, json
|
|
20
20
|
from fastcore.utils import *
|
|
21
21
|
from fastcore.script import call_parse, store_true
|
|
22
22
|
from mcpmini.core import serve_stdio, jresp, jerr
|
|
@@ -55,6 +55,17 @@ async def tools(self:Router):
|
|
|
55
55
|
if t['name'] in HOSTED: t['inputSchema'].setdefault('properties', {})['host'] = dict(HOST_PARAM)
|
|
56
56
|
return ts
|
|
57
57
|
|
|
58
|
+
# %% ../nbs/01_mcp.ipynb #6da913bf
|
|
59
|
+
def _cap_images(content, budget=5 << 20):
|
|
60
|
+
"`content`, with each image that would take the total base64 image data past `budget` bytes replaced by a line of text."
|
|
61
|
+
left,res = budget,[]
|
|
62
|
+
for b in content:
|
|
63
|
+
if b['type'] == 'image':
|
|
64
|
+
if (n := len(b['data'])) > left: b = dict(type='text', text=f"[{b['mimeType']} image of {n:,} bytes omitted: images in one reply are limited to {budget:,} bytes]")
|
|
65
|
+
else: left -= n
|
|
66
|
+
res.append(b)
|
|
67
|
+
return res
|
|
68
|
+
|
|
58
69
|
# %% ../nbs/01_mcp.ipynb #e1b057dd
|
|
59
70
|
@patch
|
|
60
71
|
async def dispatch(self:Router, msg, requester=None):
|
|
@@ -83,10 +94,11 @@ async def dispatch(self:Router, msg, requester=None):
|
|
|
83
94
|
try: res = await s.tr.send(msg)
|
|
84
95
|
except httpx.HTTPStatusError as e:
|
|
85
96
|
if e.response.status_code != 404: raise
|
|
86
|
-
del self.sessions[h]
|
|
97
|
+
del self.sessions[h]
|
|
87
98
|
res = await (await self.session(h)).tr.send(msg)
|
|
88
|
-
if
|
|
89
|
-
res['result']['content']
|
|
99
|
+
if 'result' in res:
|
|
100
|
+
content = res['result']['content'] = _cap_images(res['result']['content'])
|
|
101
|
+
if fired and not self.quiet: content.insert(0, dict(type='text', text=''.join(f'<note>\n{r.note}\n</note>\n' for r in fired)))
|
|
90
102
|
return res
|
|
91
103
|
return jerr(id, -32601, f'method not found: {method}')
|
|
92
104
|
except Exception as e: return None if id is None else jerr(id, -32603, str(e))
|
|
@@ -114,3 +126,15 @@ def main(
|
|
|
114
126
|
finally: await router.aclose()
|
|
115
127
|
asyncio.run(_main())
|
|
116
128
|
|
|
129
|
+
|
|
130
|
+
# %% ../nbs/01_mcp.ipynb #bc8afe8c
|
|
131
|
+
def agent_input(
|
|
132
|
+
event:dict, # A Claude Code `PreToolUse` hook event
|
|
133
|
+
):
|
|
134
|
+
"`event`'s tool input with `agent` set to the calling subagent's id, or None for a call from the main conversation."
|
|
135
|
+
if aid := event.get('agent_id'): return event['tool_input'] | dict(agent=aid)
|
|
136
|
+
|
|
137
|
+
# %% ../nbs/01_mcp.ipynb #105e6780
|
|
138
|
+
def hook():
|
|
139
|
+
"Run `clikernel-hook`: read a `PreToolUse` event on stdin, and print the `updatedInput` that gives a subagent its own kernel."
|
|
140
|
+
if upd := agent_input(json.load(sys.stdin)): print(json.dumps(dict(hookSpecificOutput=dict(hookEventName='PreToolUse', updatedInput=upd))))
|
|
@@ -139,7 +139,7 @@ def _sys_path(tree, src):
|
|
|
139
139
|
|
|
140
140
|
|
|
141
141
|
RULES = [
|
|
142
|
-
Rule('read_file', 'Read files with lnhashview_file (for editing) or view_file(nums=False).', _read_file),
|
|
142
|
+
Rule('read_file', 'Read files with open_doc (large), lnhashview_file (for editing) or view_file(nums=False).', _read_file),
|
|
143
143
|
Rule('big_replace', 'Replace a whole cell or file with %%exhash <path> [<cell_id>] % c; an inner region with a range-c address.', _big_replace),
|
|
144
144
|
Rule('cell_str_replace', 'Edit notebook cells with %%exhash <path> <cell_id>.', _cell_str_replace),
|
|
145
145
|
Rule('rawstr', 'Write non-trivial strings as r""" raw strings; %%exhash text needs no escaping at all.', _rawstr),
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Use `clikernel` MCP as the default persistent Python workspace for file/notebook reads and edits, exploration, library checks, data reshaping and debugging. Imports and variables carry across calls. Read before writing, running or debugging Python with `clikernel` connected.
|
|
2
|
+
|
|
3
|
+
Prefer it to one-off scripts (`python -c`, shell heredocs) and installed in-kernel pyskills to shell equivalents: `rgapi` (`rg()`/`fd()`/`ls()`) for search/listing; `ghapi` for GitHub/local git. Shell stays right for project tests/builds and non-Python tools.
|
|
4
|
+
|
|
5
|
+
MCP tool descriptions give parameters/rules; this guide combines calls.
|
|
6
|
+
|
|
7
|
+
`create(kernel="py")` first; read its reply's startup imports and next steps before running anything. Kernels this conversation creates stop at its end. For one that should outlive it, `create(kernel="py", dlgname="work", autoclose=false)` and tell the user its id. It needs a gateway that keeps running; say so if the reply shows a conversation-started gateway.
|
|
8
|
+
|
|
9
|
+
To continue earlier work or use the user's solveit kernel: `list_kernels`, then `use_kernel` with the id. Attach runs no setup, claims no ownership and never stops that live kernel for you.
|
|
10
|
+
|
|
11
|
+
`restart` reruns Python startup only in kernels this conversation created; redo everything else. `interrupt` stops a long `exec`, keeping state. A kernel-gone reply requires `create` or `use_kernel` before executing again.
|
|
12
|
+
|
|
13
|
+
Remote gateways are named in `~/.config/clikernel/gateways.toml`. Same tools/rules: `host=` on `list_kernels`/`use_kernel`/`create`; plain `exec` follows the selected host until the next selection.
|
|
14
|
+
|
|
15
|
+
`create(kernel="luau")`/`create(kernel="bpl")`: same `exec`/autoclose rules, no Python startup/magics. Each creation reply gives language basics. Luau's `exec(code="help()")` gives the full native API; `help("ex.edit_file")` covers one function. BPL needs `basedpl` in the gateway environment for its `bpl` kernelspec.
|
|
16
|
+
|
|
17
|
+
# Python
|
|
18
|
+
- Magics work as written, including `%%bash`. Change directory with `%cd` (expands `~`), not `os.chdir`.
|
|
19
|
+
- clikernel-created Python kernels display every top-level expression, not expressions inside loops/blocks. User startup can override this.
|
|
20
|
+
- All cell output reaches the conversation: `len(v)` first, then choose what to show.
|
|
21
|
+
- Don't repeat imports. A `NameError` for a name you set up means the kernel restarted or is newly attached: redo setup.
|
|
22
|
+
- After `nbdev-export` or imported-module edits, `importlib.reload` and re-import held names: `from x import y` keeps the old object; `@patch` refreshes on reload by writing onto the shared class. Restart only when a class you hold instances of was redefined. Check loaded behavior by calling it, never `inspect.getsource` (reads disk).
|
|
23
|
+
- Try the simple import/API call before environment changes, monkeypatching or extra setup.
|
|
24
|
+
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clikernel
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.16
|
|
4
4
|
Summary: Serve persistent Jupyter kernels to LLMs as concise text, over MCP or a plain stream protocol
|
|
5
5
|
Author: clikernel contributors
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -15,7 +15,7 @@ Requires-Dist: fastcore>=2.2.2
|
|
|
15
15
|
Requires-Dist: jupyasyncclient>=0.2.10
|
|
16
16
|
Requires-Dist: jupywire>=0.1.9
|
|
17
17
|
Requires-Dist: mcpmini>=0.0.5
|
|
18
|
-
Requires-Dist: rustygate>=0.1.
|
|
18
|
+
Requires-Dist: rustygate>=0.1.25
|
|
19
19
|
Requires-Dist: ipymini>=0.1.23
|
|
20
20
|
Requires-Dist: httpx
|
|
21
21
|
Requires-Dist: ipython
|
|
@@ -61,7 +61,9 @@ create(kernel="py")
|
|
|
61
61
|
exec(code="6*7")
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
Use `kernel="luau"` for bundled Luau, or `kernel="
|
|
64
|
+
Use `kernel="luau"` for bundled Luau, or `kernel="bpl"` for BPL when `basedpl` is installed in the gateway’s environment. `create` accepts any kernelspec the gateway finds. In the main conversation, `exec` never creates or switches kernels. See [MCP](https://AnswerDotAI.github.io/clikernel/mcp.html) for named gateways, dialog bindings, and other tools.
|
|
65
|
+
|
|
66
|
+
In Claude Code, subagents share the current kernel unless `clikernel-hook` is registered as a `PreToolUse` hook. With the hook, each subagent’s first `exec` creates a kernel of its own. See [Subagent kernels](https://AnswerDotAI.github.io/clikernel/mcp.html#subagent-kernels) for the settings entry.
|
|
65
67
|
|
|
66
68
|
The router checks each `exec` cell against built-in rules for mistakes agents often make with this toolkit. Most rules add a note to the reply, and `--quiet` leaves the notes out. Two rules always block the cell: shell access through `subprocess`, [`os.system`](https://docs.python.org/3/library/os.html#os.system) or [`os.popen`](https://docs.python.org/3/library/os.html#os.popen), and changes to `sys.path`.
|
|
67
69
|
|
|
@@ -20,7 +20,7 @@ dependencies = [
|
|
|
20
20
|
"jupyasyncclient>=0.2.10",
|
|
21
21
|
"jupywire>=0.1.9",
|
|
22
22
|
"mcpmini>=0.0.5",
|
|
23
|
-
"rustygate>=0.1.
|
|
23
|
+
"rustygate>=0.1.25",
|
|
24
24
|
"ipymini>=0.1.23",
|
|
25
25
|
"httpx",
|
|
26
26
|
"ipython",
|
|
@@ -42,6 +42,7 @@ Documentation = "https://AnswerDotAI.github.io/clikernel/"
|
|
|
42
42
|
[project.scripts]
|
|
43
43
|
clikernel = "clikernel.cli:main"
|
|
44
44
|
clikernel-mcp = "clikernel.mcp:main"
|
|
45
|
+
clikernel-hook = "clikernel.mcp:hook"
|
|
45
46
|
|
|
46
47
|
[project.entry-points.nbdev]
|
|
47
48
|
clikernel = "clikernel._modidx:d"
|
|
@@ -67,11 +67,11 @@ def test_rules():
|
|
|
67
67
|
assert fires(f"file_exhash(p, ('1|aa|', 's', 'x', {'y'*121!r}))", "s_long")
|
|
68
68
|
assert not fires(f"file_exhash(p, ('1|aa|', 's', 'x', {'y'*120!r}))", "s_long")
|
|
69
69
|
|
|
70
|
-
# blockers: Python's own shell routes, but not `!` escapes, since the router can't tell Python from
|
|
70
|
+
# blockers: Python's own shell routes, but not `!` escapes, since the router can't tell Python from BPL
|
|
71
71
|
assert fires("import subprocess", "shell_escape")
|
|
72
72
|
assert fires("os.system('ls')", "shell_escape")
|
|
73
73
|
assert fires("os.popen('ls')", "shell_escape")
|
|
74
|
-
assert not fires("!5", "shell_escape") #
|
|
74
|
+
assert not fires("!5", "shell_escape") # BPL's factorial parses exactly like IPython's `!ls`
|
|
75
75
|
assert not fires("io.popen('ls')", "shell_escape") # Luau's popen
|
|
76
76
|
assert fires("import subprocess.x", "shell_escape") # submodule spellings
|
|
77
77
|
assert fires("from subprocess.x import y", "shell_escape")
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
"""Use the `clikernel` MCP session as the default workspace for Python work: reading and changing files, notebook work, trying things out, checking how a library behaves, and reshaping data. One session stays open, so imports and variables carry between calls. Read this before writing, running, or debugging Python code in a session with `clikernel` connected.
|
|
2
|
-
|
|
3
|
-
Prefer it to one-off scripts (`python -c`, shell heredocs), and in-kernel pyskills to shell equivalents when installed: `rgapi` (`rg()`/`fd()`/`ls()`) for search and listing, `ghapi` for GitHub and local git. Shell stays right for project tests/builds and non-Python tools.
|
|
4
|
-
|
|
5
|
-
# Kernels
|
|
6
|
-
|
|
7
|
-
MCP tool descriptions give each tool's parameters and rules; this covers combining them.
|
|
8
|
-
|
|
9
|
-
`create(kernel="py")` first; its reply reports what startup imported and what to run next: read it before running anything. Kernels this conversation creates stop when it ends; for one that should outlive it, `create(kernel="py", dlgname="work", autoclose=false)` and tell the user its id. That needs a gateway that keeps running: say so if the reply shows a conversation-started gateway.
|
|
10
|
-
|
|
11
|
-
To continue earlier work or use the user's solveit kernel: `list_kernels`, then `use_kernel` with the id. Attaching runs no setup and claims no ownership: the live state is the point, and it's never stopped for you.
|
|
12
|
-
|
|
13
|
-
`restart` re-runs Python startup only in kernels this conversation created; redo everything else. `interrupt` stops a long `exec`, keeping state. If a reply says the kernel is gone, `create` or `use_kernel` before executing again.
|
|
14
|
-
|
|
15
|
-
Remote gateways (named in `~/.config/clikernel/gateways.toml`): same tools, with `host=` on `list_kernels`/`use_kernel`/`create`; all other rules unchanged. After selecting with a host, plain `exec` runs there until the next selection.
|
|
16
|
-
|
|
17
|
-
# Luau and APL
|
|
18
|
-
|
|
19
|
-
`create(kernel="luau")`/`create(kernel="apl")`: same `exec` and autoclose rules; no Python startup or magics. Each `create` reply gives the language basics; Luau's `exec(code="help()")` is its full native API guide (`help("ex.edit_file")` for one function). APL needs `basedpl` in the gateway's environment, which provides the `apl` kernelspec.
|
|
20
|
-
|
|
21
|
-
# Python
|
|
22
|
-
|
|
23
|
-
- Magics work as written, incl. `%%bash`. Change directory with `%cd` (expands `~`), not `os.chdir`.
|
|
24
|
-
- clikernel-created Python kernels display every expression result (`ast_node_interactivity='all'`); user startup can override.
|
|
25
|
-
- All cell output lands in the conversation: `len(v)` first, then choose what to show.
|
|
26
|
-
- Don't re-run imports already run. A `NameError` for a name you set up means the kernel restarted or is newly attached: redo setup.
|
|
27
|
-
- After `nbdev-export` or any edit to an imported module, `importlib.reload` it and re-import held names: `from x import y` keeps the old object; a `@patch`ed method refreshes on reload (the patch writes onto the shared class). Restart only when a class you hold instances of was redefined. Check what's loaded by calling it, never with `inspect.getsource` (reads the file on disk).
|
|
28
|
-
- Try the simple import or API call before changing the environment, monkeypatching, or adding setup.
|
|
29
|
-
"""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|