codebind 0.6.23__py3-none-any.whl → 0.7.0__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/auth.py ADDED
@@ -0,0 +1,151 @@
1
+ """Interactive provider sign-in for the Codebind command line."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import dataclasses
7
+ import logging
8
+ import os
9
+ import webbrowser
10
+ from collections.abc import Mapping
11
+ from typing import Any
12
+
13
+ from .configuration import CodebindStore, load_models, load_store, save_store
14
+
15
+ AUTH_TIMEOUT_SECONDS = 300.0
16
+
17
+ logger = logging.getLogger("codebind.auth")
18
+
19
+
20
+ class AuthError(RuntimeError):
21
+ """A provider sign-in could not be completed"""
22
+
23
+
24
+ def _credential_payload(models: Any, identifier: str, value: Any) -> dict[str, Any]:
25
+ """Return one provider credential as a JSON-ready mapping.
26
+
27
+ A completed flow stores its token object in the authorization values, so
28
+ each provider serializes it through its own adapter to keep every extra
29
+ field it defines. A plain mapping is accepted for credentials a host wrote
30
+ directly.
31
+ """
32
+ if isinstance(value, Mapping):
33
+ payload = dict(value)
34
+ else:
35
+ adapter = next(
36
+ (
37
+ provider.oauth
38
+ for provider in models._runtime._providers
39
+ if provider.identifier == identifier and provider.oauth is not None
40
+ ),
41
+ None,
42
+ )
43
+ if adapter is not None:
44
+ payload = dict(adapter.serialize_tokens(value))
45
+ elif dataclasses.is_dataclass(value) and not isinstance(value, type):
46
+ payload = dataclasses.asdict(value)
47
+ else:
48
+ raise AuthError(f"cannot save {type(value).__name__} credentials for {identifier}")
49
+ if not payload:
50
+ raise AuthError(f"{identifier} returned no credentials to save")
51
+ return payload
52
+
53
+
54
+ def _account_of(payload: Any) -> str:
55
+ if not isinstance(payload, Mapping):
56
+ return ""
57
+ for key in ("email", "account", "account_id"):
58
+ value = payload.get(key)
59
+ if value:
60
+ return str(value)
61
+ return ""
62
+
63
+
64
+ def authenticate(provider: str, *, timeout: float = AUTH_TIMEOUT_SECONDS) -> str:
65
+ """Run one provider browser sign-in and save its credentials.
66
+
67
+ Returns the saved account label for the provider, or an empty string when
68
+ the provider returned no account information.
69
+ """
70
+ identifier = provider.strip().lower()
71
+ if not identifier:
72
+ raise AuthError("a provider name is required")
73
+
74
+ models = load_models()
75
+
76
+ async def run() -> dict[str, Any]:
77
+ authorization = await models.sign_in(identifier)
78
+ logger.info("opening your browser to authorize %s", identifier)
79
+ logger.info("if it does not open, visit %s", authorization.url)
80
+ try:
81
+ opened = webbrowser.open(authorization.url)
82
+ except Exception as error: # a headless host still gets the logged URL
83
+ logger.warning("could not open a browser: %s", error)
84
+ else:
85
+ if not opened:
86
+ logger.warning("no browser could be opened, use the URL above")
87
+ try:
88
+ await authorization.complete(timeout=timeout)
89
+ finally:
90
+ await authorization.close()
91
+ return _credential_payload(models, identifier, authorization.values.get(identifier))
92
+
93
+ try:
94
+ payload = asyncio.run(asyncio.wait_for(run(), timeout=timeout + 15.0))
95
+ except AuthError:
96
+ raise
97
+ except TimeoutError as error:
98
+ raise AuthError(f"{identifier} did not authorize within {timeout:g} seconds") from error
99
+ except Exception as error:
100
+ raise AuthError(f"could not sign in to {identifier}: {error}") from error
101
+
102
+ account = _account_of(payload)
103
+ path = save_store(load_store().with_provider(identifier, payload))
104
+ logger.info(
105
+ "signed in to %s%s, saved to %s", identifier, f" as {account}" if account else "", path
106
+ )
107
+ return account
108
+
109
+
110
+ def status() -> list[tuple[str, str, str]]:
111
+ """Return the stored credential state for every OAuth-capable provider."""
112
+ models = load_models()
113
+ providers = load_store().providers
114
+ authentication = models._runtime.authentication(providers)
115
+
116
+ rows: list[tuple[str, str, str]] = []
117
+ for provider in models._runtime._providers:
118
+ identifier = provider.identifier
119
+ if provider.oauth is None:
120
+ continue
121
+ if identifier in providers:
122
+ resolved = authentication.status(identifier)
123
+ if resolved.expired:
124
+ logger.warning(
125
+ "%s credentials have expired, run: codebind auth %s", identifier, identifier
126
+ )
127
+ rows.append(
128
+ (identifier, "expired" if resolved.expired else "signed in", resolved.account)
129
+ )
130
+ continue
131
+ variables = tuple(authentication.profile(identifier).environment_variables)
132
+ variable = next((name for name in variables if os.environ.get(name)), "")
133
+ rows.append((identifier, "environment" if variable else "not signed in", variable))
134
+ return rows
135
+
136
+
137
+ def logout(provider: str) -> int:
138
+ """Remove one provider credential, returning 1 when there was none."""
139
+ identifier = provider.strip().lower()
140
+ store = load_store()
141
+ if identifier not in store.providers:
142
+ logger.warning("not signed in to %s", identifier)
143
+ return 1
144
+ remaining = dict(store.providers)
145
+ remaining.pop(identifier)
146
+ path = save_store(CodebindStore(store.configuration, remaining))
147
+ logger.info("removed %s credentials from %s", identifier, path)
148
+ return 0
149
+
150
+
151
+ __all__ = ["AUTH_TIMEOUT_SECONDS", "AuthError", "authenticate", "logout", "status"]
codebind/cli.py CHANGED
@@ -1,17 +1,88 @@
1
- """Standard IPython launcher with the Codebind extension loaded."""
1
+ """Codebind command line: terminal launcher and provider authentication."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
5
  import sys
