Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9860ee3286 | ||
|
|
93b7f4841d | ||
|
|
0d036fbda7 | ||
|
|
9f8e5a3a19 | ||
|
|
ba7a45eb62 | ||
|
|
b52f8c1ae7 | ||
|
|
6777fed3bd | ||
|
|
2563c7fe08 | ||
|
|
6e20e7e61e | ||
|
|
919a9092b6 | ||
|
|
5ecb678c49 |
23
.github/workflows/pylint.yml
vendored
23
.github/workflows/pylint.yml
vendored
@@ -1,23 +0,0 @@
|
||||
name: Pylint
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.8", "3.9", "3.10"]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install pylint
|
||||
- name: Analysing the code with pylint
|
||||
run: |
|
||||
pylint $(git ls-files '*.py')
|
||||
1
.python-version
Normal file
1
.python-version
Normal file
@@ -0,0 +1 @@
|
||||
3.13
|
||||
16
README.md
16
README.md
@@ -2,11 +2,21 @@
|
||||
|
||||
PwnPi Amora is a wireless keystroke injection tool built on the Raspberry Pi Pico W using CircuitPython.
|
||||
|
||||
The project makes an attempt to provide a fully featured web IDE for building
|
||||
and deploying keystroke injection scripts.
|
||||
Web IDE for and deploying keystroke injection scripts over WiFi.
|
||||
|
||||

