nanoPyCodeAgent 0.2.0__py3-none-any.whl → 0.4.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.
- nanopycodeagent/agent.py +66 -124
- nanopycodeagent/bash_tool.py +77 -0
- nanopycodeagent/settings.py +35 -0
- nanopycodeagent/terminal.py +29 -0
- {nanopycodeagent-0.2.0.dist-info → nanopycodeagent-0.4.0.dist-info}/METADATA +1 -1
- nanopycodeagent-0.4.0.dist-info/RECORD +10 -0
- nanopycodeagent-0.2.0.dist-info/RECORD +0 -7
- {nanopycodeagent-0.2.0.dist-info → nanopycodeagent-0.4.0.dist-info}/WHEEL +0 -0
- {nanopycodeagent-0.2.0.dist-info → nanopycodeagent-0.4.0.dist-info}/entry_points.txt +0 -0
- {nanopycodeagent-0.2.0.dist-info → nanopycodeagent-0.4.0.dist-info}/licenses/LICENSE +0 -0
nanopycodeagent/agent.py
CHANGED
|
@@ -1,106 +1,59 @@
|
|
|
1
1
|
"""A minimal agent loop built on the Anthropic Python SDK.
|
|
2
2
|
|
|
3
3
|
Run the program, type a message, and Agent replies. The full conversation is
|
|
4
|
-
kept in memory so each turn has context.
|
|
4
|
+
kept in memory so each turn has context. The model can call a single ``bash``
|
|
5
|
+
tool to run shell commands; every command and its output are echoed to the
|
|
6
|
+
terminal as they happen. Type ``/exit`` to quit.
|
|
7
|
+
|
|
8
|
+
The loop handles only the happy path: anything unexpected — a network error,
|
|
9
|
+
a Ctrl-C mid-turn — crashes the session, and restarting it is the recovery.
|
|
10
|
+
That trade keeps the core flow readable; the hardened variant it replaced is
|
|
11
|
+
preserved at the ``hardened-agent-loop`` tag.
|
|
5
12
|
"""
|
|
6
13
|
|
|
7
|
-
import json
|
|
8
14
|
import os
|
|
9
|
-
from pathlib import Path
|
|
10
15
|
|
|
11
16
|
import anthropic
|
|
12
|
-
from anthropic.types import MessageParam
|
|
17
|
+
from anthropic.types import MessageParam, ToolResultBlockParam, ToolUseBlock
|
|
13
18
|
|
|
14
|
-
|
|
15
|
-
|
|
19
|
+
from .bash_tool import BASH_TOOL, run_bash
|
|
20
|
+
from .settings import load_settings_env
|
|
21
|
+
from .terminal import print_tool
|
|
22
|
+
|
|
23
|
+
# The model used when ANTHROPIC_MODEL is set in neither the environment nor
|
|
24
|
+
# the config file.
|
|
16
25
|
DEFAULT_MODEL = "claude-sonnet-4-6"
|
|
17
26
|
MAX_TOKENS = 8192
|
|
18
|
-
SYSTEM_PROMPT =
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
SYSTEM_PROMPT = (
|
|
28
|
+
"You are nanoPyCodeAgent, a concise and helpful coding assistant. "
|
|
29
|
+
"Use the bash tool to inspect files, run code, and complete tasks that "
|
|
30
|
+
"need real command output instead of guessing."
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _run_one_tool(block: ToolUseBlock) -> ToolResultBlockParam:
|
|
35
|
+
"""Execute one ``tool_use`` block, echoing the command and its output."""
|
|
36
|
+
command = block.input["command"]
|
|
37
|
+
print_tool(f"[bash]$ {command}")
|
|
38
|
+
output, is_error = run_bash(command)
|
|
39
|
+
print_tool(output)
|
|
40
|
+
return {
|
|
41
|
+
"type": "tool_result",
|
|
42
|
+
"tool_use_id": block.id,
|
|
43
|
+
"content": output,
|
|
44
|
+
"is_error": is_error,
|
|
45
|
+
}
|
|
36
46
|
|
|
37
47
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def load_settings_env(path: Path | None = None) -> None:
|
|
42
|
-
"""Apply the ``env`` mapping from the config file into ``os.environ``.
|
|
43
|
-
|
|
44
|
-
``path`` defaults to the module-level ``SETTINGS_PATH`` (resolved at call
|
|
45
|
-
time, so it stays overridable). Only ``ANTHROPIC_*`` keys that are not
|
|
46
|
-
already present are set, so environment variables take precedence over the
|
|
47
|
-
config file and unrelated variables are never injected. Behaviour by case:
|
|
48
|
+
def run() -> None:
|
|
49
|
+
"""Start the read → ask → answer loop until the user types ``/exit``.
|
|
48
50
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
- Unreadable / non-UTF-8 file, malformed JSON, non-object top level, or a
|
|
52
|
-
non-object ``env``: a warning is printed and the file is otherwise
|
|
53
|
-
ignored — a bad config never blocks startup.
|
|
54
|
-
- Empty, whitespace-only, or non-string values, and values the OS rejects
|
|
55
|
-
(e.g. an embedded NUL): skipped (the documented example ships these keys
|
|
56
|
-
as empty-string placeholders).
|
|
51
|
+
A reply may include bash tool calls; they are executed and their results
|
|
52
|
+
fed back to the model until it finishes the turn without tool use.
|
|
57
53
|
"""
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return # home dir unresolvable → behave as if no config file exists
|
|
62
|
-
try:
|
|
63
|
-
raw = path.read_text(encoding="utf-8")
|
|
64
|
-
except FileNotFoundError:
|
|
65
|
-
return
|
|
66
|
-
except (OSError, UnicodeDecodeError) as exc:
|
|
67
|
-
print(f"Warning: could not read config file {path}: {exc}")
|
|
68
|
-
return
|
|
69
|
-
|
|
70
|
-
try:
|
|
71
|
-
data = json.loads(raw)
|
|
72
|
-
except json.JSONDecodeError as exc:
|
|
73
|
-
print(f"Warning: ignoring malformed config file {path}: {exc}")
|
|
74
|
-
return
|
|
75
|
-
|
|
76
|
-
if not isinstance(data, dict):
|
|
77
|
-
print(f"Warning: ignoring config file {path}: top level must be an object.")
|
|
78
|
-
return
|
|
79
|
-
|
|
80
|
-
env = data.get("env", {})
|
|
81
|
-
if not isinstance(env, dict):
|
|
82
|
-
print(f"Warning: ignoring 'env' in config file {path}: it must be an object.")
|
|
83
|
-
return
|
|
84
|
-
|
|
85
|
-
for key, value in env.items():
|
|
86
|
-
# Only honor ANTHROPIC_* keys (the config's documented purpose) so a
|
|
87
|
-
# shared settings.json cannot silently inject unrelated variables such
|
|
88
|
-
# as HTTPS_PROXY into the process environment.
|
|
89
|
-
if not key.startswith("ANTHROPIC_"):
|
|
90
|
-
continue
|
|
91
|
-
if not (isinstance(value, str) and value.strip()):
|
|
92
|
-
continue
|
|
93
|
-
try:
|
|
94
|
-
os.environ.setdefault(key, value.strip())
|
|
95
|
-
except ValueError as exc:
|
|
96
|
-
# e.g. an embedded NUL in the value or '=' in the key name.
|
|
97
|
-
print(f"Warning: ignoring invalid config entry {key!r}: {exc}")
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def run() -> None:
|
|
101
|
-
"""Start the read → ask → answer loop until the user types ``/exit``."""
|
|
102
|
-
# Fill any unset ANTHROPIC_* keys from the config file (environment variables
|
|
103
|
-
# take precedence), then let the SDK read credentials from os.environ.
|
|
54
|
+
# Fill any unset ANTHROPIC_* keys from the config file (environment
|
|
55
|
+
# variables take precedence), then let the SDK read credentials from
|
|
56
|
+
# os.environ.
|
|
104
57
|
load_settings_env()
|
|
105
58
|
client = anthropic.Anthropic()
|
|
106
59
|
if client.api_key is None and client.auth_token is None:
|
|
@@ -113,62 +66,51 @@ def run() -> None:
|
|
|
113
66
|
)
|
|
114
67
|
return
|
|
115
68
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
configured_model = os.environ.get("ANTHROPIC_MODEL", "").strip()
|
|
119
|
-
model = configured_model or DEFAULT_MODEL
|
|
120
|
-
|
|
121
|
-
messages: list[MessageParam] = []
|
|
122
|
-
if configured_model:
|
|
123
|
-
print(f"nanoPyCodeAgent — using model {model} (from ANTHROPIC_MODEL).")
|
|
124
|
-
else:
|
|
125
|
-
print(
|
|
126
|
-
f"nanoPyCodeAgent — using default model {model} "
|
|
127
|
-
"(set ANTHROPIC_MODEL to override)."
|
|
128
|
-
)
|
|
69
|
+
model = os.environ.get("ANTHROPIC_MODEL", "").strip() or DEFAULT_MODEL
|
|
70
|
+
print(f"nanoPyCodeAgent — model {model} (set ANTHROPIC_MODEL to override).")
|
|
129
71
|
print("Type a message to chat, or /exit to quit.")
|
|
130
72
|
|
|
73
|
+
messages: list[MessageParam] = []
|
|
131
74
|
while True:
|
|
132
75
|
try:
|
|
133
76
|
user_input = input("\nYou> ").strip()
|
|
134
77
|
except (EOFError, KeyboardInterrupt):
|
|
135
78
|
print()
|
|
136
79
|
break
|
|
137
|
-
|
|
138
80
|
if not user_input:
|
|
139
81
|
continue
|
|
140
82
|
if user_input == "/exit":
|
|
141
83
|
break
|
|
142
84
|
|
|
143
85
|
messages.append({"role": "user", "content": user_input})
|
|
144
|
-
|
|
145
|
-
|
|
86
|
+
# The model may ask to run tools; keep streaming replies and feeding
|
|
87
|
+
# results back until it finishes a reply without tool calls.
|
|
88
|
+
while True:
|
|
146
89
|
print("\nAgent> ", end="", flush=True)
|
|
147
|
-
|
|
90
|
+
# Stream the reply so text shows up as it is generated, then grab
|
|
91
|
+
# the accumulated message for the conversation history.
|
|
92
|
+
with client.messages.stream(
|
|
148
93
|
model=model,
|
|
149
94
|
max_tokens=MAX_TOKENS,
|
|
150
95
|
system=SYSTEM_PROMPT,
|
|
96
|
+
tools=[BASH_TOOL],
|
|
151
97
|
messages=messages,
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
|
|
98
|
+
) as stream:
|
|
99
|
+
for text in stream.text_stream:
|
|
100
|
+
print(text, end="", flush=True)
|
|
101
|
+
message = stream.get_final_message()
|
|
155
102
|
print()
|
|
156
|
-
except KeyboardInterrupt:
|
|
157
|
-
# Ctrl-C while waiting on the reply cancels cleanly, mirroring the
|
|
158
|
-
# graceful quit offered at the input prompt.
|
|
159
|
-
print()
|
|
160
|
-
break
|
|
161
|
-
except anthropic.AuthenticationError:
|
|
162
|
-
print(
|
|
163
|
-
"\nAuthentication failed. Check that ANTHROPIC_API_KEY is set correctly."
|
|
164
|
-
)
|
|
165
|
-
break
|
|
166
|
-
except anthropic.APIError as exc:
|
|
167
|
-
print(f"\nRequest failed: {exc}")
|
|
168
|
-
messages.pop() # drop the unanswered user turn so history stays valid
|
|
169
|
-
continue
|
|
170
103
|
|
|
171
|
-
|
|
172
|
-
|
|
104
|
+
messages.append({"role": "assistant", "content": message.content})
|
|
105
|
+
if message.stop_reason != "tool_use":
|
|
106
|
+
break
|
|
107
|
+
# Every tool_use block needs a matching tool_result in the next
|
|
108
|
+
# user message, or the API rejects the request.
|
|
109
|
+
results = [
|
|
110
|
+
_run_one_tool(block)
|
|
111
|
+
for block in message.content
|
|
112
|
+
if block.type == "tool_use"
|
|
113
|
+
]
|
|
114
|
+
messages.append({"role": "user", "content": results})
|
|
173
115
|
|
|
174
116
|
print("Bye!")
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""The ``bash`` tool: its definition and its execution.
|
|
2
|
+
|
|
3
|
+
Each call runs a command with ``bash -c`` in a fresh shell and returns one
|
|
4
|
+
result string: stdout, then labelled stderr, then the exit code when non-zero.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import subprocess
|
|
8
|
+
|
|
9
|
+
from anthropic.types import ToolParam
|
|
10
|
+
|
|
11
|
+
# Guardrails for the bash tool: a hung command is killed after this many
|
|
12
|
+
# seconds, and results are truncated so one command cannot flood the context.
|
|
13
|
+
BASH_TIMEOUT_SECONDS = 120
|
|
14
|
+
MAX_TOOL_OUTPUT_CHARS = 20_000
|
|
15
|
+
|
|
16
|
+
BASH_TOOL: ToolParam = {
|
|
17
|
+
"name": "bash",
|
|
18
|
+
"description": (
|
|
19
|
+
"Run a command with `bash -c` on the user's machine and return its "
|
|
20
|
+
"output: stdout, then stderr (labelled), then the exit code when "
|
|
21
|
+
"non-zero. Each call is a fresh shell in the agent's working "
|
|
22
|
+
"directory, so environment variables and `cd` do not persist between "
|
|
23
|
+
"calls. Long output is truncated and long-running commands are killed "
|
|
24
|
+
"after a timeout."
|
|
25
|
+
),
|
|
26
|
+
"input_schema": {
|
|
27
|
+
"type": "object",
|
|
28
|
+
"properties": {
|
|
29
|
+
"command": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"description": "The bash command to run.",
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"required": ["command"],
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def run_bash(command: str) -> tuple[str, bool]:
|
|
40
|
+
"""Run ``command`` with ``bash -c`` and return ``(output, is_error)``.
|
|
41
|
+
|
|
42
|
+
``is_error`` is true only when the tool itself failed — here, a timeout.
|
|
43
|
+
A command that ran to completion is a successful tool call whatever its
|
|
44
|
+
exit code: the code is reported in the output text, where the model can
|
|
45
|
+
tell a negative answer (``grep`` finding nothing) from a failure.
|
|
46
|
+
Non-UTF-8 output bytes are replaced rather than raising, and stdin is
|
|
47
|
+
``/dev/null`` so a command that prompts sees EOF instead of eating the
|
|
48
|
+
user's keystrokes.
|
|
49
|
+
|
|
50
|
+
Known trades for simplicity: a background child inherits the output
|
|
51
|
+
pipes, so ``some_server &`` blocks until the timeout; the timeout kills
|
|
52
|
+
bash itself, not necessarily everything it forked; and text mode
|
|
53
|
+
translates ``\\r`` in output to ``\\n`` (universal newlines).
|
|
54
|
+
"""
|
|
55
|
+
try:
|
|
56
|
+
process = subprocess.run(
|
|
57
|
+
["bash", "-c", command],
|
|
58
|
+
capture_output=True,
|
|
59
|
+
text=True,
|
|
60
|
+
errors="replace",
|
|
61
|
+
stdin=subprocess.DEVNULL,
|
|
62
|
+
timeout=BASH_TIMEOUT_SECONDS,
|
|
63
|
+
)
|
|
64
|
+
except subprocess.TimeoutExpired:
|
|
65
|
+
return f"[command timed out after {BASH_TIMEOUT_SECONDS} seconds]", True
|
|
66
|
+
|
|
67
|
+
parts = []
|
|
68
|
+
if stdout := process.stdout.rstrip("\n"):
|
|
69
|
+
parts.append(stdout)
|
|
70
|
+
if stderr := process.stderr.rstrip("\n"):
|
|
71
|
+
parts.append("[stderr]\n" + stderr)
|
|
72
|
+
if process.returncode != 0:
|
|
73
|
+
parts.append(f"[exit code: {process.returncode}]")
|
|
74
|
+
output = "\n".join(parts) or "(no output)"
|
|
75
|
+
if len(output) > MAX_TOOL_OUTPUT_CHARS:
|
|
76
|
+
output = output[:MAX_TOOL_OUTPUT_CHARS] + "\n[... output truncated ...]"
|
|
77
|
+
return output, False
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""User-level configuration for nanoPyCodeAgent.
|
|
2
|
+
|
|
3
|
+
The config file at ``~/.nanoPyCodeAgent/settings.json`` may carry an ``env``
|
|
4
|
+
mapping that supplies ``ANTHROPIC_*`` values for keys that are not already set
|
|
5
|
+
in the environment (environment variables win).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
SETTINGS_PATH = Path.home() / ".nanoPyCodeAgent" / "settings.json"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def load_settings_env(path: Path | None = None) -> None:
|
|
16
|
+
"""Apply the ``env`` mapping from the config file into ``os.environ``.
|
|
17
|
+
|
|
18
|
+
Only ``ANTHROPIC_*`` keys that are not already present are set, so
|
|
19
|
+
environment variables take precedence over the config file and unrelated
|
|
20
|
+
variables are never injected. Empty, whitespace-only, and non-string
|
|
21
|
+
values are skipped (the documented example ships the keys as empty-string
|
|
22
|
+
placeholders). A missing file is normal; a malformed one raises — fix or
|
|
23
|
+
delete it.
|
|
24
|
+
"""
|
|
25
|
+
if path is None:
|
|
26
|
+
path = SETTINGS_PATH
|
|
27
|
+
try:
|
|
28
|
+
raw = path.read_text(encoding="utf-8")
|
|
29
|
+
except FileNotFoundError:
|
|
30
|
+
return
|
|
31
|
+
for key, value in json.loads(raw).get("env", {}).items():
|
|
32
|
+
if not key.startswith("ANTHROPIC_"):
|
|
33
|
+
continue
|
|
34
|
+
if isinstance(value, str) and value.strip():
|
|
35
|
+
os.environ.setdefault(key, value.strip())
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Background shading for tool activity in the terminal."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
# Dark gray from the 256-color palette, so echoed commands and their output
|
|
7
|
+
# read apart from the user's prompts and the model's prose.
|
|
8
|
+
_TOOL_BG = "\x1b[48;5;236m"
|
|
9
|
+
_RESET = "\x1b[0m"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _use_color() -> bool:
|
|
13
|
+
"""Whether to emit ANSI colors: a real terminal, and NO_COLOR unset."""
|
|
14
|
+
return sys.stdout.isatty() and not os.environ.get("NO_COLOR")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def print_tool(text: str) -> None:
|
|
18
|
+
"""Print tool activity (an echoed command or its output) shaded.
|
|
19
|
+
|
|
20
|
+
Each line carries its own set-background / erase-to-EOL / reset, so the
|
|
21
|
+
shading spans the full terminal width and never leaks past a line
|
|
22
|
+
boundary. Escape sequences inside ``text`` are printed as-is and may
|
|
23
|
+
disrupt the shading — a cosmetic trade for staying simple.
|
|
24
|
+
"""
|
|
25
|
+
if _use_color():
|
|
26
|
+
text = "\n".join(
|
|
27
|
+
f"{_TOOL_BG}{line}\x1b[K{_RESET}" for line in text.split("\n")
|
|
28
|
+
)
|
|
29
|
+
print(text, flush=True)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nanoPyCodeAgent
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: A nano code agent built from scratch in pure Python.
|
|
5
5
|
Project-URL: Homepage, https://github.com/minixalpha/nanoPyCodeAgent
|
|
6
6
|
Project-URL: Repository, https://github.com/minixalpha/nanoPyCodeAgent
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
nanopycodeagent/__init__.py,sha256=pYFCw7WNkZsGXbqNLkXituX4iBy86RJ4LKKAr78XwM8,70
|
|
2
|
+
nanopycodeagent/agent.py,sha256=AvfiDvaPVycbA3H_jJhD8DA_gELbcPOxrZWrZfa2hek,4354
|
|
3
|
+
nanopycodeagent/bash_tool.py,sha256=fTaGz4gfFpBykBaw4jyYeKgibOKrIfJMKqBYoBEE4og,2958
|
|
4
|
+
nanopycodeagent/settings.py,sha256=Tn9si3uRTt2IioP4GTYJGuCYVBCyTTJErq9FmuTs0pw,1296
|
|
5
|
+
nanopycodeagent/terminal.py,sha256=ZkRvKbGQGiu7SI8Pe40THVYR1-4CMBEMVEgAH8h8Ytw,995
|
|
6
|
+
nanopycodeagent-0.4.0.dist-info/METADATA,sha256=a0FBbVdPfx9K8zYiGA6fMZh2ZqC6tvFUm2OZTymLeUs,3028
|
|
7
|
+
nanopycodeagent-0.4.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
8
|
+
nanopycodeagent-0.4.0.dist-info/entry_points.txt,sha256=4gU7YNzQaf2RKlOdfebOWYNYIUk2LDoyhu3G4Vgole8,57
|
|
9
|
+
nanopycodeagent-0.4.0.dist-info/licenses/LICENSE,sha256=g24TppoiLdeF_1_-Y7H2H_eOavKseWA5utuK83Vq1sA,1067
|
|
10
|
+
nanopycodeagent-0.4.0.dist-info/RECORD,,
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
nanopycodeagent/__init__.py,sha256=pYFCw7WNkZsGXbqNLkXituX4iBy86RJ4LKKAr78XwM8,70
|
|
2
|
-
nanopycodeagent/agent.py,sha256=aAY60px0CuXllg3qICFFOz9_iJKIjf2OWWjmo6PDfzk,6628
|
|
3
|
-
nanopycodeagent-0.2.0.dist-info/METADATA,sha256=etl0E9_jsVOXrRHhTD7lcODGNzSY1VjJ5t-LKxP5adE,3028
|
|
4
|
-
nanopycodeagent-0.2.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
5
|
-
nanopycodeagent-0.2.0.dist-info/entry_points.txt,sha256=4gU7YNzQaf2RKlOdfebOWYNYIUk2LDoyhu3G4Vgole8,57
|
|
6
|
-
nanopycodeagent-0.2.0.dist-info/licenses/LICENSE,sha256=g24TppoiLdeF_1_-Y7H2H_eOavKseWA5utuK83Vq1sA,1067
|
|
7
|
-
nanopycodeagent-0.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|