clikernel 0.2.0__tar.gz → 0.2.2__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.
@@ -2,6 +2,20 @@
2
2
 
3
3
  <!-- do not remove -->
4
4
 
5
+ ## 0.2.2
6
+
7
+ ### New Features
8
+
9
+ - skip AST inspectors on nested `run_cell` so tool-replayed cells (e.g. %nbrun) are not treated as user-typed input ([#32](https://github.com/AnswerDotAI/clikernel/issues/32))
10
+
11
+
12
+ ## 0.2.1
13
+
14
+ ### New Features
15
+
16
+ - Delegate traceback truncation and ANSI stripping to fastcore `render_text`(`tb_maxlen`=120); drop `render_outs` ([#31](https://github.com/AnswerDotAI/clikernel/issues/31))
17
+
18
+
5
19
  ## 0.2.0
6
20
 
7
21
  ### New Features
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clikernel
3
- Version: 0.2.0
3
+ Version: 0.2.2
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: Apache-2.0
@@ -11,7 +11,7 @@ Classifier: Programming Language :: Python :: 3 :: Only
11
11
  Requires-Python: >=3.11
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
- Requires-Dist: fastcore>=2.1.17
14
+ Requires-Dist: fastcore>=2.2.2
15
15
  Requires-Dist: jupyasyncclient>=0.2.1
16
16
  Requires-Dist: mcpmini>=0.0.1
17
17
  Requires-Dist: aidialog>=0.0.7
@@ -2,7 +2,10 @@
2
2
 
3
3
  Modules:
4
4
 
5
+ - `clikernel.cli`: The stream-protocol frontend: the service on stdin/stdout for token-reading clients
6
+ - `clikernel.core`: Connect to jupygate-hosted kernels and turn execution into concise text
7
+ - `clikernel.mcp`: The MCP frontend: `Client` as tools on stdio
5
8
  - `clikernel.skill`: Use the persistent `clikernel` MCP session as the default workspace for any task advanced through live Python execution -- stateful inspection, file-editing workflows, debugging, experiments, API probes, data transforms, or notebook-style work. Read this before writing, running, or debugging Python code in a session with `clikernel` connected.
6
9
  - `clikernel.stream`: Streaming JSON-lines worker protocol: nbformat-shaped output events, and a supervisor for select-based UIs."""
7
10
 
8
- __version__ = "0.2.0"
11
+ __version__ = "0.2.2"
@@ -1,6 +1,10 @@
1
1
  # Autogenerated by nbdev
2
2
 
3
- d = { 'settings': {'branch': 'main', 'doc_baseurl': '/', 'doc_host': '', 'git_url': '', 'lib_path': 'clikernel'},
3
+ d = { 'settings': { 'branch': 'main',
4
+ 'doc_baseurl': '/clikernel',
5
+ 'doc_host': 'https://AnswerDotAI.github.io',
6
+ 'git_url': 'https://github.com/AnswerDotAI/clikernel',
7
+ 'lib_path': 'clikernel'},
4
8
  'syms': { 'clikernel.cli': { 'clikernel.cli._new_delim': ('cli.html#_new_delim', 'clikernel/cli.py'),
5
9
  'clikernel.cli._next_line': ('cli.html#_next_line', 'clikernel/cli.py'),
6
10
  'clikernel.cli._read_block': ('cli.html#_read_block', 'clikernel/cli.py'),
@@ -23,11 +27,8 @@ d = { 'settings': {'branch': 'main', 'doc_baseurl': '/', 'doc_host': '', 'git_ur
23
27
  'clikernel.core.Client.stop': ('core.html#client.stop', 'clikernel/core.py'),
24
28
  'clikernel.core._inspector_setup': ('core.html#_inspector_setup', 'clikernel/core.py'),
25
29
  'clikernel.core._startup_src': ('core.html#_startup_src', 'clikernel/core.py'),
26
- 'clikernel.core._tb_line': ('core.html#_tb_line', 'clikernel/core.py'),
27
- 'clikernel.core._trunc_tb': ('core.html#_trunc_tb', 'clikernel/core.py'),
28
30
  'clikernel.core.cfg_dir': ('core.html#cfg_dir', 'clikernel/core.py'),
29
31
  'clikernel.core.gateways': ('core.html#gateways', 'clikernel/core.py'),
30
- 'clikernel.core.render_outs': ('core.html#render_outs', 'clikernel/core.py'),
31
32
  'clikernel.core.resolve': ('core.html#resolve', 'clikernel/core.py')},
32
33
  'clikernel.mcp': { 'clikernel.mcp.main': ('mcp.html#main', 'clikernel/mcp.py'),
33
34
  'clikernel.mcp.mk_server': ('mcp.html#mk_server', 'clikernel/mcp.py'),
@@ -1,6 +1,8 @@
1
1
  """The stream-protocol frontend: the service on stdin/stdout for token-reading clients
2
2
 
3
- The `clikernel` command: the delimiter-framed stdin/stdout protocol v1 established (documented in the README, rationale unchanged — a client that reads stdout as tokens wants no echo, a cheap ack byte, and a per-process random delimiter to read until). The protocol machinery ports from v1 verbatim; underneath, the process is now a thin client of a jupygate kernel. Run bare it creates a kernel and stops it again on exit — whoever ran the command made that decision by running it — while `--kernel` attaches to an existing kernel and leaves it exactly as found. Ctrl-C during a long cell translates into a kernel interrupt, jupyter-console style, instead of killing the process."""
3
+ The `clikernel` command: the delimiter-framed stdin/stdout protocol v1 established (documented in the README, rationale unchanged — a client that reads stdout as tokens wants no echo, a cheap ack byte, and a per-process random delimiter to read until). The protocol machinery ports from v1 verbatim; underneath, the process is now a thin client of a jupygate kernel. Run bare it creates a kernel and stops it again on exit — whoever ran the command made that decision by running it — while `--kernel` attaches to an existing kernel and leaves it exactly as found. Ctrl-C during a long cell translates into a kernel interrupt, jupyter-console style, instead of killing the process.
4
+
5
+ Docs: https://AnswerDotAI.github.io/clikernel/cli.html.md"""
4
6
 
5
7
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/02_cli.ipynb.
6
8
 
@@ -1,16 +1,17 @@
1
1
  """Connect to jupygate-hosted kernels and turn execution into concise text
2
2
 
3
- clikernel is the LLM side of a two-process design: jupygate runs all the time and hosts the kernels; clikernel starts and stops with each conversation and holds nothing but a pointer. This module is the whole client: gateway resolution from `gateways.toml`, the concise-text rendering contract (ANSI-stripped, capped tracebacks), delivery of `startup.py` and `inspectors.py` into freshly created kernels, and `Client` — connect (create or attach), execute, interrupt, restart, stop, list. The MCP and CLI frontends are thin faces over `Client`; nothing here creates or kills a kernel except when asked."""
3
+ clikernel is the LLM side of a two-process design: jupygate runs all the time and hosts the kernels; clikernel starts and stops with each conversation and holds nothing but a pointer. This module is the whole client: gateway resolution from `gateways.toml`, the concise-text rendering contract (ANSI-stripped, capped tracebacks), delivery of `startup.py` and `inspectors.py` into freshly created kernels, and `Client` — connect (create or attach), execute, interrupt, restart, stop, list. The MCP and CLI frontends are thin faces over `Client`; nothing here creates or kills a kernel except when asked.
4
+
5
+ Docs: https://AnswerDotAI.github.io/clikernel/core.html.md"""
4
6
 
5
7
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/00_core.ipynb.
6
8
 
7
9
  # %% auto #0
8
- __all__ = ['DEFAULT_URL', 'STATE_LOST', 'cfg_dir', 'gateways', 'resolve', 'render_outs', 'Client']
10
+ __all__ = ['DEFAULT_URL', 'STATE_LOST', 'cfg_dir', 'gateways', 'resolve', 'Client']
9
11
 
10
12
  # %% ../nbs/00_core.ipynb #2b3b7f4a
11
13
  import os, tomllib
12
14
  from fastcore.utils import *
13
- from fastcore.ansi import strip_ansi
14
15
  from fastcore.nbio import render_text
15
16
  from fastcore.xdg import xdg_config_home
16
17
  from jupyasyncclient import JupyAsyncMultiKernelManager, DeadKernelError
@@ -36,33 +37,6 @@ def resolve(host='', cfgdir=None):
36
37
  return g['url'], g.get('token') or os.environ.get(g.get('token_env','')) or None
37
38
 
38
39
 
39
- # %% ../nbs/00_core.ipynb #b739b087
40
- def _tb_line(l, maxlen):
41
- "One traceback line, capped at `maxlen`; `None` drops it (an over-long anchor line means nothing once cut). `File `/`Cell ` locations are exempt."
42
- if len(l) <= maxlen: return l
43
- if l.strip() and not (set(l) - set('~^ ')): return None
44
- if l.lstrip().startswith(('File ', 'Cell ')): return l
45
- return l[:maxlen] + '…'
46
-
47
-
48
- def _trunc_tb(outputs, maxlen=120):
49
- "Cap over-long lines in error tracebacks: a cell magic's transformed source echoes its whole payload on one line. Locations survive whole, and so does the last chunk, which is the exception message in both IPython's format and the stdlib's."
50
- for o in outputs:
51
- if o.get("output_type") != "error": continue
52
- # Each chunk keeps its own identity: the renderer joins the list with newlines, so
53
- # flattening chunks into lines would silently drop the blank line between frames.
54
- # Strip ANSI before capping: the cap can sever an escape sequence, leaving an unstrippable fragment.
55
- tb = [strip_ansi(c) for c in o.get("traceback", [])]
56
- o["traceback"] = ["\n".join(l for l in (_tb_line(x, maxlen) for x in chunk.split("\n")) if l is not None)
57
- for chunk in tb[:-1]] + tb[-1:]
58
- return outputs
59
-
60
- # %% ../nbs/00_core.ipynb #f70c7acd
61
- def render_outs(outs):
62
- "Concise text for nbformat-style `outs`: cap tracebacks, render, strip ANSI"
63
- return strip_ansi(render_text(_trunc_tb(outs)))
64
-
65
-
66
40
  # %% ../nbs/00_core.ipynb #0d846754
67
41
  def _startup_src(src, path):
68
42
  "The startup file's source wrapped so `__file__` is bound to its path during the run, and absent after"
@@ -73,6 +47,7 @@ finally: del __file__'''
73
47
  # %% ../nbs/00_core.ipynb #3dbf4cb6
74
48
  _INSP_RUNNER = r'''
75
49
  import inspect as _clik_inspect
50
+ import sys as _clik_sys
76
51
  from IPython.core.error import InputRejected
77
52
  class RuleBlock(InputRejected):
78
53
  "Raise from an inspector to deliberately block a cell; any other inspector exception is a bug, and fails open"
@@ -81,6 +56,11 @@ class _ClikInspect:
81
56
  "Calls each inspector once per cell: 1-arg get the AST, 2-arg also the raw source"
82
57
  def __init__(self, fs): self.fs = fs
83
58
  def visit(self, tree):
59
+ fr, n = _clik_sys._getframe(), 0
60
+ while fr:
61
+ n += fr.f_code.co_name == 'run_cell_async'
62
+ fr = fr.f_back
63
+ if n > 1: return tree # nested run_cell: cell replayed by a tool (%nbrun etc.), not typed
84
64
  for f in self.fs:
85
65
  try:
86
66
  note = f(tree, _clik_src) if len(_clik_inspect.signature(f).parameters) > 1 else f(tree)
@@ -161,7 +141,7 @@ async def execute_outs(self:Client, code):
161
141
  async def execute(self:Client, code):
162
142
  "Run `code` in the current kernel; concise rendered text of its outputs"
163
143
  r = await self.execute_outs(code)
164
- return r if isinstance(r, str) else render_outs(r)
144
+ return r if isinstance(r, str) else render_text(r, tb_maxlen=120)
165
145
 
166
146
  # %% ../nbs/00_core.ipynb #1ef42fff
167
147
  @patch
@@ -1,6 +1,8 @@
1
1
  """The MCP frontend: `Client` as tools on stdio
2
2
 
3
- The frontend Claude Code launches per conversation: `mk_server` closes the tools over a `Client`, and `main` (the `clikernel-mcp` console script) serves it on stdio via mcpmini. There is no instructions machinery and nothing eager — usage is taught by skill text, the server answers `initialize` instantly, and nothing happens until the model calls `connect`. Tool descriptions carry v1's hard-won wording."""
3
+ The frontend Claude Code launches per conversation: `mk_server` closes the tools over a `Client`, and `main` (the `clikernel-mcp` console script) serves it on stdio via mcpmini. There is no instructions machinery and nothing eager — usage is taught by skill text, the server answers `initialize` instantly, and nothing happens until the model calls `connect`. Tool descriptions carry v1's hard-won wording.
4
+
5
+ Docs: https://AnswerDotAI.github.io/clikernel/mcp.html.md"""
4
6
 
5
7
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/01_mcp.ipynb.
6
8
 
@@ -14,7 +16,8 @@ from mcpmini.core import MCPServer, serve_stdio
14
16
  from aidialog.dialog import Message
15
17
  from aidialog.hist import output_parts, merge_media
16
18
  from aidialog.msg_parts import PartType, data_url
17
- from .core import Client, render_outs
19
+ from fastcore.nbio import render_text
20
+ from .core import Client
18
21
  from . import __version__
19
22
 
20
23
 
@@ -41,7 +44,7 @@ def mk_server(c:Client):
41
44
  "Run `code` in the current kernel, keeping state across calls (imports, variables, monkeypatches, cached objects). Requires a `connect` first. If the reply says the kernel died, `connect` again. Image outputs (plots etc.) come back as image blocks, resized to a token-friendly size, each preceded by its `<media id=...>` tag."
42
45
  r = await c.execute_outs(code)
43
46
  if isinstance(r, str): return r
44
- res = merge_media(render_outs(r), output_parts(Message(msg_type='code', output=r)))
47
+ res = merge_media(render_text(r, tb_maxlen=120), output_parts(Message(msg_type='code', output=r)))
45
48
  return res if isinstance(res, str) else dict(content=[part2block(p) for p in res], isError=False)
46
49
 
47
50
  async def list_kernels(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clikernel
3
- Version: 0.2.0
3
+ Version: 0.2.2
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: Apache-2.0
@@ -11,7 +11,7 @@ Classifier: Programming Language :: Python :: 3 :: Only
11
11
  Requires-Python: >=3.11
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
- Requires-Dist: fastcore>=2.1.17
14
+ Requires-Dist: fastcore>=2.2.2
15
15
  Requires-Dist: jupyasyncclient>=0.2.1
16
16
  Requires-Dist: mcpmini>=0.0.1
17
17
  Requires-Dist: aidialog>=0.0.7
@@ -1,4 +1,4 @@
1
- fastcore>=2.1.17
1
+ fastcore>=2.2.2
2
2
  jupyasyncclient>=0.2.1
3
3
  mcpmini>=0.0.1
4
4
  aidialog>=0.0.7
@@ -16,7 +16,7 @@ classifiers = [
16
16
  ]
17
17
 
18
18
  dependencies = [
19
- "fastcore>=2.1.17",
19
+ "fastcore>=2.2.2",
20
20
  "jupyasyncclient>=0.2.1",
21
21
  "mcpmini>=0.0.1",
22
22
  "aidialog>=0.0.7",
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes