codebind 0.3.0__py3-none-any.whl → 0.4.1__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.
codebind/__init__.py CHANGED
@@ -4,6 +4,7 @@ from importlib.metadata import version
4
4
 
5
5
  from .execution import ExecutionReport, IPythonExecutor
6
6
  from .extension import load_ipython_extension, unload_ipython_extension
7
+ from .jupyter import JupyterLabBridge
7
8
  from .session import Session
8
9
 
9
10
 
@@ -12,6 +13,7 @@ __version__ = version("codebind")
12
13
  __all__ = [
13
14
  "ExecutionReport",
14
15
  "IPythonExecutor",
16
+ "JupyterLabBridge",
15
17
  "Session",
16
18
  "__version__",
17
19
  "load_ipython_extension",
codebind/execution.py CHANGED
@@ -2,13 +2,18 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import sys
6
+ from collections.abc import Callable, Iterator
7
+ from contextlib import contextmanager
5
8
  from dataclasses import asdict, dataclass
6
- from typing import Any
9
+ from typing import Any, cast
7
10
 
11
+ from IPython.core.displaypub import DisplayPublisher
8
12
  from IPython.core.interactiveshell import InteractiveShell
9
13
  from IPython.utils.capture import capture_output
10
14
 
11
15
  from .display import display_cell
16
+ from .jupyter import JupyterLabBridge
12
17
 
13
18
 
14
19
  @dataclass(frozen=True, slots=True)
@@ -27,21 +32,194 @@ class ExecutionReport:
27
32
  return asdict(self)
28
33
 
29
34
 
35
+ class _ExecutionDisplayHook:
36
+ """Capture an expression result while preserving IPython output history."""
37
+
38
+ def __init__(
39
+ self,
40
+ shell: InteractiveShell,
41
+ outputs: list[dict[str, Any]],
42
+ on_output: Callable[[dict[str, Any]], None] | None = None,
43
+ ) -> None:
44
+ self.shell = shell
45
+ self.outputs = outputs
46
+ self.on_output = on_output
47
+
48
+ def __call__(self, value: Any = None) -> None:
49
+ if value is None:
50
+ return
51
+ displayhook = self.shell.displayhook
52
+ displayhook.check_for_underscore()
53
+ if displayhook.quiet():
54
+ return
55
+ data, metadata = displayhook.compute_format_data(value)
56
+ displayhook.update_user_ns(value)
57
+ displayhook.fill_exec_result(value)
58
+ if data:
59
+ displayhook.log_output(data)
60
+ self.outputs.append({"data": data, "metadata": metadata})
61
+ if self.on_output is not None:
62
+ self.on_output(
63
+ {
64
+ "output_type": "execute_result",
65
+ "execution_count": displayhook.prompt_count,
66
+ "data": data,
67
+ "metadata": metadata,
68
+ }
69
+ )
70
+
71
+
72
+ class _StreamingTextIO:
73
+ """Write to IPython's capture buffer while forwarding live stream output."""
74
+
75
+ def __init__(
76
+ self,
77
+ stream: Any,
78
+ name: str,
79
+ on_output: Callable[[dict[str, Any]], None],
80
+ ) -> None:
81
+ self.stream = stream
82
+ self.name = name
83
+ self.on_output = on_output
84
+
85
+ def write(self, text: str) -> int:
86
+ written = self.stream.write(text)
87
+ if text:
88
+ self.on_output({"output_type": "stream", "name": self.name, "text": text})
89
+ return written
90
+
91
+ def flush(self) -> None:
92
+ self.stream.flush()
93
+
94
+ def __getattr__(self, name: str) -> Any:
95
+ return getattr(self.stream, name)
96
+
97
+
98
+ class _StreamingDisplayPublisher:
99
+ """Capture rich displays while forwarding them to the notebook cell."""
100
+
101
+ def __init__(
102
+ self,
103
+ publisher: Any,
104
+ on_output: Callable[[dict[str, Any]], None],
105
+ on_clear: Callable[[bool], None],
106
+ ) -> None:
107
+ self.publisher = publisher
108
+ self.on_output = on_output
109
+ self.on_clear = on_clear
110
+
111
+ def publish(
112
+ self,
113
+ data: dict[str, Any],
114
+ metadata: dict[str, Any] | None = None,
115
+ source: str | None = None,
116
+ *,
117
+ transient: dict[str, Any] | None = None,
118
+ update: bool = False,
119
+ ) -> None:
120
+ self.publisher.publish(
121
+ data,
122
+ metadata=metadata,
123
+ source=source,
124
+ transient=transient,
125
+ update=update,
126
+ )
127
+ self.on_output(
128
+ {
129
+ "output_type": "display_data",
130
+ "data": data,
131
+ "metadata": metadata or {},
132
+ }
133
+ )
134
+
135
+ def clear_output(self, wait: bool = False) -> None:
136
+ self.publisher.clear_output(wait)
137
+ self.on_clear(wait)
138
+
139
+ def __getattr__(self, name: str) -> Any:
140
+ return getattr(self.publisher, name)
141
+
142
+
143
+ @contextmanager
144
+ def _captured_execution(
145
+ shell: InteractiveShell,
146
+ expression_outputs: list[dict[str, Any]],
147
+ *,
148
+ bridged: bool,
149
+ on_output: Callable[[dict[str, Any]], None] | None = None,
150
+ on_clear: Callable[[bool], None] | None = None,
151
+ ) -> Iterator[Any]:
152
+ """Capture one nested execution without leaking its output to the parent cell."""
153
+ with capture_output() as captured:
154
+ if not bridged:
155
+ yield captured
156
+ return
157
+
158
+ previous_displayhook = sys.displayhook
159
+ previous_showtraceback = shell.showtraceback
160
+ previous_showsyntaxerror = shell.showsyntaxerror
161
+ if on_output is not None:
162
+ sys.stdout = _StreamingTextIO(sys.stdout, "stdout", on_output)
163
+ sys.stderr = _StreamingTextIO(sys.stderr, "stderr", on_output)
164
+ shell.display_pub = cast(
165
+ DisplayPublisher,
166
+ _StreamingDisplayPublisher(
167
+ shell.display_pub,
168
+ on_output,
169
+ on_clear or (lambda wait: None),
170
+ ),
171
+ )
172
+ sys.displayhook = _ExecutionDisplayHook(shell, expression_outputs, on_output)
173
+ shell.showtraceback = lambda *args, **kwargs: None
174
+ shell.showsyntaxerror = lambda *args, **kwargs: None
175
+ try:
176
+ yield captured
177
+ finally:
178
+ sys.displayhook = previous_displayhook
179
+ shell.showtraceback = previous_showtraceback
180
+ shell.showsyntaxerror = previous_showsyntaxerror
181
+
182
+
30
183
  class IPythonExecutor:
31
184
  """Run cells through one existing IPython shell."""
32
185
 
33
- def __init__(self, shell: InteractiveShell) -> None:
186
+ def __init__(
187
+ self,
188
+ shell: InteractiveShell,
189
+ bridge: JupyterLabBridge | None = None,
190
+ ) -> None:
34
191
  self.shell = shell
192
+ self.bridge = bridge
35
193
 
36
194
  def execute(self, cell: str) -> ExecutionReport:
37
195
  """Execute a cell through IPython and replay its native rich output."""
38
196
  if not isinstance(cell, str) or not cell.strip():
39
197
  raise ValueError("cell must be a non-empty string")
40
198
 
41
- display_cell(cell)
42
- with capture_output() as captured:
199
+ bridge = self.bridge
200
+ cell_id = bridge.start_code_cell(cell) if bridge is not None else None
201
+ bridged = cell_id is not None
202
+ on_output, on_clear = self._stream_callbacks(cell_id)
203
+ if not bridged:
204
+ display_cell(cell)
205
+ expression_outputs: list[dict[str, Any]] = []
206
+ with _captured_execution(
207
+ self.shell,
208
+ expression_outputs,
209
+ bridged=bridged,
210
+ on_output=on_output,
211
+ on_clear=on_clear,
212
+ ) as captured:
43
213
  result = self.shell.run_cell(cell, store_history=True)
44
- captured.show()
214
+ if bridged:
215
+ assert bridge is not None and cell_id is not None
216
+ bridge.finish_code_cell(
217
+ cell_id,
218
+ result.execution_count,
219
+ self._notebook_outputs(result, captured, expression_outputs),
220
+ )
221
+ else:
222
+ captured.show()
45
223
 
46
224
  return self._report(result, captured)
47
225
 
@@ -50,18 +228,57 @@ class IPythonExecutor:
50
228
  if not isinstance(cell, str) or not cell.strip():
51
229
  raise ValueError("cell must be a non-empty string")
52
230
 
53
- display_cell(cell)
231
+ bridge = self.bridge
232
+ cell_id = bridge.start_code_cell(cell) if bridge is not None else None
233
+ bridged = cell_id is not None
234
+ on_output, on_clear = self._stream_callbacks(cell_id)
235
+ if not bridged:
236
+ display_cell(cell)
54
237
  transformed = self.shell.transform_cell(cell)
55
- with capture_output() as captured:
238
+ expression_outputs: list[dict[str, Any]] = []
239
+ with _captured_execution(
240
+ self.shell,
241
+ expression_outputs,
242
+ bridged=bridged,
243
+ on_output=on_output,
244
+ on_clear=on_clear,
245
+ ) as captured:
56
246
  result = await self.shell.run_cell_async(
57
247
  cell,
58
248
  store_history=True,
59
249
  transformed_cell=transformed,
60
250
  )
61
- captured.show()
251
+ if bridged:
252
+ assert bridge is not None and cell_id is not None
253
+ bridge.finish_code_cell(
254
+ cell_id,
255
+ result.execution_count,
256
+ self._notebook_outputs(result, captured, expression_outputs),
257
+ )
258
+ else:
259
+ captured.show()
62
260
 
63
261
  return self._report(result, captured)
64
262
 
263
+ def _stream_callbacks(
264
+ self,
265
+ cell_id: str | None,
266
+ ) -> tuple[
267
+ Callable[[dict[str, Any]], None] | None,
268
+ Callable[[bool], None] | None,
269
+ ]:
270
+ bridge = self.bridge
271
+ if bridge is None or cell_id is None:
272
+ return None, None
273
+
274
+ def on_output(output: dict[str, Any]) -> None:
275
+ bridge.append_code_output(cell_id, output)
276
+
277
+ def on_clear(wait: bool) -> None:
278
+ bridge.clear_code_output(cell_id, wait=wait)
279
+
280
+ return on_output, on_clear
281
+
65
282
  @staticmethod
66
283
  def _report(result: Any, captured: Any) -> ExecutionReport:
67
284
  """Build the model-facing text projection of an IPython execution."""
@@ -86,3 +303,61 @@ class IPythonExecutor:
86
303
  displays=tuple(displays),
87
304
  error=error,
88
305
  )
306
+
307
+ def _notebook_outputs(
308
+ self,
309
+ result: Any,
310
+ captured: Any,
311
+ expression_outputs: list[dict[str, Any]],
312
+ ) -> list[dict[str, Any]]:
313
+ """Build standard nbformat outputs for a JupyterLab code cell."""
314
+ outputs: list[dict[str, Any]] = []
315
+ if captured.stdout:
316
+ outputs.append({"output_type": "stream", "name": "stdout", "text": captured.stdout})
317
+ if captured.stderr:
318
+ outputs.append({"output_type": "stream", "name": "stderr", "text": captured.stderr})
319
+
320
+ for output in captured.outputs:
321
+ data = getattr(output, "data", None)
322
+ if not isinstance(data, dict):
323
+ continue
324
+ outputs.append(
325
+ {
326
+ "output_type": "display_data",
327
+ "data": data,
328
+ "metadata": getattr(output, "metadata", {}) or {},
329
+ }
330
+ )
331
+
332
+ for expression in expression_outputs:
333
+ outputs.append(
334
+ {
335
+ "output_type": "execute_result",
336
+ "execution_count": result.execution_count,
337
+ "data": expression["data"],
338
+ "metadata": expression["metadata"],
339
+ }
340
+ )
341
+
342
+ exception = result.error_before_exec or result.error_in_exec
343
+ if exception is not None:
344
+ if isinstance(exception, SyntaxError):
345
+ traceback = self.shell.SyntaxTB.structured_traceback(
346
+ type(exception),
347
+ exception,
348
+ )
349
+ else:
350
+ traceback = self.shell.InteractiveTB.structured_traceback(
351
+ type(exception),
352
+ exception,
353
+ exception.__traceback__,
354
+ )
355
+ outputs.append(
356
+ {
357
+ "output_type": "error",
358
+ "ename": type(exception).__name__,
359
+ "evalue": str(exception),
360
+ "traceback": traceback,
361
+ }
362
+ )
363
+ return outputs
codebind/extension.py CHANGED
@@ -7,6 +7,7 @@ from typing import Any
7
7
  from IPython.core.interactiveshell import InteractiveShell
8
8
  from models_provider import Models
9
9
 
10
+ from .jupyter import JupyterLabBridge
10
11
  from .session import Session
11
12
 
12
13
 
@@ -17,9 +18,13 @@ def load_ipython_extension(ipython: InteractiveShell) -> None:
17
18
  """Load Codebind into the active IPython user namespace."""
18
19
  previous = getattr(ipython, _NAMESPACE_ATTRIBUTE, None)
19
20
  if isinstance(previous, dict):
21
+ previous_chat = previous.get("chat")
22
+ if isinstance(previous_chat, Session) and previous_chat.bridge is not None:
23
+ previous_chat.bridge.close()
20
24
  ipython.drop_by_id(previous)
25
+ bridge = JupyterLabBridge.connect(ipython)
21
26
  namespace: dict[str, Any] = {
22
- "chat": Session(shell=ipython),
27
+ "chat": Session(shell=ipython, bridge=bridge),
23
28
  "Models": Models,
24
29
  }
25
30
  ipython.push(namespace)
@@ -30,5 +35,8 @@ def unload_ipython_extension(ipython: InteractiveShell) -> None:
30
35
  """Remove names added by Codebind without touching user replacements."""
31
36
  namespace = getattr(ipython, _NAMESPACE_ATTRIBUTE, None)
32
37
  if isinstance(namespace, dict):
38
+ chat = namespace.get("chat")
39
+ if isinstance(chat, Session) and chat.bridge is not None:
40
+ chat.bridge.close()
33
41
  ipython.drop_by_id(namespace)
34
42
  delattr(ipython, _NAMESPACE_ATTRIBUTE)
codebind/jupyter.py ADDED
@@ -0,0 +1,109 @@
1
+ """Optional bridge from an IPython kernel to the Codebind JupyterLab extension."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+ from uuid import uuid4
7
+
8
+ from IPython.core.interactiveshell import InteractiveShell
9
+
10
+
11
+ _TARGET_NAME = "codebind"
12
+
13
+
14
+ class JupyterLabBridge:
15
+ """Send native notebook cells to a connected JupyterLab frontend."""
16
+
17
+ def __init__(self, comm: Any) -> None:
18
+ self._comm = comm
19
+ self.ready = False
20
+ comm.on_msg(self._on_message)
21
+
22
+ @classmethod
23
+ def connect(cls, shell: InteractiveShell) -> JupyterLabBridge | None:
24
+ """Open a frontend comm when running inside an IPython kernel."""
25
+ if not hasattr(shell, "kernel"):
26
+ return None
27
+
28
+ try:
29
+ from comm import create_comm # pyright: ignore[reportMissingImports]
30
+
31
+ return cls(create_comm(target_name=_TARGET_NAME))
32
+ except (ImportError, RuntimeError):
33
+ return None
34
+
35
+ def close(self) -> None:
36
+ """Close the frontend connection."""
37
+ self._comm.close()
38
+ self.ready = False
39
+
40
+ def start_code_cell(self, source: str) -> str | None:
41
+ """Insert a running code cell before its execution begins."""
42
+ if not self.ready:
43
+ return None
44
+ cell_id = str(uuid4())
45
+ self._comm.send(
46
+ {
47
+ "type": "code_cell_started",
48
+ "cell_id": cell_id,
49
+ "source": source,
50
+ }
51
+ )
52
+ return cell_id
53
+
54
+ def finish_code_cell(
55
+ self,
56
+ cell_id: str,
57
+ execution_count: int | None,
58
+ outputs: list[dict[str, Any]],
59
+ ) -> bool:
60
+ """Finish a previously inserted code cell with its native outputs."""
61
+ if not self.ready:
62
+ return False
63
+ self._comm.send(
64
+ {
65
+ "type": "code_cell_finished",
66
+ "cell_id": cell_id,
67
+ "execution_count": execution_count,
68
+ "outputs": outputs,
69
+ }
70
+ )
71
+ return True
72
+
73
+ def append_code_output(self, cell_id: str, output: dict[str, Any]) -> bool:
74
+ """Append one live output to a running code cell."""
75
+ if not self.ready:
76
+ return False
77
+ self._comm.send(
78
+ {
79
+ "type": "code_cell_output",
80
+ "cell_id": cell_id,
81
+ "output": output,
82
+ }
83
+ )
84
+ return True
85
+
86
+ def clear_code_output(self, cell_id: str, *, wait: bool = False) -> bool:
87
+ """Clear a running code cell's outputs."""
88
+ if not self.ready:
89
+ return False
90
+ self._comm.send(
91
+ {
92
+ "type": "code_cell_clear",
93
+ "cell_id": cell_id,
94
+ "wait": wait,
95
+ }
96
+ )
97
+ return True
98
+
99
+ def insert_markdown_cell(self, source: str) -> bool:
100
+ """Insert one rendered Markdown cell through JupyterLab."""
101
+ if not self.ready:
102
+ return False
103
+ self._comm.send({"type": "markdown_cell", "source": source})
104
+ return True
105
+
106
+ def _on_message(self, message: dict[str, Any]) -> None:
107
+ data = message.get("content", {}).get("data", {})
108
+ if isinstance(data, dict) and data.get("type") == "ready":
109
+ self.ready = True
codebind/session.py CHANGED
@@ -15,6 +15,7 @@ from langchain_core.runnables import Runnable
15
15
 
16
16
  from .display import display_assistant
17
17
  from .execution import ExecutionReport, IPythonExecutor
18
+ from .jupyter import JupyterLabBridge
18
19
 
19
20
 
20
21
  IPYTHON_TOOL = {
@@ -66,13 +67,15 @@ class Session:
66
67
  *,
67
68
  shell: InteractiveShell | None = None,
68
69
  instructions: str | None = None,
70
+ bridge: JupyterLabBridge | None = None,
69
71
  ) -> None:
70
72
  resolved_shell = shell or get_ipython()
71
73
  if resolved_shell is None:
72
74
  raise RuntimeError("Session must be created inside IPython or given an IPython shell.")
73
75
 
74
76
  self.shell = resolved_shell
75
- self.executor = IPythonExecutor(resolved_shell)
77
+ self.bridge = bridge
78
+ self.executor = IPythonExecutor(resolved_shell, bridge)
76
79
  self.instructions = instructions.strip() if instructions else None
77
80
  self.messages: list[BaseMessage] = []
78
81
  self.last_response: AIMessage | None = None
@@ -112,7 +115,7 @@ class Session:
112
115
  if not response.tool_calls:
113
116
  answer = _message_text(response)
114
117
  if answer:
115
- display_assistant(answer)
118
+ self._display_assistant(answer)
116
119
  return
117
120
 
118
121
  for call in response.tool_calls:
@@ -144,7 +147,7 @@ class Session:
144
147
  if not response.tool_calls:
145
148
  answer = _message_text(response)
146
149
  if answer:
147
- display_assistant(answer)
150
+ self._display_assistant(answer)
148
151
  return
149
152
 
150
153
  for call in response.tool_calls:
@@ -164,6 +167,10 @@ class Session:
164
167
  except NotImplementedError:
165
168
  return model.bind(tools=[IPYTHON_TOOL], parallel_tool_calls=False)
166
169
 
170
+ def _display_assistant(self, answer: str) -> None:
171
+ if self.bridge is None or not self.bridge.insert_markdown_cell(answer):
172
+ display_assistant(answer)
173
+
167
174
  def _execute_call(self, call: Mapping[str, Any]) -> ExecutionReport:
168
175
  if call.get("name") != "ipython":
169
176
  return _tool_error("UnknownTool", f"unknown tool: {call.get('name')!r}")