6
6
 
7
- from IPython import start_ipython
7
+ import click
8
8
 
9
9
  from . import __version__
10
+ from .logging import configure
10
11
 
11
12
 
12
- def main() -> None:
13
- """Start ordinary IPython with Codebind loaded as an extension."""
14
- if sys.argv[1:] == ["--version"]:
15
- print(f"codebind {__version__}")
13
+ @click.group(
14
+ invoke_without_command=True,
15
+ context_settings={"help_option_names": ["-h", "--help"]},
16
+ )
17
+ @click.version_option(__version__, "-V", "--version", prog_name="codebind")
18
+ @click.option(
19
+ "--log-level",
20
+ default=None,
21
+ metavar="LEVEL",
22
+ help="Log level: DEBUG, INFO, WARNING, or ERROR. Defaults to $LOG_LEVEL.",
23
+ )
24
+ @click.pass_context
25
+ def main(context: click.Context, log_level: str | None) -> None:
26
+ """Start IPython with Codebind, or run a Codebind command."""
27
+ configure(log_level)
28
+ if context.invoked_subcommand is None:
29
+ from IPython import start_ipython
30
+
31
+ start_ipython(argv=["--ext=codebind", *sys.argv[1:]])
32
+
33
+
34
+ @main.command()
35
+ @click.argument("provider")
36
+ @click.option(
37
+ "--timeout",
38
+ default=300.0,
39
+ show_default=True,
40
+ metavar="SECONDS",
41
+ help="How long to wait for browser authorization",
42
+ )
43
+ def auth(provider: str, timeout: float) -> None:
44
+ """Sign in to PROVIDER in your browser and save the credentials"""
45
+ from .auth import AuthError, authenticate
46
+
47
+ try:
48
+ authenticate(provider, timeout=timeout)
49
+ except AuthError as error:
50
+ raise click.ClickException(str(error)) from error
51
+
52
+
53
+ @main.command()
54
+ @click.option("--provider", default=None, metavar="PROVIDER", help="Show one provider only")
55
+ def status(provider: str | None) -> None:
56
+ """Show stored credentials for every supported provider"""
57
+ from .auth import status as provider_status
58
+
59
+ rows = provider_status()
60
+ if provider is not None:
61
+ rows = [row for row in rows if row[0] == provider.strip().lower()]
62
+ if not rows:
63
+ raise click.ClickException(f"no such provider: {provider}")
64
+ if not rows:
65
+ click.echo("no provider supports browser sign-in")
16
66
  return