|
||||
|
||||
## Getting Started
|
||||
|
||||
Check out the [wiki](https://github.com/lavafroth/pwnpi-amora/wiki/Quick-Start) for getting started.
|
||||
- Clone this repo and run the following inside the project directory:
|
||||
|
||||
```sh
|
||||
uv sync
|
||||
source .venv/bin/activate
|
||||
circuitpython-tool uf2 install
|
||||
circup install asyncio adafruit_hid adafruit_httpserver
|
||||
```
|
||||
|
||||
Copy all files inside the `src/` directory to the board.
|
||||
|
||||
Check out the [wiki](https://github.com/lavafroth/pwnpi-amora/wiki/) for getting started.
|
||||
|
||||
86
build.py
86
build.py
@@ -1,86 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Builder script to compile .py files to .mpy bytecode using mpy-cross
|
||||
"""
|
||||
|
||||
import glob
|
||||
from errno import ENOTDIR
|
||||
from os import listdir, makedirs
|
||||
from os.path import join, splitext
|
||||
from shutil import copy, copytree, rmtree
|
||||
from subprocess import PIPE, Popen
|
||||
|
||||
SRC = "src"
|
||||
DST = "build"
|
||||
|
||||
|
||||
def recursive_copy(src: str, dst: str):
|
||||
"""
|
||||
Copy a file or directory from src to dst.
|
||||
|
||||
Parameters:
|
||||
src (str): The path of the source file or directory.
|
||||
dst (str): The path of the destination file or directory.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
try:
|
||||
copytree(src, dst)
|
||||
except OSError as exc:
|
||||
if exc.errno == ENOTDIR:
|
||||
copy(src, dst)
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
def to_compile(name: str) -> str:
|
||||
"""
|
||||
Check if a given file is a Python source file that needs to be compiled.
|
||||
|
||||
Parameters:
|
||||
name (str): The name of the file.
|
||||
|
||||
Returns:
|
||||
str: The name of the file without the extension if it need compilation,
|
||||
otherwise None.
|
||||
"""
|
||||
base, ext = splitext(name)
|
||||
if base not in ("code", "boot") and ext == ".py":
|
||||
return base
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Use mpy-cross to compile .py files to .mpy bytecode
|
||||
"""
|
||||
|
||||
# Remove the build directory if it exists, then create it again
|
||||
rmtree(DST, ignore_errors=True)
|
||||
makedirs(DST, exist_ok=True)
|
||||
makedirs(join(DST, "lib"), exist_ok=True)
|
||||
|
||||
# Find the path of the mpy-cross binary
|
||||
mpy_cross_bin = join(".", glob.glob("mpy-cross.static*")[0])
|
||||
|
||||
# Process each entry in the source directory
|
||||
for entry in listdir(SRC):
|
||||
src_path = join(SRC, entry)
|
||||
# If the entry is a Python source file that needs to be compiled
|
||||
if name := to_compile(entry):
|
||||
# Compile the file using mpy-cross
|
||||
with Popen(
|
||||
[mpy_cross_bin, "-o",
|
||||
join(DST, "lib", f"{name}.mpy"), src_path],
|
||||
stdout=PIPE,
|
||||
) as process:
|
||||
process.communicate()
|
||||
else:
|
||||
# Copy the file or directory to the build directory
|
||||
dst_path = join(DST, entry)
|
||||
recursive_copy(src_path, dst_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
10
pyproject.toml
Normal file
10
pyproject.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[project]
|
||||
name = "pwnpi-amora"
|
||||
version = "0.1.0"
|
||||
description = "A wireless keystroke injection tool built on the Raspberry Pi Pico W using CircuitPython."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"circuitpython-tool>=0.11.0",
|
||||
"circup>=2.3.0",
|
||||
]
|
||||
@@ -23,7 +23,7 @@ async def setup_server():
|
||||
"""
|
||||
wifi.radio.start_ap(ssid=os.getenv("SSID"), password=os.getenv("PASSWORD"))
|
||||
pool = socketpool.SocketPool(wifi.radio)
|
||||
server = Server(pool)
|
||||
server = Server(pool, debug=True)
|
||||
|
||||
@server.route("/")
|
||||
def base(request: Request):
|
||||
@@ -41,7 +41,7 @@ async def setup_server():
|
||||
def api(request: Request):
|
||||
return handle(request)
|
||||
|
||||
server.serve_forever(str(wifi.radio.ipv4_address_ap))
|
||||
server.serve_forever("0.0.0.0", 80)
|
||||
|
||||
|
||||
async def main():
|
||||
|
||||
105
src/ducky.py
105
src/ducky.py
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Logic to interpret and execute user defined ducky script payloads.
|
||||
Execute user defined python scripts in a sandbox
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
import usb_hid
|
||||
@@ -10,9 +11,13 @@ from adafruit_hid.keyboard import Keyboard
|
||||
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS as KeyboardLayout
|
||||
from adafruit_hid.keycode import Keycode
|
||||
from board import LED
|
||||
|
||||
import digitalio
|
||||
from logs import info, warn
|
||||
|
||||
led = digitalio.DigitalInOut(LED)
|
||||
led.direction = digitalio.Direction.OUTPUT
|
||||
|
||||
|
||||
# uncomment these lines for non_US keyboards
|
||||
# replace LANG with appropriate language
|
||||
# from keyboard_layout_win_LANG import KeyboardLayout
|
||||
@@ -23,95 +28,54 @@ kbd = Keyboard(usb_hid.devices)
|
||||
layout = KeyboardLayout(kbd)
|
||||
|
||||
|
||||
def delay(millis):
|
||||
def delay(millis=1000.0):
|
||||
"""
|
||||
Sleep, do absolutely nothing.
|
||||
"""
|
||||
time.sleep(float(millis) / 1000)
|
||||
|
||||
|
||||
def prefix_checker(line: str):
|
||||
"""
|
||||
Returns a function that checks if line begins with
|
||||
any of the prefixes supplied to it.
|
||||
|
||||
Syntax sugar so that we can later use it in conditional
|
||||
statements like if something := checker("foo", "bar")
|
||||
"""
|
||||
|
||||
def checker(*prefixes):
|
||||
for prefix in prefixes:
|
||||
if line.startswith(prefix):
|
||||
return line[len(prefix) + 1:]
|
||||
return None
|
||||
|
||||
return checker
|
||||
|
||||
|
||||
def press_keys(line: str):
|
||||
def press(*keys):
|
||||
"""
|
||||
Press all the keys and then release them.
|
||||
Really useful for keyboard shortcuts like Meta+R.
|
||||
"""
|
||||
# loop on each key filtering empty values
|
||||
for key in filter(None, line.upper().split(" ")):
|
||||
|
||||
# If a single key is invalid, fail fast
|
||||
for key in keys:
|
||||
if key not in Keycode.__dict__:
|
||||
warn(f"unknown key: <{key}>")
|
||||
return
|
||||
|
||||
for key in keys:
|
||||
if command_keycode := Keycode.__dict__.get(key):
|
||||
# If this is a valid key, send its keycode
|
||||
kbd.press(command_keycode)
|
||||
continue
|
||||
# If it's not a known key name, log it for diagnosis
|
||||
warn(f"unknown key: <{key}>")
|
||||
kbd.release_all()
|
||||
|
||||
|
||||
def repeat(contents: str, times: int):
|
||||
"""
|
||||
If the contents supplied is not empty or None,
|
||||
repeat those ducky script lines `times` times.
|
||||
"""
|
||||
if not contents:
|
||||
return
|
||||
for _ in range(times):
|
||||
run_script(contents)
|
||||
def toggle_led(value: bool):
|
||||
led.value = value
|
||||
|
||||
|
||||
def run_script(contents):
|
||||
def run_script(contents: str):
|
||||
"""
|
||||
Interpret the ducky script and execute it line by line
|
||||
"""
|
||||
default_delay = 0
|
||||
previous_line = None
|
||||
for line in filter(None, contents.splitlines()):
|
||||
line = line.rstrip()
|
||||
after = prefix_checker(line)
|
||||
|
||||
if times := after("REPEAT"):
|
||||
repeat(previous_line, int(times))
|
||||
|
||||
elif after("REM"):
|
||||
continue
|
||||
elif (millis := after("DELAY")) is not None:
|
||||
delay(millis or default_delay)
|
||||
elif message := after("PRINT"):
|
||||
info(message)
|
||||
elif path := after("IMPORT"):
|
||||
run_script_file(path)
|
||||
elif millis := after("DEFAULT_DELAY", "DEFAULTDELAY"):
|
||||
default_delay = int(millis)
|
||||
elif after("LED") is not None:
|
||||
LED.value ^= True
|
||||
elif string := after("STRING"):
|
||||
layout.write(string)
|
||||
else:
|
||||
press_keys(line)
|
||||
|
||||
previous_line = line
|
||||
delay(default_delay)
|
||||
exec(
|
||||
contents,
|
||||
dict(
|
||||
print=info,
|
||||
write=layout.write,
|
||||
toggle_led=toggle_led,
|
||||
delay=delay,
|
||||
press=press,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def run_script_file(path: str):
|
||||
"""
|
||||
Try reading and running a ducky script from the supplied path.
|
||||
Try reading and running a python script from the supplied path.
|
||||
"""
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as handle:
|
||||
@@ -122,11 +86,10 @@ def run_script_file(path: str):
|
||||
|
||||
async def run_boot_script():
|
||||
"""
|
||||
If a script with the name 'boot.dd' exists,
|
||||
run it without user interaction on boot.
|
||||
Try reading and running a python script from the supplied path.
|
||||
"""
|
||||
try:
|
||||
with open("payloads/boot.dd", "r", encoding="utf-8") as handle:
|
||||
with open("boot_payload.py", "r", encoding="utf-8") as handle:
|
||||
run_script(handle.read())
|
||||
except OSError:
|
||||
info("boot script does not exist, skipping its execution")
|
||||
info("not boot script set")
|
||||
|
||||
@@ -6,7 +6,7 @@ for the bottom pane of the Web UI.
|
||||
logs = []
|
||||
|
||||
|
||||
def consume() -> str:
|
||||
def consume():
|
||||
"""
|
||||
Convert all the log entries from the module's global mutable
|
||||
list to json return them, clearing the list after the dump.
|
||||
@@ -16,15 +16,15 @@ def consume() -> str:
|
||||
return dump
|
||||
|
||||
|
||||
def info(message: str):
|
||||
def info(message):
|
||||
"""
|
||||
Add a log entry with the message prepended with the info marker
|
||||
"""
|
||||
logs.append("info: " + message)
|
||||
logs.append("info: " + str(message))
|
||||
|
||||
|
||||
def warn(message: str):
|
||||
"""
|
||||
Add a log entry with the message prepended with the warning marker
|
||||
"""
|
||||
logs.append("warning: " + message)
|
||||
logs.append("warn: " + str(message))
|
||||
|
||||
@@ -11,17 +11,22 @@
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="hide files">
|
||||
</div>
|
||||
<div class="hide files"></div>
|
||||
<div class="sidebar">
|
||||
<div class="documents icon"></div>
|
||||
<div class="run icon"></div>
|
||||
<div class="add icon"></div>
|
||||
<button id="documents" class="icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="#fff" viewBox="0 0 24 24"><path d="M2 4.75C2 3.784 2.784 3 3.75 3h4.971c.58 0 1.12.286 1.447.765l1.404 2.063c.046.069.124.11.207.11h8.471c.966 0 1.75.783 1.75 1.75V19.25A1.75 1.75 0 0 1 20.25 21H3.75A1.75 1.75 0 0 1 2 19.25Zm1.75-.25a.25.25 0 0 0-.25.25v14.5c0 .138.112.25.25.25h16.5a.25.25 0 0 0 .25-.25V7.687a.25.25 0 0 0-.25-.25h-8.471a1.75 1.75 0 0 1-1.447-.765L8.928 4.61a.252.252 0 0 0-.208-.11Z" stroke="#fff"></path></svg>
|
||||
</button>
|
||||
<button id="run" class="icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M5 4.98951C5 4.01835 5 3.53277 5.20249 3.2651C5.37889 3.03191 5.64852 2.88761 5.9404 2.87018C6.27544 2.85017 6.67946 3.11953 7.48752 3.65823L18.0031 10.6686C18.6708 11.1137 19.0046 11.3363 19.1209 11.6168C19.2227 11.8621 19.2227 12.1377 19.1209 12.383C19.0046 12.6635 18.6708 12.886 18.0031 13.3312L7.48752 20.3415C6.67946 20.8802 6.27544 21.1496 5.9404 21.1296C5.64852 21.1122 5.37889 20.9679 5.20249 20.7347C5 20.467 5 19.9814 5 19.0103V4.98951Z" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
<button id="add" class="icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M11.75 4.5a.75.75 0 0 1 .75.75V11h5.75a.75.75 0 0 1 0 1.5H12.5v5.75a.75.75 0 0 1-1.5 0V12.5H5.25a.75.75 0 0 1 0-1.5H11V5.25a.75.75 0 0 1 .75-.75Z" fill="#fff"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="editorarea">
|
||||
<div class="title-bar">
|
||||
<input class="title" placeholder="new file"></input>
|
||||
<button class="title-btn">
|
||||
<button id="title_btn">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-check" viewBox="0 0 16 16">
|
||||
<path d="M10.97 4.97a.75.75 0 0 1 1.07 1.05l-3.99 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425a.267.267 0 0 1 .02-.022z"/>
|
||||
</svg>
|
||||
|
||||
@@ -26,13 +26,6 @@ body {
|
||||
border-right: #444 solid 0.1rem;
|
||||
}
|
||||
|
||||
.sidebar > div {
|
||||
background-size: 1rem;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.logs {
|
||||
height: calc(30vh - 2rem);
|
||||
background: #000;
|
||||
@@ -60,33 +53,20 @@ body {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.run {
|
||||
background: url('data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTUgNC45ODk1MUM1IDQuMDE4MzUgNSAzLjUzMjc3IDUuMjAyNDkgMy4yNjUxQzUuMzc4ODkgMy4wMzE5MSA1LjY0ODUyIDIuODg3NjEgNS45NDA0IDIuODcwMThDNi4yNzU0NCAyLjg1MDE3IDYuNjc5NDYgMy4xMTk1MyA3LjQ4NzUyIDMuNjU4MjNMMTguMDAzMSAxMC42Njg2QzE4LjY3MDggMTEuMTEzNyAxOS4wMDQ2IDExLjMzNjMgMTkuMTIwOSAxMS42MTY4QzE5LjIyMjcgMTEuODYyMSAxOS4yMjI3IDEyLjEzNzcgMTkuMTIwOSAxMi4zODNDMTkuMDA0NiAxMi42NjM1IDE4LjY3MDggMTIuODg2IDE4LjAwMzEgMTMuMzMxMkw3LjQ4NzUyIDIwLjM0MTVDNi42Nzk0NiAyMC44ODAyIDYuMjc1NDQgMjEuMTQ5NiA1Ljk0MDQgMjEuMTI5NkM1LjY0ODUyIDIxLjExMjIgNS4zNzg4OSAyMC45Njc5IDUuMjAyNDkgMjAuNzM0N0M1IDIwLjQ2NyA1IDE5Ljk4MTQgNSAxOS4wMTAzVjQuOTg5NTFaIiBzdHJva2U9IiNmZmYiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PC9zdmc+Cg==');
|
||||
}
|
||||
|
||||
.documents {
|
||||
background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTIgNC43NUMyIDMuNzg0IDIuNzg0IDMgMy43NSAzaDQuOTcxYy41OCAwIDEuMTIuMjg2IDEuNDQ3Ljc2NWwxLjQwNCAyLjA2M2MuMDQ2LjA2OS4xMjQuMTEuMjA3LjExaDguNDcxYy45NjYgMCAxLjc1Ljc4MyAxLjc1IDEuNzVWMTkuMjVBMS43NSAxLjc1IDAgMCAxIDIwLjI1IDIxSDMuNzVBMS43NSAxLjc1IDAgMCAxIDIgMTkuMjVabTEuNzUtLjI1YS4yNS4yNSAwIDAgMC0uMjUuMjV2MTQuNWMwIC4xMzguMTEyLjI1LjI1LjI1aDE2LjVhLjI1LjI1IDAgMCAwIC4yNS0uMjVWNy42ODdhLjI1LjI1IDAgMCAwLS4yNS0uMjVoLTguNDcxYTEuNzUgMS43NSAwIDAgMS0xLjQ0Ny0uNzY1TDguOTI4IDQuNjFhLjI1Mi4yNTIgMCAwIDAtLjIwOC0uMTFaIiBzdHJva2U9IiNmZmYiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjwvcGF0aD48L3N2Zz4K');
|
||||
}
|
||||
|
||||
|
||||
.delete {
|
||||
background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTkuMDM2IDcuOTc2YS43NS43NSAwIDAgMC0xLjA2IDEuMDZMMTAuOTM5IDEybC0yLjk2MyAyLjk2M2EuNzUuNzUgMCAxIDAgMS4wNiAxLjA2TDEyIDEzLjA2bDIuOTYzIDIuOTY0YS43NS43NSAwIDAgMCAxLjA2MS0xLjA2TDEzLjA2MSAxMmwyLjk2My0yLjk2NGEuNzUuNzUgMCAxIDAtMS4wNi0xLjA2TDEyIDEwLjkzOSA5LjAzNiA3Ljk3NloiIGZpbGw9IiNmZmYiPjwvcGF0aD48cGF0aCBkPSJNMTIgMWM2LjA3NSAwIDExIDQuOTI1IDExIDExcy00LjkyNSAxMS0xMSAxMVMxIDE4LjA3NSAxIDEyIDUuOTI1IDEgMTIgMVpNMi41IDEyYTkuNSA5LjUgMCAwIDAgOS41IDkuNSA5LjUgOS41IDAgMCAwIDkuNS05LjVBOS41IDkuNSAwIDAgMCAxMiAyLjUgOS41IDkuNSAwIDAgMCAyLjUgMTJaIiBmaWxsPSIjZmZmIj48L3BhdGg+PC9zdmc+Cg==');
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
cursor: pointer;
|
||||
button {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border: 0.5rem solid transparent;
|
||||
background: #222;
|
||||
}
|
||||
|
||||
.icon:active {
|
||||
background-color: #444;
|
||||
transition: 0.25s background-color;
|
||||
}
|
||||
|
||||
.add {
|
||||
background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMCAyMCI+PHBhdGggZD0iTTExLjc1IDQuNWEuNzUuNzUgMCAwIDEgLjc1Ljc1VjExaDUuNzVhLjc1Ljc1IDAgMCAxIDAgMS41SDEyLjV2NS43NWEuNzUuNzUgMCAwIDEtMS41IDBWMTIuNUg1LjI1YS43NS43NSAwIDAgMSAwLTEuNUgxMVY1LjI1YS43NS43NSAwIDAgMSAuNzUtLjc1WiIgZmlsbD0iI2ZmZiI+PC9wYXRoPjwvc3ZnPgo=');
|
||||
button:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
.editorarea {
|
||||
@@ -112,9 +92,9 @@ body {
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
.editorarea > .title-bar > .title-btn {
|
||||
#title_btn {
|
||||
border: none;
|
||||
background: #222;
|
||||
display: none;
|
||||
padding: 0 0.4rem 0 0.4rem;
|
||||
}
|
||||
|
||||
@@ -156,4 +136,4 @@ body {
|
||||
.show {
|
||||
width: calc(100vw - 3rem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,7 @@ const waitTime = 500
|
||||
const files = document.querySelector(".files")
|
||||
const editor = document.querySelector('.editor')
|
||||
const logs = document.querySelector('.logs')
|
||||
const documents_icon = document.querySelector('.documents')
|
||||
const run_icon = document.querySelector('.run')
|
||||
const add_icon = document.querySelector('.add')
|
||||
const title = document.querySelector('.editorarea > .title-bar > .title')
|
||||
const title_button = document.querySelector('.editorarea > .title-bar > .title-btn')
|
||||
let timer
|
||||
|
||||
function doApi(message) {
|
||||
@@ -22,6 +18,21 @@ function doApi(message) {
|
||||
body: JSON.stringify(message)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
editor.addEventListener("keydown", (e) => {
|
||||
if (e.keyCode === 9) {
|
||||
e.preventDefault();
|
||||
|
||||
editor.setRangeText(
|
||||
" ",
|
||||
editor.selectionStart,
|
||||
editor.selectionStart,
|
||||
"end"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
editor.addEventListener('keyup', (_) => {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => {
|
||||
@@ -35,8 +46,8 @@ editor.addEventListener('keyup', (_) => {
|
||||
function reload_logs() {
|
||||
doApi({'action':'logs'}).then(r => r.json()).then(body => {
|
||||
body.map(entry => {
|
||||
logs.innerText += entry + '\n'
|
||||
logs.scrollTo(0, logs.scrollHeight)
|
||||
logs.innerText += entry + '\n'
|
||||
logs.scrollTo(0, logs.scrollHeight)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -73,26 +84,33 @@ function create_file() {
|
||||
doApi({"action": "create", "filename": title.value})
|
||||
}
|
||||
|
||||
title_button.addEventListener('click', create_file);
|
||||
title_btn.addEventListener('click', create_file);
|
||||
title.addEventListener('keypress', (e) => {
|
||||
if (e.keyCode==13) {
|
||||
create_file()
|
||||
}
|
||||
})
|
||||
title.addEventListener('keyup', (_) => {
|
||||
if (title.value == "") {
|
||||
title_btn.style.display = 'none'
|
||||
} else {
|
||||
title_btn.style.display = 'block'
|
||||
}
|
||||
})
|
||||
|
||||
add_icon.addEventListener('click', () => {
|
||||
add.addEventListener('click', () => {
|
||||
editor.value = ''
|
||||
title.value = ''
|
||||
title.readOnly = false
|
||||
title.focus()
|
||||
})
|
||||
|
||||
documents_icon.addEventListener('click', () => {
|
||||
documents.addEventListener('click', () => {
|
||||
files.classList.toggle("show");
|
||||
files.classList.toggle("hide");
|
||||
});
|
||||
|
||||
run_icon.addEventListener('click', () => {
|
||||
run.addEventListener('click', () => {
|
||||
if (title.value != "") {
|
||||
doApi({"action": "run", "filename": title.value})
|
||||
} else {
|
||||
|
||||
289
uv.lock
generated
Normal file
289
uv.lock
generated
Normal file
@@ -0,0 +1,289 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[[package]]
|
||||
name = "appdirs"
|
||||
version = "1.4.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d7/d8/05696357e0311f5b5c316d7b95f46c669dd9c15aaeecbb48c7d0aeb88c40/appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", size = 13470, upload-time = "2020-05-11T07:59:51.037Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/00/2344469e2084fb287c2e0b57b72910309874c3245463acd6cf5e3db69324/appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128", size = 9566, upload-time = "2020-05-11T07:59:49.499Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2025.11.12"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.4.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "circuitpython-tool"
|
||||
version = "0.11.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "humanize" },
|
||||
{ name = "platformdirs" },
|
||||
{ name = "readchar" },
|
||||
{ name = "rich" },
|
||||
{ name = "rich-click" },
|
||||
{ name = "tomlkit" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/23/12/192ca417d7302140b8bf1650c576351526ca2801b9fb3f2d3b8afd49d35b/circuitpython_tool-0.11.0.tar.gz", hash = "sha256:3966d12fbed9dd58e62a265dc7e95aaa706f83e25b2a62443daed926d968ce6a", size = 68558, upload-time = "2024-06-02T22:15:24.869Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/95/ca/48539d9a40155538400570a0cd28afb4319af4f2c1014094df327bb44c21/circuitpython_tool-0.11.0-py3-none-any.whl", hash = "sha256:203a6785842d02b6b615d0398cd78861869a728b8c8d13f39e0f3fcfc8d28219", size = 62504, upload-time = "2024-06-02T22:15:23.631Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "circup"
|
||||
version = "2.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "appdirs" },
|
||||
{ name = "click" },
|
||||
{ name = "requests" },
|
||||
{ name = "semver" },
|
||||
{ name = "toml" },
|
||||
{ name = "update-checker" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b8/1c/9f9ae44db96e5dd75bc4f95be96c99e560085d73274e805ef2bdc8231431/circup-2.3.0.tar.gz", hash = "sha256:ce5117d6353d3b3055793738b35735955d1f535566eb90a8e0f9138814b18284", size = 122943, upload-time = "2025-11-06T15:16:11.352Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/fe/160cf005ca5dbcb0454bbffe3f6dcb5744f902bdffd4317e86dfee2f4724/circup-2.3.0-py3-none-any.whl", hash = "sha256:93226d034f262bcd7ffdc5e700036ddfa853be3241fd70e4e1943bafcca98d53", size = 49016, upload-time = "2025-11-06T15:16:09.658Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humanize"
|
||||
version = "4.15.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ba/66/a3921783d54be8a6870ac4ccffcd15c4dc0dd7fcce51c6d63b8c63935276/humanize-4.15.0.tar.gz", hash = "sha256:1dd098483eb1c7ee8e32eb2e99ad1910baefa4b75c3aff3a82f4d78688993b10", size = 83599, upload-time = "2025-12-20T20:16:13.19Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/7b/bca5613a0c3b542420cf92bd5e5fb8ebd5435ce1011a091f66bb7693285e/humanize-4.15.0-py3-none-any.whl", hash = "sha256:b1186eb9f5a9749cd9cb8565aee77919dd7c8d076161cf44d70e59e3301e1769", size = 132203, upload-time = "2025-12-20T20:16:11.67Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.11"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markdown-it-py"
|
||||
version = "4.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "mdurl" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mdurl"
|
||||
version = "0.1.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "4.5.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pwnpi-amora"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "circuitpython-tool" },
|
||||
{ name = "circup" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "circuitpython-tool", specifier = ">=0.11.0" },
|
||||
{ name = "circup", specifier = ">=2.3.0" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.19.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "readchar"
|
||||
version = "4.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/dd/f8/8657b8cbb4ebeabfbdf991ac40eca8a1d1bd012011bd44ad1ed10f5cb494/readchar-4.2.1.tar.gz", hash = "sha256:91ce3faf07688de14d800592951e5575e9c7a3213738ed01d394dcc949b79adb", size = 9685, upload-time = "2024-11-04T18:28:07.757Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/10/e4b1e0e5b6b6745c8098c275b69bc9d73e9542d5c7da4f137542b499ed44/readchar-4.2.1-py3-none-any.whl", hash = "sha256:a769305cd3994bb5fa2764aa4073452dc105a4ec39068ffe6efd3c20c60acc77", size = 9350, upload-time = "2024-11-04T18:28:02.859Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.32.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "charset-normalizer" },
|
||||
{ name = "idna" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rich"
|
||||
version = "14.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "markdown-it-py" },
|
||||
{ name = "pygments" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rich-click"
|
||||
version = "1.9.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "rich" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6b/d1/b60ca6a8745e76800b50c7ee246fd73f08a3be5d8e0b551fc93c19fa1203/rich_click-1.9.5.tar.gz", hash = "sha256:48120531493f1533828da80e13e839d471979ec8d7d0ca7b35f86a1379cc74b6", size = 73927, upload-time = "2025-12-21T14:49:44.167Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/25/0a/d865895e1e5d88a60baee0fc3703eb111c502ee10c8c107516bc7623abf8/rich_click-1.9.5-py3-none-any.whl", hash = "sha256:9b195721a773b1acf0e16ff9ec68cef1e7d237e53471e6e3f7ade462f86c403a", size = 70580, upload-time = "2025-12-21T14:49:42.905Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "3.0.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/d1/d3159231aec234a59dd7d601e9dd9fe96f3afff15efd33c1070019b26132/semver-3.0.4.tar.gz", hash = "sha256:afc7d8c584a5ed0a11033af086e8af226a9c0b206f313e0301f8dd7b6b589602", size = 269730, upload-time = "2025-01-24T13:19:27.617Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/24/4d91e05817e92e3a61c8a21e08fd0f390f5301f1c448b137c57c4bc6e543/semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746", size = 17912, upload-time = "2025-01-24T13:19:24.949Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.10.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tomlkit"
|
||||
version = "0.13.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207, upload-time = "2025-06-05T07:13:44.947Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload-time = "2025-06-05T07:13:43.546Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "update-checker"
|
||||
version = "0.18.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "requests" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5c/0b/1bec4a6cc60d33ce93d11a7bcf1aeffc7ad0aa114986073411be31395c6f/update_checker-0.18.0.tar.gz", hash = "sha256:6a2d45bb4ac585884a6b03f9eade9161cedd9e8111545141e9aa9058932acb13", size = 6699, upload-time = "2020-08-04T07:08:50.429Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/ba/8dd7fa5f0b1c6a8ac62f8f57f7e794160c1f86f31c6d0fb00f582372a3e4/update_checker-0.18.0-py3-none-any.whl", hash = "sha256:cbba64760a36fe2640d80d85306e8fe82b6816659190993b7bdabadee4d4bbfd", size = 7008, upload-time = "2020-08-04T07:08:49.51Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.5.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" },
|
||||
]
|
||||
Reference in New Issue
Block a user