Compare commits

...

3 Commits

Author SHA1 Message Date
2f3db9272b feat(cli): extend upload flags for src, patterns, lib, and --all
Support --patterns/--paterns, --all, --erase, and src upload excluding patterns/.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-03 14:54:13 +12:00
713cd6e9a1 feat(cli): add ls flag and default lib upload
Made-with: Cursor
2026-04-15 00:46:19 +12:00
9e72c62481 fix(led-tool): build cli without spec file dependency 2026-04-14 23:13:21 +12:00
3 changed files with 107 additions and 14 deletions

View File

@@ -16,5 +16,5 @@ python_version = "3"
[scripts] [scripts]
web = "python web.py" web = "python web.py"
cli = "python cli.py" cli = "python cli.py"
build = "pyinstaller --clean led-cli.spec" build = "pyinstaller --clean --onefile --name led-cli --paths lib cli.py"
install = "pipenv install" install = "pipenv install"

117
cli.py
View File

@@ -11,6 +11,7 @@ import argparse
import subprocess import subprocess
import sys import sys
import time import time
import shutil
from typing import Dict, Any, List, Optional from typing import Dict, Any, List, Optional
import tempfile import tempfile
@@ -147,14 +148,14 @@ _FLAGS_WITH_VALUE = frozenset({
'-p', '--port', '-n', '--name', '--pin', '-b', '--brightness', '-p', '--port', '-n', '--name', '--pin', '-b', '--brightness',
'-l', '--leds', '-d', '-debug', '--debug', '-o', '--order', '-l', '--leds', '-d', '-debug', '--debug', '-o', '--order',
'--preset', '--pattern', '--default', '--transport', '--ssid', '--preset', '--pattern', '--default', '--transport', '--ssid',
'--wifi-password', '--wifi-channel', '--wifi-password', '--wifi-channel', '--src', '--lib', '--patterns', '--paterns',
}) })
def _get_ordered_actions(argv: List[str]) -> List[tuple]: def _get_ordered_actions(argv: List[str]) -> List[tuple]:
""" """
Scan argv and return list of (action_name, value) in order of appearance. Scan argv and return list of (action_name, value) in order of appearance.
Actions: flash, pause, reset, upload, erase_all, rm, follow. Actions: flash, pause, reset, upload, ls, erase_all, rm, follow.
""" """
actions = [] actions = []
i = 1 i = 1
@@ -201,22 +202,47 @@ def _get_ordered_actions(argv: List[str]) -> List[tuple]:
i += 2 i += 2
else: else:
i += 1 i += 1
# Use empty string as remote_dir to map to root on device # Upload source tree excluding patterns/ (handled by --patterns).
actions.append(('upload', [local_dir, ""])) actions.append(('upload_src_no_patterns', local_dir))
continue continue
if arg == '--lib': if arg in ('--patterns', '--paterns'):
# Upload local DIR to /lib on device # Upload local patterns DIR (default: ./src/patterns) to /patterns.
if i + 1 < len(argv): local_dir = os.path.join("src", "patterns")
if i + 1 < len(argv) and not argv[i + 1].startswith('-'):
local_dir = argv[i + 1] local_dir = argv[i + 1]
actions.append(('upload', [local_dir, "lib"]))
i += 2 i += 2
else: else:
raise ValueError("--lib requires a directory argument") i += 1
actions.append(('upload', [local_dir, "patterns"]))
continue
if arg == '--all':
actions.append(('upload_src_no_patterns', "src"))
actions.append(('upload', [os.path.join("src", "patterns"), "patterns"]))
actions.append(('upload', ["lib", "lib"]))
i += 1
continue
if arg == '--lib':
# Upload local DIR (default: ./lib) to /lib on device
local_dir = "lib"
if i + 1 < len(argv) and not argv[i + 1].startswith('-'):
local_dir = argv[i + 1]
i += 2
else:
i += 1
actions.append(('upload', [local_dir, "lib"]))
continue
if arg == '--ls':
actions.append(('ls', None))
i += 1
continue continue
if arg == '-e': if arg == '-e':
actions.append(('erase_all', None)) actions.append(('erase_all', None))
i += 1 i += 1
continue continue
if arg == '--erase':
actions.append(('erase_all', None))
i += 1
continue
if arg == '--rm': if arg == '--rm':
if i + 1 < len(argv): if i + 1 < len(argv):
actions.append(('rm', argv[i + 1])) actions.append(('rm', argv[i + 1]))
@@ -429,17 +455,40 @@ Examples:
nargs="?", nargs="?",
const="src", const="src",
metavar="DIR", metavar="DIR",
help="Upload DIR recursively to device root (:/, no leading directory). If DIR is omitted, uses local ./src." help="Upload DIR recursively to device root (:/, no leading directory), excluding patterns/. If DIR is omitted, uses local ./src."
) )
parser.add_argument( parser.add_argument(
"--lib", "--lib",
nargs="?",
const="lib",
metavar="DIR", metavar="DIR",
help="Upload DIR recursively to /lib on device" help="Upload DIR recursively to /lib on device. If DIR is omitted, uses local ./lib."
) )
parser.add_argument( parser.add_argument(
"-e", "--all",
action="store_true",
help="Upload ./src (excluding patterns), ./src/patterns, and ./lib."
)
parser.add_argument(
"--patterns", "--paterns",
dest="patterns_dir",
nargs="?",
const=os.path.join("src", "patterns"),
metavar="DIR",
help="Upload DIR recursively to /patterns on device. If DIR is omitted, uses local ./src/patterns."
)
parser.add_argument(
"--ls",
action="store_true",
help="List files on the device root (:/)"
)
parser.add_argument(
"-e", "--erase",
dest="erase_all", dest="erase_all",
action="store_true", action="store_true",
help="Erase all code on the device (delete all files except settings.json)" help="Erase all code on the device (delete all files except settings.json)"
@@ -526,6 +575,50 @@ Examples:
except Exception as e: except Exception as e:
print(f"Error uploading directory: {e}", file=sys.stderr) print(f"Error uploading directory: {e}", file=sys.stderr)
sys.exit(1) sys.exit(1)
elif action_name == 'upload_src_no_patterns':
src_dir = value
if not os.path.exists(src_dir):
print(f"Error: Directory does not exist: {src_dir}", file=sys.stderr)
sys.exit(1)
if not os.path.isdir(src_dir):
print(f"Error: Not a directory: {src_dir}", file=sys.stderr)
sys.exit(1)
try:
with tempfile.TemporaryDirectory() as temp_src:
for entry in sorted(os.listdir(src_dir)):
if entry == "patterns":
continue
src_entry = os.path.join(src_dir, entry)
dst_entry = os.path.join(temp_src, entry)
if os.path.isdir(src_entry):
shutil.copytree(src_entry, dst_entry)
else:
shutil.copy2(src_entry, dst_entry)
print(
f"Uploading {src_dir} (excluding patterns/) to device root on {port}...",
file=sys.stderr,
)
conn = DeviceConnection(port)
files_copied, dirs_created = conn.upload_directory(temp_src, "")
print(
f"Upload complete: {files_copied} files, {dirs_created} directories created.",
file=sys.stderr,
)
except Exception as e:
print(f"Error uploading src (excluding patterns): {e}", file=sys.stderr)
sys.exit(1)
elif action_name == 'ls':
try:
print(f"Listing files on device {port}...", file=sys.stderr)
conn = DeviceConnection(port)
items = conn.list_files('')
for name, is_dir, size in items:
marker = "d" if is_dir else "-"
print(f"{marker} {size:>8} {name}")
except Exception as e:
print(f"Error listing files: {e}", file=sys.stderr)
sys.exit(1)
elif action_name == 'erase_all': elif action_name == 'erase_all':
try: try:

View File

@@ -10,7 +10,7 @@ pipenv install "$@"
if [ ! -f "dist/led-cli" ] || [ "cli.py" -nt "dist/led-cli" ] || [ "device.py" -nt "dist/led-cli" ]; then if [ ! -f "dist/led-cli" ] || [ "cli.py" -nt "dist/led-cli" ] || [ "device.py" -nt "dist/led-cli" ]; then
echo "" echo ""
echo "Building binary..." echo "Building binary..."
pipenv run pyinstaller --clean led-cli.spec pipenv run pyinstaller --clean --onefile --name led-cli --paths lib cli.py
fi fi
# Ensure ~/.local/bin exists # Ensure ~/.local/bin exists