@@ -0,0 +1,5 @@
1
+ {
2
+ "packageManager": "python",
3
+ "packageName": "codebind",
4
+ "uninstallInstructions": "Use the Python package manager that installed Codebind to uninstall it."
5
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "codebind-jupyterlab",
3
+ "version": "0.4.1",
4
+ "description": "Native JupyterLab cells for Codebind model executions.",
5
+ "license": "MIT",
6
+ "main": "lib/index.js",
7
+ "types": "lib/index.d.ts",
8
+ "files": [
9
+ "lib/**/*"
10
+ ],
11
+ "scripts": {
12
+ "build": "jlpm run clean && jlpm run build:lib && jupyter-builder build && jlpm run build:install",
13
+ "build:install": "node -e \"require('node:fs').copyFileSync('install.json', '../data/share/jupyter/labextensions/codebind-jupyterlab/install.json')\"",
14
+ "build:lib": "tsc",
15
+ "clean": "rimraf lib tsconfig.tsbuildinfo"
16
+ },
17
+ "dependencies": {
18
+ "@jupyterlab/application": "^4.0.0",
19
+ "@jupyterlab/cells": "^4.0.0",
20
+ "@jupyterlab/nbformat": "^4.0.0",
21
+ "@jupyterlab/notebook": "^4.0.0",
22
+ "@jupyterlab/services": "^7.0.0"
23
+ },
24
+ "devDependencies": {
25
+ "@jupyter/builder": "^1.2.0",
26
+ "rimraf": "^6.0.0",
27
+ "typescript": "~5.7.0"
28
+ },
29
+ "jupyterlab": {
30
+ "extension": true,
31
+ "outputDir": "../data/share/jupyter/labextensions/codebind-jupyterlab",
32
+ "_build": {
33
+ "load": "static/remoteEntry.6f017385dfc989f1.js",
34
+ "extension": "./extension"
35
+ }
36
+ }
37
+ }
@@ -0,0 +1 @@
1
+ "use strict";(self.rspackChunkcodebind_jupyterlab=self.rspackChunkcodebind_jupyterlab||[]).push([[590],{509(e,t,n){n.r(t);var o=n(171);function l(e){let t=e.sessionContext.session?.kernel;if(!t)return;let n=null,l=new Map;t.registerCommTarget("codebind",(u,c)=>{u.onMsg=u=>{var c;let d,i,r=u.content.data;if(!("object"==typeof r&&null!==r&&("markdown_cell"===r.type?"string"==typeof r.source:"code_cell_started"===r.type?"string"==typeof r.cell_id&&"string"==typeof r.source:"code_cell_output"===r.type?"string"==typeof r.cell_id&&"object"==typeof r.output&&null!==r.output:"code_cell_clear"===r.type?"string"==typeof r.cell_id&&"boolean"==typeof r.wait:"code_cell_finished"===r.type&&"string"==typeof r.cell_id&&("number"==typeof r.execution_count||null===r.execution_count)&&Array.isArray(r.outputs))))return;if("code_cell_output"===r.type)return void l.get(r.cell_id)?.outputs.add(r.output);if("code_cell_clear"===r.type)return void l.get(r.cell_id)?.outputs.clear(r.wait);if("code_cell_finished"===r.type){let e=l.get(r.cell_id);if(!e)return;e.outputs.fromJSON(r.outputs),e.executionCount=r.execution_count,e.executionState="idle",n&&null===n.parentExecutionCount&&null!==r.execution_count&&(n.parentExecutionCount=r.execution_count-1);return}let s=(()=>{if(n)return n;let o=e.content,l=o.widgets.findIndex(e=>"code"===e.model.type&&"running"===e.model.executionState);n={parentModel:l>=0?o.widgets[l].model:null,parentExecutionCount:null,resumeModel:o.activeCell?.model??null,nextIndex:l>=0?l+1:o.activeCell?o.activeCellIndex+1:0};let u=(o,l)=>{"idle"===l&&(t.statusChanged.disconnect(u),(()=>{if(!n)return;let t=e.content,o=n;if(n=null,o.parentModel&&null!==o.parentExecutionCount&&(o.parentModel.executionCount=o.parentExecutionCount),o.resumeModel){let e=t.widgets.findIndex(e=>e.model===o.resumeModel);e>=0&&(t.activeCellIndex=e)}})())};return t.statusChanged.connect(u),n})(),a=(c=s.nextIndex,(i=(d=e.content).model)?("code_cell_started"===r.type?i.sharedModel.insertCell(c,{cell_type:"code",source:r.source,metadata:{trusted:!0},execution_count:null,outputs:[]}):i.sharedModel.insertCell(c,{cell_type:"markdown",source:r.source,metadata:{}}),d.activeCellIndex=c,d.deselectAll(),"markdown_cell"===r.type&&o.NotebookActions.run(d),d.scrollToItem(c),d.widgets[c]?.model??null):null);"code_cell_started"===r.type&&a?.type==="code"&&(a.executionState="running",l.set(r.cell_id,a)),s.nextIndex+=1},u.send({type:"ready"})})}function u(e){e.sessionContext.ready.then(()=>l(e)),e.sessionContext.kernelChanged.connect(()=>l(e))}let c={id:"codebind-jupyterlab:plugin",description:"Insert Codebind executions as native notebook cells.",autoStart:!0,requires:[o.INotebookTracker],activate:(e,t)=>{t.forEach(u),t.widgetAdded.connect((e,t)=>u(t))}};n.d(t,{},{default:c})}}]);
@@ -0,0 +1 @@
1
+ var _JUPYTERLAB;(()=>{"use strict";var e,n,r,t,o,i,u,a,c,l,f,s,p,d,h,v,g,m,y,b,S,j,k,w,E,x,T,P,M,A,C,O,D,_,L,N,$,q={457(e,n,r){var t={"./index":()=>r.e(590).then(()=>()=>r(509)),"./extension":()=>r.e(590).then(()=>()=>r(509))},o=function(e,n){return r.R=n,n=r.o(t,e)?t[e]():Promise.resolve().then(()=>{throw Error('Module "'+e+'" does not exist in container.')}),r.R=void 0,n},i=function(e,n){if(r.S){var t="default",o=r.S[t];if(o&&o!==e)throw Error("Container initialization failed as it has already been initialized with a different share scope");return r.S[t]=e,r.I(t,n)}};r.d(n,{get:()=>o,init:()=>i})}},z={};function I(e){var n=z[e];if(void 0!==n)return n.exports;var r=z[e]={exports:{}};return q[e](r,r.exports,I),r.exports}I.m=q,I.c=z,I.n=e=>{var n=e&&e.__esModule?()=>e.default:()=>e;return I.d(n,{a:n}),n},I.d=(e,n,r)=>{var t=(n,r)=>{for(var t in n)I.o(n,t)&&!I.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,[r]:n[t]})};t(n,"get"),t(r,"value")},I.f={},I.e=e=>Promise.all(Object.keys(I.f).reduce((n,r)=>(I.f[r](e,n),n),[])),I.u=e=>""+e+".6544266cd73a003b.js?v=6544266cd73a003b",I.g=(()=>{if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}})(),I.o=(e,n)=>Object.prototype.hasOwnProperty.call(e,n),R={},I.l=function(e,n,r,t){if(R[e])return void R[e].push(n);if(void 0!==r)for(var o,i,u=document.getElementsByTagName("script"),a=0;a<u.length;a++){var c=u[a];if(c.getAttribute("src")==e||c.getAttribute("data-rspack")=="codebind-jupyterlab:"+r){o=c;break}}o||(i=!0,(o=document.createElement("script")).timeout=120,I.nc&&o.setAttribute("nonce",I.nc),o.setAttribute("data-rspack","codebind-jupyterlab:"+r),o.src=e),R[e]=[n];var l=function(n,r){o.onerror=o.onload=null,clearTimeout(f);var t=R[e];if(delete R[e],o.parentNode&&o.parentNode.removeChild(o),t&&t.forEach(function(e){return e(r)}),n)return n(r)},f=setTimeout(l.bind(null,void 0,{type:"timeout",target:o}),12e4);o.onerror=l.bind(null,o.onerror),o.onload=l.bind(null,o.onload),i&&document.head.appendChild(o)},I.r=e=>{"u">typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},I.S={},I.initializeSharingData={scopeToSharingDataMapping:{default:[{name:"codebind-jupyterlab",version:"0.4.1",factory:()=>I.e(590).then(()=>()=>I(509)),eager:0,treeShakingMode:null}]},uniqueName:"codebind-jupyterlab"},U={},B={},I.I=function(e,n){n||(n=[]);var r=B[e];if(r||(r=B[e]={}),!(n.indexOf(r)>=0)){if(n.push(r),U[e])return U[e];I.o(I.S,e)||(I.S[e]={});var t=I.S[e],o=function(e){"u">typeof console&&console.warn&&console.warn(e)},i=I.initializeSharingData.uniqueName,u=function(e,n,r,o){var u=t[e]=t[e]||{},a=u[n];(!a||!a.loaded&&(!o!=!a.eager?o:i>a.from))&&(u[n]={get:r,from:i,eager:!!o})},a=function(r){var t=function(e){o("Initialization of sharing external failed: "+e)};try{var i=I(r);if(!i)return;var u=function(r){return r&&r.init&&r.init(I.S[e],n)};if(i.then)return c.push(i.then(u,t));var a=u(i);if(a&&a.then)return c.push(a.catch(t))}catch(e){t(e)}},c=[],l=I.initializeSharingData.scopeToSharingDataMapping;return(l[e]&&l[e].forEach(function(e){"object"==typeof e?u(e.name,e.version,e.factory,e.eager):a(e)}),c.length)?U[e]=Promise.all(c).then(function(){return U[e]=1}):U[e]=1}},I.g.importScripts&&(V=I.g.location+"");var R,U,B,V,J=I.g.document;if(!V&&J&&(J.currentScript&&"SCRIPT"===J.currentScript.tagName.toUpperCase()&&(V=J.currentScript.src),!V)){var Y=J.getElementsByTagName("script");if(Y.length)for(var K=Y.length-1;K>-1&&(!V||!/^http(s?):/.test(V));)V=Y[K--].src}if(!V)throw Error("Automatic publicPath is not supported in this browser");I.p=V=V.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),I.consumesLoadingData={chunkMapping:{590:["171"]},moduleIdToConsumeDataMapping:{171:{shareScope:"default",shareKey:"@jupyterlab/notebook",import:null,requiredVersion:"^4.6.3",strictVersion:!1,singleton:!0,eager:!1,fallback:void 0,treeShakingMode:null}},initialConsumes:[]},e=function(e){return e.split(".").map(function(e){return+e==e?+e:e})},n=function(n){var r=function(n){var r=/^([^-+]+)?(?:-([^+]+))?(?:\+(.+))?$/.exec(n),t=r[1]?[0].concat(e(r[1])):[0];r[2]&&(t.length++,t.push.apply(t,e(r[2])));let o=t[t.length-1];for(;t.length&&(void 0===o||/^[*xX]$/.test(o));)t.pop(),o=t[t.length-1];return t},t=function(e){return 1===e.length?[0]:2===e.length?[1].concat(e.slice(1)):3===e.length?[2].concat(e.slice(1)):[e.length].concat(e.slice(1))},o=function(e){return[-e[0]-1].concat(e.slice(1))},i=function(e){let n=/^(\^|~|<=|<|>=|>|=|v|!)/.exec(e),i=n?n[0]:"",u=r(i.length?e.slice(i.length).trim():e.trim());switch(i){case"^":if(u.length>1&&0===u[1]){if(u.length>2&&0===u[2])return[3].concat(u.slice(1));return[2].concat(u.slice(1))}return[1].concat(u.slice(1));case"~":return[2].concat(u.slice(1));case">=":return u;case"=":case"v":case"":return t(u);case"<":return o(u);case">":return[,t(u),0,u,2];case"<=":return[,t(u),o(u),1];case"!":return[,t(u),0];default:throw Error("Unexpected start value")}},u=function(e,n){if(1===e.length)return e[0];let r=[];for(let n of e.slice().reverse())0 in n?r.push(n):r.push.apply(r,n.slice(1));return[,].concat(r,e.slice(1).map(()=>n))};return u(n.split(/\s*\|\|\s*/).map(function(e){let n=e.split(/\s+-\s+/);if(1===n.length){e=e.trim();let n=[],r=/[-0-9A-Za-z]\s+/g;for(var a,c=0;a=r.exec(e);){let r=a.index+1;n.push(i(e.slice(c,r).trim())),c=r}return n.push(i(e.slice(c).trim())),u(n,2)}let l=r(n[0]),f=r(n[1]);return[,t(f),o(f),1,l,2]}),1)},r=function(n){var r=/^([^-+]+)?(?:-([^+]+))?(?:\+(.+))?$/.exec(n),t=r[1]?e(r[1]):[];return r[2]&&(t.length++,t.push.apply(t,e(r[2]))),r[3]&&(t.push([]),t.push.apply(t,e(r[3]))),t},t=function(e,n){e=r(e),n=r(n);for(var t=0;;){if(t>=e.length)return t<n.length&&"u"!=(typeof n[t])[0];var o=e[t],i=(typeof o)[0];if(t>=n.length)return"u"==i;var u=n[t],a=(typeof u)[0];if(i==a){if("o"!=i&&"u"!=i&&o!=u)return o<u;t++}else{if("o"==i&&"n"==a)return!0;return"s"==a||"u"==i}}},o=function(e){var n=e[0],r="";if(1===e.length)return"*";if(n+.5){r+=0==n?">=":-1==n?"<":1==n?"^":2==n?"~":n>0?"=":"!=";for(var t=1,i=1;i<e.length;i++){var u=e[i],a=(typeof u)[0];t--,r+="u"==a?"-":(t>0?".":"")+(t=2,u)}return r}for(var c=[],i=1;i<e.length;i++){var u=e[i];c.push(0===u?"not("+l()+")":1===u?"("+l()+" || "+l()+")":2===u?c.pop()+" "+c.pop():o(u))}return l();function l(){return c.pop().replace(/^\((.+)\)$/,"$1")}},i=function(e,n){if(0 in e){n=r(n);var t=e[0],o=t<0;o&&(t=-t-1);for(var u=0,a=1,c=!0;;a++,u++){var l,f,s=a<e.length?(typeof e[a])[0]:"";if(u>=n.length||"o"==(f=(typeof(l=n[u]))[0])){if(!c)return!0;if("u"==s)return a>t&&!o;return""==s!=o}if("u"==f){if(!c||"u"!=s)return!1}else if(c)if(s==f)if(a<=t){if(l!=e[a])return!1}else{if(o?l>e[a]:l<e[a])return!1;l!=e[a]&&(c=!1)}else if("s"!=s&&"n"!=s){if(o||a<=t)return!1;c=!1,a--}else{if(a<=t||f<s!=o)return!1;c=!1}else"s"!=s&&"n"!=s&&(c=!1,a--)}}for(var p=[],d=p.pop.bind(p),u=1;u<e.length;u++){var h=e[u];p.push(1==h?d()|d():2==h?d()&d():h?i(h,n):!d())}return!!d()},u=function(e,n){var r=I.S[e];if(!r||!I.o(r,n))throw Error("Shared module "+n+" doesn't exist in shared scope "+e);return r},a=function(e,n){var r=e[n],n=Object.keys(r).reduce(function(e,n){return!e||t(e,n)?n:e},0);return n&&r[n]},c=function(e,n){var r=e[n];return Object.keys(r).reduce(function(e,n){return!e||!r[e].loaded&&t(e,n)?n:e},0)},l=function(e,n,r,t){return"Unsatisfied version "+r+" from "+(r&&e[n][r].from)+" of shared singleton module "+n+" (required "+o(t)+")"},f=function(e,n,r,t){var o=c(e,r);return y(e[r][o])},s=function(e,n,r,t){var o=c(e,r);return i(t,o)||g(l(e,r,o,t)),y(e[r][o])},p=function(e,n,r,t){var o=c(e,r);if(!i(t,o))throw Error(l(e,r,o,t));return y(e[r][o])},d=function(e,n,r){var o=e[n],n=Object.keys(o).reduce(function(e,n){return i(r,n)&&(!e||t(e,n))?n:e},0);return n&&o[n]},h=function(e,n,r,t){var i=e[r];return"No satisfying version ("+o(t)+") of shared module "+r+" found in shared scope "+n+".\nAvailable versions: "+Object.keys(i).map(function(e){return e+" from "+i[e].from}).join(", ")},v=function(e,n,r,t){var o=d(e,r,t);if(o)return y(o);throw Error(h(e,n,r,t))},g=function(e){"u">typeof console&&console.warn&&console.warn(e)},m=function(e,n,r,t){g(h(e,n,r,t))},y=function(e){return e.loaded=1,e.get()},S=(b=function(e){return function(n,r,t,o){var i=I.I(n);return i&&i.then?i.then(e.bind(e,n,I.S[n],r,t,o)):e(n,I.S[n],r,t,o)}})(function(e,n,r){return u(e,r),y(a(n,r))}),j=b(function(e,n,r,t){return n&&I.o(n,r)?y(a(n,r)):t()}),k=b(function(e,n,r,t){return u(e,r),y(d(n,r,t)||m(n,e,r,t)||a(n,r))}),w=b(function(e,n,r){return u(e,r),f(n,e,r)}),E=b(function(e,n,r,t){return u(e,r),s(n,e,r,t)}),x=b(function(e,n,r,t){return u(e,r),v(n,e,r,t)}),T=b(function(e,n,r,t){return u(e,r),p(n,e,r,t)}),P=b(function(e,n,r,t,o){return n&&I.o(n,r)?y(d(n,r,t)||m(n,e,r,t)||a(n,r)):o()}),M=b(function(e,n,r,t){return n&&I.o(n,r)?f(n,e,r):t()}),A=b(function(e,n,r,t,o){return n&&I.o(n,r)?s(n,e,r,t):o()}),C=b(function(e,n,r,t,o){var i=n&&I.o(n,r)&&d(n,r,t);return i?y(i):o()}),O=b(function(e,n,r,t,o){return n&&I.o(n,r)?p(n,e,r,t):o()}),D=function(e){var r=!1,t=!1,o=!1,i=!1,u=[e.shareScope,e.shareKey];return(e.requiredVersion?(e.strictVersion&&(r=!0),e.singleton&&(t=!0),u.push(n(e.requiredVersion)),o=!0):e.singleton&&(t=!0),e.fallback&&(i=!0,u.push(e.fallback)),r&&t&&o&&i)?function(){return O.apply(null,u)}:r&&o&&i?function(){return C.apply(null,u)}:t&&o&&i?function(){return A.apply(null,u)}:r&&t&&o?function(){return T.apply(null,u)}:t&&i?function(){return M.apply(null,u)}:o&&i?function(){return P.apply(null,u)}:r&&o?function(){return x.apply(null,u)}:t&&o?function(){return E.apply(null,u)}:t?function(){return w.apply(null,u)}:o?function(){return k.apply(null,u)}:i?function(){return j.apply(null,u)}:function(){return S.apply(null,u)}},_={},I.f.consumes=function(e,n){var r=I.consumesLoadingData.moduleIdToConsumeDataMapping,t=I.consumesLoadingData.chunkMapping;I.o(t,e)&&t[e].forEach(function(e){if(I.o(_,e))return n.push(_[e]);var t=function(n){_[e]=0,I.m[e]=function(r){delete I.c[e],r.exports=n()}},o=function(n){delete _[e],I.m[e]=function(r){throw delete I.c[e],n}};try{var i=D(r[e])();i.then?n.push(_[e]=i.then(t).catch(o)):t(i)}catch(e){o(e)}})},L={871:0},I.f.j=function(e,n){var r=I.o(L,e)?L[e]:void 0;if(0!==r)if(r)n.push(r[2]);else{var t=new Promise((n,t)=>r=L[e]=[n,t]);n.push(r[2]=t);var o=I.p+I.u(e),i=Error();I.l(o,function(n){if(I.o(L,e)&&(0!==(r=L[e])&&(L[e]=void 0),r)){var t=n&&("load"===n.type?"missing":n.type),o=n&&n.target&&n.target.src;i.message="Loading chunk "+e+" failed.\n("+t+": "+o+")",i.name="ChunkLoadError",i.type=t,i.request=o,r[1](i)}},"chunk-"+e,e)}},N=(e,n)=>{var r,t,[o,i,u]=n,a=0;if(o.some(e=>0!==L[e])){for(r in i)I.o(i,r)&&(I.m[r]=i[r]);u&&u(I)}for(e&&e(n);a<o.length;a++)t=o[a],I.o(L,t)&&L[t]&&L[t][0](),L[t]=0},($=self.rspackChunkcodebind_jupyterlab=self.rspackChunkcodebind_jupyterlab||[]).forEach(N.bind(null,0)),$.push=N.bind(null,$.push.bind($));var F=I(457);(_JUPYTERLAB=void 0===_JUPYTERLAB?{}:_JUPYTERLAB)["codebind-jupyterlab"]=F})();
@@ -0,0 +1,2 @@
1
+ /* This is a generated file of CSS imports */
2
+ /* It was generated by @jupyter/builder in Build.ensureAssets() */
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codebind
3
- Version: 0.3.0
3
+ Version: 0.4.1
4
4
  Summary: A frontend-neutral model loop for persistent IPython sessions.
