pygent 0.1.13__py3-none-any.whl → 0.1.15__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.
- pygent/__init__.py +3 -0
- pygent/agent.py +47 -12
- pygent/cli.py +5 -1
- pygent/config.py +40 -0
- pygent/runtime.py +21 -1
- pygent/task_manager.py +41 -5
- pygent/tools.py +11 -1
- {pygent-0.1.13.dist-info → pygent-0.1.15.dist-info}/METADATA +2 -1
- pygent-0.1.15.dist-info/RECORD +19 -0
- pygent-0.1.13.dist-info/RECORD +0 -18
- {pygent-0.1.13.dist-info → pygent-0.1.15.dist-info}/WHEEL +0 -0
- {pygent-0.1.13.dist-info → pygent-0.1.15.dist-info}/entry_points.txt +0 -0
- {pygent-0.1.13.dist-info → pygent-0.1.15.dist-info}/licenses/LICENSE +0 -0
- {pygent-0.1.13.dist-info → pygent-0.1.15.dist-info}/top_level.txt +0 -0
pygent/__init__.py
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
"""Pygent package."""
|
2
2
|
from importlib import metadata as _metadata
|
3
3
|
|
4
|
+
from .config import load_config
|
5
|
+
|
4
6
|
try:
|
5
7
|
__version__: str = _metadata.version(__name__)
|
6
8
|
except _metadata.PackageNotFoundError: # pragma: no cover - fallback for tests
|
@@ -15,6 +17,7 @@ from .task_manager import TaskManager # noqa: E402,F401
|
|
15
17
|
__all__ = [
|
16
18
|
"Agent",
|
17
19
|
"run_interactive",
|
20
|
+
"load_config",
|
18
21
|
"Model",
|
19
22
|
"OpenAIModel",
|
20
23
|
"PygentError",
|
pygent/agent.py
CHANGED
@@ -16,15 +16,20 @@ from .runtime import Runtime
|
|
16
16
|
from . import tools
|
17
17
|
from .models import Model, OpenAIModel
|
18
18
|
|
19
|
+
DEFAULT_PERSONA = os.getenv("PYGENT_PERSONA", "You are Pygent, a sandboxed coding assistant.")
|
20
|
+
|
21
|
+
def build_system_msg(persona: str) -> str:
|
22
|
+
return (
|
23
|
+
f"{persona}\n"
|
24
|
+
"Respond with JSON when you need to use a tool."
|
25
|
+
"If you need to stop or finished you task, call the `stop` tool.\n"
|
26
|
+
"You can use the following tools:\n"
|
27
|
+
f"{json.dumps(tools.TOOL_SCHEMAS, indent=2)}\n"
|
28
|
+
"You can also use the `continue` tool to request user input or continue the conversation.\n"
|
29
|
+
)
|
30
|
+
|
19
31
|
DEFAULT_MODEL = os.getenv("PYGENT_MODEL", "gpt-4.1-mini")
|
20
|
-
SYSTEM_MSG = (
|
21
|
-
"You are Pygent, a sandboxed coding assistant.\n"
|
22
|
-
"Respond with JSON when you need to use a tool."
|
23
|
-
"If you need to stop or finished you task, call the `stop` tool.\n"
|
24
|
-
"You can use the following tools:\n"
|
25
|
-
f"{json.dumps(tools.TOOL_SCHEMAS, indent=2)}\n"
|
26
|
-
"You can also use the `continue` tool to request user input or continue the conversation.\n"
|
27
|
-
)
|
32
|
+
SYSTEM_MSG = build_system_msg(DEFAULT_PERSONA)
|
28
33
|
|
29
34
|
console = Console()
|
30
35
|
|
@@ -36,15 +41,21 @@ class Agent:
|
|
36
41
|
runtime: Runtime = field(default_factory=Runtime)
|
37
42
|
model: Model = field(default_factory=OpenAIModel)
|
38
43
|
model_name: str = DEFAULT_MODEL
|
39
|
-
|
44
|
+
persona: str = DEFAULT_PERSONA
|
45
|
+
system_msg: str = field(default_factory=lambda: build_system_msg(DEFAULT_PERSONA))
|
40
46
|
history: List[Dict[str, Any]] = field(default_factory=list)
|
41
47
|
|
42
48
|
def __post_init__(self) -> None:
|
49
|
+
if not self.system_msg:
|
50
|
+
self.system_msg = build_system_msg(self.persona)
|
43
51
|
if not self.history:
|
44
52
|
self.history.append({"role": "system", "content": self.system_msg})
|
45
53
|
|
46
54
|
def step(self, user_msg: str):
|
55
|
+
"""Execute one round of interaction with the model."""
|
56
|
+
|
47
57
|
self.history.append({"role": "user", "content": user_msg})
|
58
|
+
|
48
59
|
assistant_msg = self.model.chat(
|
49
60
|
self.history, self.model_name, tools.TOOL_SCHEMAS
|
50
61
|
)
|
@@ -60,12 +71,36 @@ class Agent:
|
|
60
71
|
console.print(Panel(markdown_response, title="Resposta do Agente", title_align="left", border_style="cyan"))
|
61
72
|
return assistant_msg
|
62
73
|
|
63
|
-
def run_until_stop(
|
64
|
-
|
65
|
-
|
74
|
+
def run_until_stop(
|
75
|
+
self,
|
76
|
+
user_msg: str,
|
77
|
+
max_steps: int = 20,
|
78
|
+
step_timeout: float | None = None,
|
79
|
+
max_time: float | None = None,
|
80
|
+
) -> None:
|
81
|
+
"""Run steps until ``stop`` is called or limits are reached."""
|
82
|
+
|
83
|
+
if step_timeout is None:
|
84
|
+
env = os.getenv("PYGENT_STEP_TIMEOUT")
|
85
|
+
step_timeout = float(env) if env else None
|
86
|
+
if max_time is None:
|
87
|
+
env = os.getenv("PYGENT_TASK_TIMEOUT")
|
88
|
+
max_time = float(env) if env else None
|
89
|
+
|
66
90
|
msg = user_msg
|
91
|
+
start = time.monotonic()
|
92
|
+
self._timed_out = False
|
67
93
|
for _ in range(max_steps):
|
94
|
+
if max_time is not None and time.monotonic() - start > max_time:
|
95
|
+
self.history.append({"role": "system", "content": f"[timeout after {max_time}s]"})
|
96
|
+
self._timed_out = True
|
97
|
+
break
|
98
|
+
step_start = time.monotonic()
|
68
99
|
assistant_msg = self.step(msg)
|
100
|
+
if step_timeout is not None and time.monotonic() - step_start > step_timeout:
|
101
|
+
self.history.append({"role": "system", "content": f"[timeout after {step_timeout}s]"})
|
102
|
+
self._timed_out = True
|
103
|
+
break
|
69
104
|
calls = assistant_msg.tool_calls or []
|
70
105
|
if any(c.function.name in ("stop", "continue") for c in calls):
|
71
106
|
break
|
pygent/cli.py
CHANGED
@@ -1,12 +1,16 @@
|
|
1
1
|
"""Command-line entry point for Pygent."""
|
2
2
|
import argparse
|
3
3
|
|
4
|
-
from .
|
4
|
+
from .config import load_config
|
5
5
|
|
6
6
|
def main() -> None: # pragma: no cover
|
7
7
|
parser = argparse.ArgumentParser(prog="pygent")
|
8
8
|
parser.add_argument("--docker", dest="use_docker", action="store_true", help="run commands in a Docker container")
|
9
9
|
parser.add_argument("--no-docker", dest="use_docker", action="store_false", help="run locally")
|
10
|
+
parser.add_argument("-c", "--config", help="path to configuration file")
|
10
11
|
parser.set_defaults(use_docker=None)
|
11
12
|
args = parser.parse_args()
|
13
|
+
load_config(args.config)
|
14
|
+
from .agent import run_interactive
|
15
|
+
|
12
16
|
run_interactive(use_docker=args.use_docker)
|
pygent/config.py
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
import os
|
2
|
+
import tomllib
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Any, Dict
|
5
|
+
|
6
|
+
DEFAULT_CONFIG_FILES = [
|
7
|
+
Path("pygent.toml"),
|
8
|
+
Path.home() / ".pygent.toml",
|
9
|
+
]
|
10
|
+
|
11
|
+
def load_config(path: str | os.PathLike[str] | None = None) -> Dict[str, Any]:
|
12
|
+
"""Load configuration from a TOML file and set environment variables.
|
13
|
+
|
14
|
+
Environment variables already set take precedence over file values.
|
15
|
+
Returns the configuration dictionary.
|
16
|
+
"""
|
17
|
+
config: Dict[str, Any] = {}
|
18
|
+
paths = [Path(path)] if path else DEFAULT_CONFIG_FILES
|
19
|
+
for p in paths:
|
20
|
+
if p.is_file():
|
21
|
+
with p.open("rb") as fh:
|
22
|
+
try:
|
23
|
+
data = tomllib.load(fh)
|
24
|
+
except Exception:
|
25
|
+
continue
|
26
|
+
config.update(data)
|
27
|
+
# update environment without overwriting existing values
|
28
|
+
if "persona" in config and "PYGENT_PERSONA" not in os.environ:
|
29
|
+
os.environ["PYGENT_PERSONA"] = str(config["persona"])
|
30
|
+
if "task_personas" in config and "PYGENT_TASK_PERSONAS" not in os.environ:
|
31
|
+
if isinstance(config["task_personas"], list):
|
32
|
+
os.environ["PYGENT_TASK_PERSONAS"] = os.pathsep.join(str(p) for p in config["task_personas"])
|
33
|
+
else:
|
34
|
+
os.environ["PYGENT_TASK_PERSONAS"] = str(config["task_personas"])
|
35
|
+
if "initial_files" in config and "PYGENT_INIT_FILES" not in os.environ:
|
36
|
+
if isinstance(config["initial_files"], list):
|
37
|
+
os.environ["PYGENT_INIT_FILES"] = os.pathsep.join(str(p) for p in config["initial_files"])
|
38
|
+
else:
|
39
|
+
os.environ["PYGENT_INIT_FILES"] = str(config["initial_files"])
|
40
|
+
return config
|
pygent/runtime.py
CHANGED
@@ -18,8 +18,18 @@ except Exception: # pragma: no cover - optional dependency
|
|
18
18
|
class Runtime:
|
19
19
|
"""Executes commands in a Docker container or locally if Docker is unavailable."""
|
20
20
|
|
21
|
-
def __init__(
|
21
|
+
def __init__(
|
22
|
+
self,
|
23
|
+
image: str | None = None,
|
24
|
+
use_docker: bool | None = None,
|
25
|
+
initial_files: list[str] | None = None,
|
26
|
+
) -> None:
|
22
27
|
self.base_dir = Path(tempfile.mkdtemp(prefix="pygent_"))
|
28
|
+
if initial_files is None:
|
29
|
+
env_files = os.getenv("PYGENT_INIT_FILES")
|
30
|
+
if env_files:
|
31
|
+
initial_files = [f.strip() for f in env_files.split(os.pathsep) if f.strip()]
|
32
|
+
self._initial_files = initial_files or []
|
23
33
|
self.image = image or os.getenv("PYGENT_IMAGE", "python:3.12-slim")
|
24
34
|
env_opt = os.getenv("PYGENT_USE_DOCKER")
|
25
35
|
if use_docker is None:
|
@@ -46,6 +56,16 @@ class Runtime:
|
|
46
56
|
self.client = None
|
47
57
|
self.container = None
|
48
58
|
|
59
|
+
# populate workspace with initial files
|
60
|
+
for fp in self._initial_files:
|
61
|
+
src = Path(fp).expanduser()
|
62
|
+
dest = self.base_dir / src.name
|
63
|
+
if src.is_dir():
|
64
|
+
shutil.copytree(src, dest, dirs_exist_ok=True)
|
65
|
+
elif src.exists():
|
66
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
67
|
+
shutil.copy(src, dest)
|
68
|
+
|
49
69
|
# ---------------- public API ----------------
|
50
70
|
def bash(self, cmd: str, timeout: int = 30) -> str:
|
51
71
|
"""Run a command in the container or locally and return the output.
|
pygent/task_manager.py
CHANGED
@@ -30,14 +30,24 @@ class TaskManager:
|
|
30
30
|
|
31
31
|
def __init__(
|
32
32
|
self,
|
33
|
-
agent_factory: Callable[
|
33
|
+
agent_factory: Callable[..., "Agent"] | None = None,
|
34
34
|
max_tasks: int | None = None,
|
35
|
+
personas: list[str] | None = None,
|
35
36
|
) -> None:
|
36
37
|
from .agent import Agent # local import to avoid circular dependency
|
37
38
|
|
38
39
|
env_max = os.getenv("PYGENT_MAX_TASKS")
|
39
40
|
self.max_tasks = max_tasks if max_tasks is not None else int(env_max or "3")
|
40
|
-
|
41
|
+
if agent_factory is None:
|
42
|
+
self.agent_factory = lambda p=None: Agent(persona=p)
|
43
|
+
else:
|
44
|
+
self.agent_factory = agent_factory
|
45
|
+
env_personas = os.getenv("PYGENT_TASK_PERSONAS")
|
46
|
+
if personas is None and env_personas:
|
47
|
+
personas = [p.strip() for p in env_personas.split(os.pathsep) if p.strip()]
|
48
|
+
default_persona = os.getenv("PYGENT_PERSONA", "You are Pygent, a sandboxed coding assistant.")
|
49
|
+
self.personas = personas or [default_persona]
|
50
|
+
self._persona_idx = 0
|
41
51
|
self.tasks: Dict[str, Task] = {}
|
42
52
|
self._lock = threading.Lock()
|
43
53
|
|
@@ -47,6 +57,8 @@ class TaskManager:
|
|
47
57
|
parent_rt: Runtime,
|
48
58
|
files: list[str] | None = None,
|
49
59
|
parent_depth: int = 0,
|
60
|
+
step_timeout: float | None = None,
|
61
|
+
task_timeout: float | None = None,
|
50
62
|
) -> str:
|
51
63
|
"""Create a new agent and run ``prompt`` asynchronously."""
|
52
64
|
|
@@ -58,7 +70,24 @@ class TaskManager:
|
|
58
70
|
if active >= self.max_tasks:
|
59
71
|
raise RuntimeError(f"max {self.max_tasks} tasks reached")
|
60
72
|
|
61
|
-
|
73
|
+
if step_timeout is None:
|
74
|
+
env = os.getenv("PYGENT_STEP_TIMEOUT")
|
75
|
+
step_timeout = float(env) if env else 60*5 # default 5 minutes
|
76
|
+
if task_timeout is None:
|
77
|
+
env = os.getenv("PYGENT_TASK_TIMEOUT")
|
78
|
+
task_timeout = float(env) if env else 60*20 # default 20 minutes
|
79
|
+
|
80
|
+
persona = self.personas[self._persona_idx % len(self.personas)]
|
81
|
+
self._persona_idx += 1
|
82
|
+
try:
|
83
|
+
agent = self.agent_factory(persona)
|
84
|
+
except TypeError:
|
85
|
+
agent = self.agent_factory()
|
86
|
+
setattr(agent, "persona", persona)
|
87
|
+
if not getattr(agent, "system_msg", None):
|
88
|
+
from .agent import build_system_msg # lazy import
|
89
|
+
|
90
|
+
agent.system_msg = build_system_msg(persona)
|
62
91
|
setattr(agent.runtime, "task_depth", parent_depth + 1)
|
63
92
|
if files:
|
64
93
|
for fp in files:
|
@@ -74,8 +103,15 @@ class TaskManager:
|
|
74
103
|
|
75
104
|
def run() -> None:
|
76
105
|
try:
|
77
|
-
agent.run_until_stop(
|
78
|
-
|
106
|
+
agent.run_until_stop(
|
107
|
+
prompt,
|
108
|
+
step_timeout=step_timeout,
|
109
|
+
max_time=task_timeout,
|
110
|
+
)
|
111
|
+
if getattr(agent, "_timed_out", False):
|
112
|
+
task.status = f"timeout after {task_timeout}s"
|
113
|
+
else:
|
114
|
+
task.status = "finished"
|
79
115
|
except Exception as exc: # pragma: no cover - error propagation
|
80
116
|
task.status = f"error: {exc}"
|
81
117
|
|
pygent/tools.py
CHANGED
@@ -122,11 +122,19 @@ def _continue(rt: Runtime) -> str: # pragma: no cover - side-effect free
|
|
122
122
|
"items": {"type": "string"},
|
123
123
|
"description": "Files to copy to the sub-agent before starting",
|
124
124
|
},
|
125
|
+
"timeout": {"type": "number", "description": "Max seconds for the task"},
|
126
|
+
"step_timeout": {"type": "number", "description": "Time limit per step"},
|
125
127
|
},
|
126
128
|
"required": ["prompt"],
|
127
129
|
},
|
128
130
|
)
|
129
|
-
def _delegate_task(
|
131
|
+
def _delegate_task(
|
132
|
+
rt: Runtime,
|
133
|
+
prompt: str,
|
134
|
+
files: list[str] | None = None,
|
135
|
+
timeout: float | None = None,
|
136
|
+
step_timeout: float | None = None,
|
137
|
+
) -> str:
|
130
138
|
if getattr(rt, "task_depth", 0) >= 1:
|
131
139
|
return "error: delegation not allowed in sub-tasks"
|
132
140
|
try:
|
@@ -135,6 +143,8 @@ def _delegate_task(rt: Runtime, prompt: str, files: list[str] | None = None) ->
|
|
135
143
|
parent_rt=rt,
|
136
144
|
files=files,
|
137
145
|
parent_depth=getattr(rt, "task_depth", 0),
|
146
|
+
step_timeout=step_timeout,
|
147
|
+
task_timeout=timeout,
|
138
148
|
)
|
139
149
|
except RuntimeError as exc:
|
140
150
|
return str(exc)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: pygent
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.15
|
4
4
|
Summary: Pygent is a minimalist coding assistant that runs commands in a Docker container when available and falls back to local execution. See https://marianochaves.github.io/pygent for documentation and https://github.com/marianochaves/pygent for the source code.
|
5
5
|
Author-email: Mariano Chaves <mchaves.software@gmail.com>
|
6
6
|
Project-URL: Documentation, https://marianochaves.github.io/pygent
|
@@ -68,6 +68,7 @@ pygent
|
|
68
68
|
Use `--docker` to run commands inside a container (requires
|
69
69
|
`pygent[docker]`). Use `--no-docker` or set `PYGENT_USE_DOCKER=0`
|
70
70
|
to force local execution.
|
71
|
+
Pass `--config path/to/pygent.toml` to load settings from a file.
|
71
72
|
|
72
73
|
Type messages normally; use `/exit` to end the session. Each command is executed
|
73
74
|
in the container and the result shown in the terminal.
|
@@ -0,0 +1,19 @@
|
|
1
|
+
pygent/__init__.py,sha256=PnTyTP8ObRQfKN0_8-BendG2gGiZhPz5iP9URrlPVwU,776
|
2
|
+
pygent/__main__.py,sha256=MSmt_5Xg84uHqzTN38JwgseJK8rsJn_11A8WD99VtEo,61
|
3
|
+
pygent/agent.py,sha256=0yo2K47PgvQJgH_y2xCCkjINks1pYoTQpztPn2J0srk,4437
|
4
|
+
pygent/cli.py,sha256=MixuFYGWdZXka6p6ccl4uEoGKFAS5l3xDdUPzcq7y3g,670
|
5
|
+
pygent/config.py,sha256=4Yn7kV62RaXa-DI5IqurgWvFZNNykjS0PS4MsUrFEzo,1636
|
6
|
+
pygent/errors.py,sha256=s5FBg_v94coSgMh7cfkP4hVXafViGYgCY8QiT698-c4,155
|
7
|
+
pygent/models.py,sha256=j3670gjUtvQRGZ5wqGDcQ7ZJVTdT5WiwL7nWTokeYzg,1141
|
8
|
+
pygent/openai_compat.py,sha256=cyWFtXt6sDfOlsZd3FuRxbcZMm3WU-DLPBQpbmcuiW8,2617
|
9
|
+
pygent/py.typed,sha256=0Wh72UpGSn4lSGW-u3xMV9kxcBHMdwE15IGUqiJTwqo,52
|
10
|
+
pygent/runtime.py,sha256=dXk4mcYWdc3UzWN4WgyH-fsAUOlqR9L7cYAAf55Gu50,5121
|
11
|
+
pygent/task_manager.py,sha256=pO4A9nAfeUylHc2h94K5JUPXTy8MXEtBbw_oYb8oOQk,5106
|
12
|
+
pygent/tools.py,sha256=xHpUpG2QcBEvwTilpw8BDlYWY3KG9mItSt0FoKNkohA,5585
|
13
|
+
pygent/ui.py,sha256=xqPAvweghPOBBvoD72HzhN6zlXew_3inb8AN7Ck2zpQ,1328
|
14
|
+
pygent-0.1.15.dist-info/licenses/LICENSE,sha256=rIktBU2VR4kHzsWul64cbom2zHIgGqYmABoZwSur6T8,1071
|
15
|
+
pygent-0.1.15.dist-info/METADATA,sha256=Im7H9V0OHX487HxfIrYvN4m8HNElXzdT6kViNizeAw8,4344
|
16
|
+
pygent-0.1.15.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
17
|
+
pygent-0.1.15.dist-info/entry_points.txt,sha256=b9j216E5UpuMrQWRZrwyEmacNEAYvw1tCKkZqdIVIOc,70
|
18
|
+
pygent-0.1.15.dist-info/top_level.txt,sha256=P26IYsb-ThK5IkGP_bRuGJQ0Q_Y8JCcbYqVpvULdxDw,7
|
19
|
+
pygent-0.1.15.dist-info/RECORD,,
|
pygent-0.1.13.dist-info/RECORD
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
pygent/__init__.py,sha256=8TB-wzDeQV0YANON-q7sUkzlIyAmTN6gyz4R-axmoYU,724
|
2
|
-
pygent/__main__.py,sha256=MSmt_5Xg84uHqzTN38JwgseJK8rsJn_11A8WD99VtEo,61
|
3
|
-
pygent/agent.py,sha256=6CfSg943GoT6roj5ZAH8nC5UrfxqmwEhIXHMYad2SFw,3073
|
4
|
-
pygent/cli.py,sha256=Hz2FZeNMVhxoT5DjCqphXla3TisGJtPEz921LEcpxrA,527
|
5
|
-
pygent/errors.py,sha256=s5FBg_v94coSgMh7cfkP4hVXafViGYgCY8QiT698-c4,155
|
6
|
-
pygent/models.py,sha256=j3670gjUtvQRGZ5wqGDcQ7ZJVTdT5WiwL7nWTokeYzg,1141
|
7
|
-
pygent/openai_compat.py,sha256=cyWFtXt6sDfOlsZd3FuRxbcZMm3WU-DLPBQpbmcuiW8,2617
|
8
|
-
pygent/py.typed,sha256=0Wh72UpGSn4lSGW-u3xMV9kxcBHMdwE15IGUqiJTwqo,52
|
9
|
-
pygent/runtime.py,sha256=KckSdTSBIooz181ef8krXbSgqTxIaYDMdCy0ryOi7KQ,4386
|
10
|
-
pygent/task_manager.py,sha256=Dq_rezUYC4bzXc3kQ9zLIcjUv3kNc9xzB9xTSDJAzFA,3482
|
11
|
-
pygent/tools.py,sha256=HbV01OlRRfzRUf4p6lujtKcHEc43vduC4vv2hq9N1bM,5252
|
12
|
-
pygent/ui.py,sha256=xqPAvweghPOBBvoD72HzhN6zlXew_3inb8AN7Ck2zpQ,1328
|
13
|
-
pygent-0.1.13.dist-info/licenses/LICENSE,sha256=rIktBU2VR4kHzsWul64cbom2zHIgGqYmABoZwSur6T8,1071
|
14
|
-
pygent-0.1.13.dist-info/METADATA,sha256=Mop-IQFJad5onR92vwdBexRjVk7FWIHE5_k3y9hS-wI,4278
|
15
|
-
pygent-0.1.13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
16
|
-
pygent-0.1.13.dist-info/entry_points.txt,sha256=b9j216E5UpuMrQWRZrwyEmacNEAYvw1tCKkZqdIVIOc,70
|
17
|
-
pygent-0.1.13.dist-info/top_level.txt,sha256=P26IYsb-ThK5IkGP_bRuGJQ0Q_Y8JCcbYqVpvULdxDw,7
|
18
|
-
pygent-0.1.13.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|