17
- start_ipython(argv=["--ext=codebind", *sys.argv[1:]])
67
+ width = max(len(row[0]) for row in rows)
68
+ click.echo("PROVIDER".ljust(width) + " STATE")
69
+ for identifier, state, detail in rows:
70
+ click.echo(f"{identifier.ljust(width)} {state}" + (f" ({detail})" if detail else ""))
71
+
72
+
73
+ @main.command()
74
+ @click.argument("provider")
75
+ def logout(provider: str) -> None:
76
+ """Remove the stored credentials for PROVIDER"""
77
+ from .auth import AuthError, logout as provider_logout
78
+
79
+ try:
80
+ removed = provider_logout(provider)
81
+ except AuthError as error:
82
+ raise click.ClickException(str(error)) from error
83
+ if removed:
84
+ raise SystemExit(removed)
85
+
86
+
87
+ if __name__ == "__main__":
88
+ main()
codebind/configuration.py CHANGED
@@ -1,15 +1,46 @@
1
- """XDG-backed Codebind configuration."""
1
+ """XDG-backed Codebind configuration and provider credentials."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import json
6
5
  import os
6
+ import tomllib
7
7
  from dataclasses import dataclass, field
8
8
  from pathlib import Path
9
9
  from typing import Any
10
10
 
11
+ import tomli_w
11
12
  from models_provider import Models
12
13
 
14
+ CONFIGURATION_FILE = "config.toml"
15
+
16
+
17
+ @dataclass(frozen=True, slots=True)
18
+ class CodebindConfiguration:
19
+ model: str
20
+ parameters: dict[str, Any] = field(default_factory=dict)
21
+
22
+
23
+ @dataclass(frozen=True, slots=True)
24
+ class CodebindStore:
25
+ """One owner-only file holding the model choice and provider credentials."""
26
+
27
+ configuration: CodebindConfiguration | None = None
28
+ providers: dict[str, Any] = field(default_factory=dict)
29
+
30
+ def with_provider(self, provider: str, payload: Any) -> CodebindStore:
31
+ """Return a copy with one provider credential added or replaced."""
32
+ providers = dict(self.providers)
33
+ providers[provider.strip().lower()] = payload
34
+ return CodebindStore(self.configuration, providers)
35
+
36
+ def to_mapping(self) -> dict[str, Any]:
37
+ value: dict[str, Any] = {"providers": dict(self.providers)}
38
+ if self.configuration is not None:
39
+ value["model"] = self.configuration.model
40
+ if self.configuration.parameters:
41
+ value["parameters"] = dict(self.configuration.parameters)
42
+ return value
43
+
13
44
 
14
45
  def configuration_home() -> Path:
15
46
  xdg_home = os.environ.get("XDG_CONFIG_HOME")
@@ -20,41 +51,80 @@ def configuration_home() -> Path:
20
51
  return Path.home() / ".config"
21
52
 
22
53
 
23
- @dataclass(frozen=True, slots=True)
24
- class CodebindConfiguration:
25
- model: str
26
- parameters: dict[str, Any] = field(default_factory=dict)
54
+ def configuration_directory() -> Path:
55
+ return configuration_home() / "codebind"
27
56
 
28
57
 
29
- def load_configuration() -> CodebindConfiguration:
30
- path = configuration_home() / "codebind" / "configuration.json"
58
+ def configuration_path() -> Path:
59
+ return configuration_directory() / CONFIGURATION_FILE
60
+
61
+
62
+ def load_store() -> CodebindStore:
63
+ """Load the configuration file, or an empty store when it is absent."""
64
+ path = configuration_path()
31
65
  if not path.exists():
32
- raise FileNotFoundError(
33
- f"Codebind configuration is missing: {path}. "
34
- 'Create it with a JSON object such as {"model": "openai/gpt-5.6-luna"}.'
35
- )
36
- value = json.loads(path.read_text())
37
- if not isinstance(value, dict) or not isinstance(value.get("model"), str):
38
- raise ValueError(f"Codebind configuration must contain a string model: {path}")
66
+ return CodebindStore()
67
+ value = tomllib.loads(path.read_text(encoding="utf-8"))
68
+
69
+ model = value.get("model")
70
+ if model is not None and not isinstance(model, str):
71
+ raise ValueError(f"model must be a string: {path}")
39
72
  parameters = value.get("parameters", {})
40
73
  if not isinstance(parameters, dict):
41
- raise ValueError(f"Codebind model parameters must be a JSON object: {path}")
42
- return CodebindConfiguration(value["model"], parameters)
74
+ raise ValueError(f"parameters must be a table: {path}")
75
+
76
+ providers = value.get("providers", {})
77
+ if not isinstance(providers, dict):
78
+ raise ValueError(f"providers must be a table: {path}")
79
+
80
+ configuration = CodebindConfiguration(model, parameters) if isinstance(model, str) else None
81
+ return CodebindStore(configuration, providers)
82
+
83
+
84
+ def save_store(store: CodebindStore) -> Path:
85
+ """Write the configuration atomically, readable only by its owner."""
86
+ path = configuration_path()
87
+ directory = path.parent
88
+ directory.mkdir(parents=True, exist_ok=True)
89
+ try:
90
+ directory.chmod(0o700)
91
+ except OSError:
92
+ pass
93
+ payload = tomli_w.dumps(store.to_mapping())
94
+ temporary = directory / f"{path.name}.{os.getpid()}.tmp"
95
+ descriptor = os.open(temporary, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
96
+ try:
97
+ with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
98
+ handle.write(payload)
99
+ except BaseException:
100
+ temporary.unlink(missing_ok=True)
101
+ raise
102
+ os.replace(temporary, path)
103
+ os.chmod(path, 0o600)
104
+ return path
105
+
106
+
107
+ def load_configuration() -> CodebindConfiguration:
108
+ """Return the configured model, or explain how to set one."""
109
+ configuration = load_store().configuration
110
+ if configuration is None:
111
+ raise FileNotFoundError(f"no model configured in {configuration_path()}")
112
+ return configuration
43
113
 
44
114
 
45
115
  def load_models() -> Models:
46
- path = configuration_home() / "codebind" / "models.json"
47
- if not path.exists():
48
- return Models()
49
- values = json.loads(path.read_text())
50
- if not isinstance(values, dict):
51
- raise ValueError(f"Codebind model configuration must be a JSON object: {path}")
52
- return Models(values)
116
+ return Models(load_store().providers)
53
117
 
54
118
 
55
119
  __all__ = [
120
+ "CONFIGURATION_FILE",
56
121
  "CodebindConfiguration",
122
+ "CodebindStore",
123
+ "configuration_directory",
57
124
  "configuration_home",
125
+ "configuration_path",
58
126
  "load_configuration",
59
127
  "load_models",
128
+ "load_store",
129
+ "save_store",
60
130
  ]
codebind/extension.py CHANGED
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import asyncio
6
6
  import inspect
7
+ import logging
7
8
  import sys
8
9
  from typing import Any
9
10
 
@@ -29,6 +30,8 @@ from .terminal import (
29
30
  )
30
31
 
31
32
 
33
+ logger = logging.getLogger("codebind.extension")
34
+
32
35
  _STATE_ATTRIBUTE = "_codebind_extension_state"
33
36
 
34
37
 
@@ -169,7 +172,7 @@ def load_ipython_extension(ipython: InteractiveShell) -> None:
169
172
  if full_output:
170
173
  page(f"\x1b[90m{full_output}\x1b[0m" if sys.stdout.isatty() else full_output)
171
174
  else:
172
- print(f"In [{number}] has no text output.")
175
+ logger.warning("In [%s] has no text output", number)
173
176
 
174
177
  ipython.register_magic_function(output_magic, "line", "output")
175
178
 
codebind/instructions.md CHANGED
@@ -3,7 +3,7 @@ You are a facts-rooted coding agent that works with extreme batching and paralle
3
3
  ## Communication
4
4
 
5
5
  - Keep the user oriented while working. Before meaningful tool work, emit one brief Markdown update saying what you are about to do. Between tool calls, emit another update only for a new phase, an important finding, or a change of plan. Do not perform several meaningful steps silently, but do not narrate trivial operations.
6
- - Keep responses brief and direct, free of jargon and human-written with a clear intent.
6
+ - Keep responses brief and direct, free of jargon and human-written with a clear intent. Never, ever, use any emojis or cute-ish language with the user; maintain professionalism, brevity and clarity in your responses. Do not indulge in roundaboutisms with your words, especially in the concluding verses.
7
7
 
8
8
  ## Working in the live session
9
9
 
@@ -14,7 +14,7 @@ You are a facts-rooted coding agent that works with extreme batching and paralle
14
14
 
15
15
  ## Efficient execution
16
16
 
17
- - Prefer one batched IPython cell that completes as much of the task as practical instead of giving the user commands to run. Make Python scripts as compact and minified as practical, with extreme information density and as much useful work per tool call as possible. Extreme batching, density of work, parallelism and detached processes are expected for maximal efficiency; preserve correctness, safety, required concurrency, and bounded output.
17
+ - Prefer one batched IPython cell that completes as much of the task as practical instead of giving the user commands to run. Make Python scripts as compact and minified as practical, with extreme information density and as much useful work per tool call as possible. Extreme batching, density of work, parallelism and detached processes are expected for maximal efficiency; preserve correctness, safety, required concurrency, and bounded output. No need to ever include in your IPython cells code any comment whatsoever.
18
18
  - Before running any IPython cell or script containing a loop or other iteration, check whether its iterations are independent. Run independent iterations concurrently with bounded workers; never silently leave parallelizable work serial. If ordering, shared state, side effects, rate limits, or trivial workload make concurrency unsafe or wasteful, keep it sequential and briefly say why.
19
19
 
20
20
  ## Deadlines and bounded output
codebind/logging.py ADDED
@@ -0,0 +1,67 @@
1
+ """Logging for Codebind.
2
+
3
+ Records go to stderr so stdout stays reserved for data a caller may pipe. The
4
+ level comes from LOG_LEVEL, or from an explicit argument.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+ import os
11
+
12
+ from rich.console import Console
13
+ from rich.logging import RichHandler
14
+
15
+ LOGGER_NAME = "codebind"
16
+ LOG_LEVEL_ENVIRONMENT_VARIABLE = "LOG_LEVEL"
17
+
18
+ LEVELS: dict[str, int] = {
19
+ "CRITICAL": logging.CRITICAL,
20
+ "ERROR": logging.ERROR,
21
+ "WARNING": logging.WARNING,
22
+ "INFO": logging.INFO,
23
+ "DEBUG": logging.DEBUG,
24
+ }
25
+
26
+ _configured = False
27
+
28
+
29
+ def level_number(level: str | int | None, *, default: int = logging.WARNING) -> int:
30
+ """Translate a level name or number into a logging level number."""
31
+ if isinstance(level, int):
32
+ return level
33
+ if level is None:
34
+ return default
35
+ return LEVELS.get(level.strip().upper(), default)
36
+
37
+
38
+ def configure(level: str | int | None = None) -> logging.Logger:
39
+ """Install one stderr handler and set the level, once per process."""
40
+ global _configured
41
+ logger = logging.getLogger(LOGGER_NAME)
42
+ resolved = level_number(
43
+ level if level is not None else os.environ.get(LOG_LEVEL_ENVIRONMENT_VARIABLE)
44
+ )
45
+ if not _configured:
46
+ handler = RichHandler(
47
+ console=Console(stderr=True, soft_wrap=True),
48
+ show_path=False,
49
+ show_time=False,
50
+ rich_tracebacks=True,
51
+ markup=False,
52
+ )
53
+ handler.setFormatter(logging.Formatter("%(message)s"))
54
+ logger.addHandler(handler)
55
+ logger.propagate = False
56
+ _configured = True
57
+ logger.setLevel(resolved)
58
+ return logger
59
+
60
+
61
+ __all__ = [
62
+ "LEVELS",
63
+ "LOGGER_NAME",
64
+ "LOG_LEVEL_ENVIRONMENT_VARIABLE",
65
+ "configure",
66
+ "level_number",
67
+ ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codebind
3
- Version: 0.6.23
3
+ Version: 0.7.0
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
@@ -22,6 +22,8 @@ Requires-Dist: mistune>=3.3,<4
22
22
  Requires-Dist: pillow>=11
23
23
  Requires-Dist: resvg-py>=0.5
24
24
  Requires-Dist: rich>=14.1
25
+ Requires-Dist: click>=8.1
26
+ Requires-Dist: tomli-w>=1.2.0
25
27
  Requires-Python: >=3.13
26
28
  Project-URL: Repository, https://github.com/ghovax/codebind
27
29
  Project-URL: Issues, https://github.com/ghovax/codebind/issues
@@ -45,18 +47,44 @@ pip install codebind
45
47
 
46
48
  ## Configuration
47
49
 
48
- Codebind reads its private model choice from `$XDG_CONFIG_HOME/codebind/configuration.json`, or `~/.config/codebind/configuration.json` when `XDG_CONFIG_HOME` is unset:
50
+ Codebind keeps everything in one file, `$XDG_CONFIG_HOME/codebind/config.toml`, or `~/.config/codebind/config.toml` when `XDG_CONFIG_HOME` is unset:
49
51
 
50
- ```json
51
- {
52
- "model": "openai/gpt-5.6-luna",
53
- "parameters": {
54
- "reasoning_effort": "medium"
55
- }
56
- }
52
+ ```toml
53
+ model = "openai/gpt-5.6-luna"
54
+
55
+ [parameters]
56
+ reasoning_effort = "medium"
57
+
58
+ [providers.opencode]
59
+ api_key = "oc_..."
57
60
  ```
58
61
 
59
- Provider credentials use the same XDG directory in `models.json`. Keep both files readable only by their owner. Loading Codebind does not add `chat`, `model`, `models`, `Models`, or any other variable to the IPython namespace.
62
+ Codebind writes the file readable only by you and creates its directory with the same restriction. Loading Codebind does not add `chat`, `model`, `models`, `Models`, or any other variable to the IPython namespace.
63
+
64
+ ### Signing in
65
+
66
+ `codebind auth` opens your browser, waits for you to finish authorizing, and saves the resulting credentials automatically:
67
+
68
+ ```console
69
+ codebind auth openai
70
+ ```
71
+
72
+ The command logs the authorization URL in case the browser does not open, and waits five minutes by default. Pass `--timeout` to wait longer or shorter. Press Ctrl+C at any time to stop without saving.
73
+
74
+ ```console
75
+ codebind status # stored credentials for every supported provider
76
+ codebind logout openai # remove one provider credential
77
+ ```
78
+
79
+ OpenAI signs in with your ChatGPT account, so the models available to you are the ones your subscription grants. Other providers may use an API key instead; set it under `[providers.<name>]` in the configuration.
80
+
81
+ ### Logging
82
+
83
+ Records go to stderr, so stdout stays clean for piping. The level comes from `LOG_LEVEL` or `--log-level`:
84
+
85
+ ```console
86
+ codebind --log-level DEBUG status
87
+ ```
60
88
 
61
89
  ## Terminal IPython
62
90
 
@@ -132,5 +160,5 @@ Codebind loads itself into the notebook kernel as soon as that kernel is ready,
132
160
  ## ChatGPT account access
133
161
 
134
162
  - Models Provider owns OpenAI account authorization and token refresh.
135
- - Save the resulting provider-values object in Codebind's XDG `models.json`; Codebind loads it privately when the extension starts.
163
+ - `codebind auth openai` performs that authorization and stores the credentials under `[providers.openai]` in `config.toml`; Codebind loads them privately when the extension starts.
136
164
  - ChatGPT subscription access is separate from the public, pay-as-you-go OpenAI API and may require compatibility updates when the account protocol changes.
@@ -0,0 +1,31 @@
1
+ codebind/__init__.py,sha256=GNO_K5EPAdgiK2CWsPVVY-3bAmH2TnFJ9ReAWgSbnqM,514
2
+ codebind/async_runtime.py,sha256=ny9QJ0wrEpBcRUsd29EgPLLKUL2YJjZjZn_UbHn9WJI,654
3
+ codebind/auth.py,sha256=Upk4TxvewTSCzOH_10bCPBzsrah28Q1fDMSroIzJWbM,5488
4
+ codebind/cli.py,sha256=2V_KZWoeWFOBtL44SXF0p8vrltY0uDtnZaWiwels-hU,2619
5
+ codebind/configuration.py,sha256=4BmoMd4Uk6PFHCgnrDqNf_l8TdDtMxCB7sGiKVwXw9Q,4028
6
+ codebind/context.md,sha256=Iht9nJx7drsvjXYiTm-vG-prXx1UQU4VdMgBecWubJI,64
7
+ codebind/context.py,sha256=Lo7WmsOrS1RE3Vk_Lo0Pig8IFK_-RVQOuRPVagvOziM,6051
8
+ codebind/conversation.py,sha256=q7KXXilacg7-BBXOU80-ErRQNf0S6_DQ8YEZYWpYWcs,18309
9
+ codebind/display.py,sha256=IpxcElyNcr5olRIatEduN_d-sRwpsMlaq1UtrOXocQA,494
10
+ codebind/execution.py,sha256=jA0OeIXVlqg_qAi3mQtfGI-qgpljt6q03UPwvvSdBYI,18198
11
+ codebind/extension.py,sha256=S9lWkTCzBkOLX6GZ7QaADxIE7ZxZ8c36qjf9PqgRL4Q,12091
12
+ codebind/images.py,sha256=9bJHjXc75_YRBFTAqAWiknRTCjs8ApyGVVSxC_S5lio,4342
13
+ codebind/instructions.md,sha256=IS_LHP59veMngm6NahKe2ThyS73gkh_E4bYsTYjzpQY,5711
14
+ codebind/interrupts.py,sha256=c1pKlzhzg-hwGqme09KnImD14VB7nAH2x93nV8FCfEo,357
15
+ codebind/jupyter.py,sha256=BCOY6mZYhFfDSBApvfhQC3M5PSQL3G01XD-jPWVp7vc,9619
16
+ codebind/logging.py,sha256=4xsAAwdbXidjvBBrQZH5QNa-RH327YktyjhWt0Q6Eno,1780
17
+ codebind/model_text.py,sha256=vBWLfQG7oo66DW5f5Y-VTMzB9KJnjCXs-NP6uRgX7WM,688
18
+ codebind/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ codebind/session.py,sha256=XcuedUuIMDSTSbx-0Gr22_PeXT6Yf7JQIN8bWN4Nsqc,24468
20
+ codebind/terminal.py,sha256=ahH13jQOmYzhh3mNdwNwRaNajikF3bAvhpPPMsLo0wo,25564
21
+ codebind-0.7.0.dist-info/licenses/LICENSE,sha256=ujX46e0LFBIBTnCDY0u3Q7LV363B7CiI7QJZxIkEXlk,1073
22
+ codebind-0.7.0.data/data/share/jupyter/labextensions/codebind-jupyterlab/install.json,sha256=78rEThkfrk1pXtekX5WrG9_Y0lJYi5XBEm8n9athpFE,164
23
+ codebind-0.7.0.data/data/share/jupyter/labextensions/codebind-jupyterlab/package.json,sha256=VOc_3LpvjlTYe7He4tqKgkDk4jSvVLbAq-H8YBPueoM,1193
24
+ codebind-0.7.0.data/data/share/jupyter/labextensions/codebind-jupyterlab/static/590.0b7494e278aa27b9.js,sha256=38LN42aeTm1y6vKdKFbdtBqaL4DFQ0wrEHKO_m3IgXA,18320
25
+ codebind-0.7.0.data/data/share/jupyter/labextensions/codebind-jupyterlab/static/remoteEntry.8f763ad3faac0f5a.js,sha256=_jCtLZhtKjRLY5b-USF98UHC4WaxSXNdVQfN57-tvig,11795
26
+ codebind-0.7.0.data/data/share/jupyter/labextensions/codebind-jupyterlab/static/style.js,sha256=J3BK6e4DQMUpNS1-fJIEyP9Aiwnryqctcj2f1F8wcjQ,115
27
+ codebind-0.7.0.data/data/share/jupyter/labextensions/codebind-jupyterlab/static/third-party-licenses.json,sha256=MNToQfru1YnHAie-2Virp23yWsc5co3qG0IzrO6SEfw,20
28
+ codebind-0.7.0.dist-info/WHEEL,sha256=VOHKyukui3UMeyUkkBf5F7N5xN_6X6W7zHVvalNVVvI,81
29
+ codebind-0.7.0.dist-info/entry_points.txt,sha256=GiPcU63sULDlywJSKigAS4M3vaziYIrubXOb3fAg2_c,48
30
+ codebind-0.7.0.dist-info/METADATA,sha256=_Ct_2o0D5pSXOov6SlfSu5UP2YJlh-XnyraaTu8_iNs,10312
31
+ codebind-0.7.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.12.20
2
+ Generator: uv 0.12.21
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,29 +0,0 @@
1
- codebind/__init__.py,sha256=GNO_K5EPAdgiK2CWsPVVY-3bAmH2TnFJ9ReAWgSbnqM,514
2
- codebind/async_runtime.py,sha256=ny9QJ0wrEpBcRUsd29EgPLLKUL2YJjZjZn_UbHn9WJI,654
3
- codebind/cli.py,sha256=9LOR1L3P8sPsOOgsnpbOsA1PNUZdAj1Z9ROFKeo3EV8,423
4
- codebind/configuration.py,sha256=b9ux-MiepzAFfRhorZp4w2pdLLs44necescIGg_IKfo,1828
5
- codebind/context.md,sha256=Iht9nJx7drsvjXYiTm-vG-prXx1UQU4VdMgBecWubJI,64
6
- codebind/context.py,sha256=Lo7WmsOrS1RE3Vk_Lo0Pig8IFK_-RVQOuRPVagvOziM,6051
7
- codebind/conversation.py,sha256=q7KXXilacg7-BBXOU80-ErRQNf0S6_DQ8YEZYWpYWcs,18309
8
- codebind/display.py,sha256=IpxcElyNcr5olRIatEduN_d-sRwpsMlaq1UtrOXocQA,494
9
- codebind/execution.py,sha256=jA0OeIXVlqg_qAi3mQtfGI-qgpljt6q03UPwvvSdBYI,18198
10
- codebind/extension.py,sha256=lQVMuGCsnsf--4_xYJCIW6qnZaMENAvfLscrmZJH9oU,12017
11
- codebind/images.py,sha256=9bJHjXc75_YRBFTAqAWiknRTCjs8ApyGVVSxC_S5lio,4342
12
- codebind/instructions.md,sha256=Ru2XWuqsE-uad4tShWTUtKiCUDnmtJ1gKJ4nFhrCLhM,5420
13
- codebind/interrupts.py,sha256=c1pKlzhzg-hwGqme09KnImD14VB7nAH2x93nV8FCfEo,357
14
- codebind/jupyter.py,sha256=BCOY6mZYhFfDSBApvfhQC3M5PSQL3G01XD-jPWVp7vc,9619
15
- codebind/model_text.py,sha256=vBWLfQG7oo66DW5f5Y-VTMzB9KJnjCXs-NP6uRgX7WM,688
16
- codebind/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
- codebind/session.py,sha256=XcuedUuIMDSTSbx-0Gr22_PeXT6Yf7JQIN8bWN4Nsqc,24468
18
- codebind/terminal.py,sha256=ahH13jQOmYzhh3mNdwNwRaNajikF3bAvhpPPMsLo0wo,25564
19
- codebind-0.6.23.dist-info/licenses/LICENSE,sha256=ujX46e0LFBIBTnCDY0u3Q7LV363B7CiI7QJZxIkEXlk,1073
20
- codebind-0.6.23.data/data/share/jupyter/labextensions/codebind-jupyterlab/install.json,sha256=78rEThkfrk1pXtekX5WrG9_Y0lJYi5XBEm8n9athpFE,164
21
- codebind-0.6.23.data/data/share/jupyter/labextensions/codebind-jupyterlab/package.json,sha256=VOc_3LpvjlTYe7He4tqKgkDk4jSvVLbAq-H8YBPueoM,1193
22
- codebind-0.6.23.data/data/share/jupyter/labextensions/codebind-jupyterlab/static/590.0b7494e278aa27b9.js,sha256=38LN42aeTm1y6vKdKFbdtBqaL4DFQ0wrEHKO_m3IgXA,18320
23
- codebind-0.6.23.data/data/share/jupyter/labextensions/codebind-jupyterlab/static/remoteEntry.8f763ad3faac0f5a.js,sha256=_jCtLZhtKjRLY5b-USF98UHC4WaxSXNdVQfN57-tvig,11795
24
- codebind-0.6.23.data/data/share/jupyter/labextensions/codebind-jupyterlab/static/style.js,sha256=J3BK6e4DQMUpNS1-fJIEyP9Aiwnryqctcj2f1F8wcjQ,115
25
- codebind-0.6.23.data/data/share/jupyter/labextensions/codebind-jupyterlab/static/third-party-licenses.json,sha256=MNToQfru1YnHAie-2Virp23yWsc5co3qG0IzrO6SEfw,20
26
- codebind-0.6.23.dist-info/WHEEL,sha256=766qEDcnZQDL36MOdWBQ0gowd8OjUWAOwMNC7Vgvxts,81
27
- codebind-0.6.23.dist-info/entry_points.txt,sha256=GiPcU63sULDlywJSKigAS4M3vaziYIrubXOb3fAg2_c,48
28
- codebind-0.6.23.dist-info/METADATA,sha256=r23aX_HYgOlOlt_aOB5UX5jJGtJq13qk6n_yPXVe1M4,9291
29
- codebind-0.6.23.dist-info/RECORD,,