5
5
  Keywords: ai,ipython,llm,repl,agents
6
6
  Author: Giovanni Gravili
@@ -8,6 +8,7 @@ Author-email: Giovanni Gravili <ghovax@users.noreply.github.com>
8
8
  License-Expression: MIT
9
9
  License-File: LICENSE
10
10
  Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Framework :: Jupyter :: JupyterLab :: Extensions :: Prebuilt
11
12
  Classifier: Intended Audience :: Developers
12
13
  Classifier: Operating System :: OS Independent
13
14
  Classifier: Programming Language :: Python :: 3
@@ -61,7 +62,13 @@ Codebind does not load files or construct a project prompt automatically. The us
61
62
 
62
63
  ## Jupyter
63
64
 
64
- Install Codebind in the environment used by a Jupyter kernel, then start the Jupyter frontend normally:
65
+ Start JupyterLab with Codebind from any directory without a permanent installation:
66
+
67
+ ```console
68
+ uvx --from jupyterlab --with codebind jupyter lab
69
+ ```
70
+
71
+ Or install both packages into the same environment, then start JupyterLab normally:
65
72
 
66
73
  ```console
67
74
  pip install codebind jupyterlab
@@ -72,15 +79,19 @@ Load Codebind in a notebook:
72
79
 
73
80
  ```python
74
81
  %load_ext codebind
82
+ ```
83
+
84
+ Run that cell once so JupyterLab can connect to the extension, then use Codebind in later cells:
75
85
 
86
+ ```python
76
87
  models = Models({"openai": "OPENAI_API_KEY"})
77
88
  model = models.chat("openai/gpt-5")
78
89
  await chat.asend("Inspect the current notebook state.", model)
79
90
  ```
80
91
 
81
- Codebind publishes cells, assistant Markdown, stdout, tracebacks, and rich results through IPython's MIME display system. The active frontend decides how to render HTML, Markdown, images, SVG, audio, tables, and plain text.
92
+ The Codebind package includes a prebuilt JupyterLab extension. In JupyterLab, each model-authored IPython execution becomes a genuine code cell with its native execution count and outputs, and the assistant response becomes a rendered Markdown cell. The cells are ordinary notebook content and are saved with the notebook.
82
93
 
83
- Model-authored cells are recorded in native IPython history and displayed through the active frontend. A kernel cannot insert a genuine input cell into every possible frontend without a frontend-specific extension, so Codebind does not attempt to control notebook or editor UI.
94
+ Other IPython frontends use the standard MIME display protocol instead. They still receive syntax-highlighted code, assistant Markdown, stdout, tracebacks, rich results, and native IPython history without Codebind depending on their UI.
84
95
 
85
96
  ## ChatGPT account login
86
97
 
@@ -0,0 +1,19 @@
1
+ codebind/__init__.py,sha256=GNO_K5EPAdgiK2CWsPVVY-3bAmH2TnFJ9ReAWgSbnqM,514
2
+ codebind/cli.py,sha256=9LOR1L3P8sPsOOgsnpbOsA1PNUZdAj1Z9ROFKeo3EV8,423
3
+ codebind/display.py,sha256=IpxcElyNcr5olRIatEduN_d-sRwpsMlaq1UtrOXocQA,494
4
+ codebind/execution.py,sha256=b1U0iT0e9fDpvGZclGGihod_iLFjJ7wLUkXhmawgeRs,11968
5
+ codebind/extension.py,sha256=elIDW_zl5o3uyil1tEnJ0A5jVkADalqPWI_qcDw4xhY,1460
6
+ codebind/jupyter.py,sha256=xuoiV2-pqtcb_dWXvaaVrlSvqcYfqRYtbX4B-Zh_pBA,3272
7
+ codebind/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ codebind/session.py,sha256=6KK2d55XsKaq5IVRQmUp5Pzdg-O76bAZvCYgxBU2BN0,7455
9
+ codebind-0.4.1.dist-info/licenses/LICENSE,sha256=ujX46e0LFBIBTnCDY0u3Q7LV363B7CiI7QJZxIkEXlk,1073
10
+ codebind-0.4.1.data/data/share/jupyter/labextensions/codebind-jupyterlab/install.json,sha256=78rEThkfrk1pXtekX5WrG9_Y0lJYi5XBEm8n9athpFE,164
11
+ codebind-0.4.1.data/data/share/jupyter/labextensions/codebind-jupyterlab/package.json,sha256=HYr80rRV4byoPUvhf5r9swwnssr7eGxIay227Cusyqo,1152
12
+ codebind-0.4.1.data/data/share/jupyter/labextensions/codebind-jupyterlab/static/590.6544266cd73a003b.js,sha256=zrQdGY0320_MGN8KRf8dzMXCjXI_6jtZoAnW4UhDquo,2771
13
+ codebind-0.4.1.data/data/share/jupyter/labextensions/codebind-jupyterlab/static/remoteEntry.6f017385dfc989f1.js,sha256=Cwm6yVfy68shv0OTb6yIbHwV5FxozSz-jO9gf9WE0vc,11087
14
+ codebind-0.4.1.data/data/share/jupyter/labextensions/codebind-jupyterlab/static/style.js,sha256=Opy15wyuR_ABqsrk-cOcOj2yZtvtq0r36aDkErnloD0,113
15
+ codebind-0.4.1.data/data/share/jupyter/labextensions/codebind-jupyterlab/static/third-party-licenses.json,sha256=MNToQfru1YnHAie-2Virp23yWsc5co3qG0IzrO6SEfw,20
16
+ codebind-0.4.1.dist-info/WHEEL,sha256=R1d3uUTbmXM1FHXH_itQashbrqrOSVj-hvBCpmkIIGE,81
17
+ codebind-0.4.1.dist-info/entry_points.txt,sha256=GiPcU63sULDlywJSKigAS4M3vaziYIrubXOb3fAg2_c,48
18
+ codebind-0.4.1.dist-info/METADATA,sha256=AwmxcH7e9fosl2xI6gJhdbyzKhGbLE5yv1JBva4iX_k,4062
19
+ codebind-0.4.1.dist-info/RECORD,,
@@ -1,12 +0,0 @@
1
- codebind/__init__.py,sha256=ZA475Vomau6BqvxTwcXUeFKXiOlrjTWK6XjD8_Ez6os,452
2
- codebind/cli.py,sha256=9LOR1L3P8sPsOOgsnpbOsA1PNUZdAj1Z9ROFKeo3EV8,423
3
- codebind/display.py,sha256=IpxcElyNcr5olRIatEduN_d-sRwpsMlaq1UtrOXocQA,494
4
- codebind/execution.py,sha256=Md2bv_oIp4rwjMhf8W4b4azA6a0aj-4b6J64cErPP1A,2876
5
- codebind/extension.py,sha256=zt30o59tynJhat5OMi_u2rci1VDhq8ZOm202hZRwFp8,1055
6
- codebind/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- codebind/session.py,sha256=Q7HabKcjHUmawLKhoNc2Rdx2-pkTPM0sh1A-tApXQsU,7146
8
- codebind-0.3.0.dist-info/licenses/LICENSE,sha256=ujX46e0LFBIBTnCDY0u3Q7LV363B7CiI7QJZxIkEXlk,1073
9
- codebind-0.3.0.dist-info/WHEEL,sha256=R1d3uUTbmXM1FHXH_itQashbrqrOSVj-hvBCpmkIIGE,81
10
- codebind-0.3.0.dist-info/entry_points.txt,sha256=GiPcU63sULDlywJSKigAS4M3vaziYIrubXOb3fAg2_c,48
11
- codebind-0.3.0.dist-info/METADATA,sha256=wPZj3RGdkOU_mJLuUGsq3Du_C76P_t0jGMdtLwugeMw,3688
12
- codebind-0.3.0.dist-info/RECORD,,