mirror of
https://github.com/HKUDS/CLI-Anything.git
synced 2026-08-31 01:42:25 +08:00
fix(krita): address code review feedback for PR #119
- Remove duplicate cli-anything-plugin/.claude-plugin/marketplace.json - Expand VALID_FILTERS to match all 23 filters advertised by filter list - Add export_options parameter to krita_backend.export_file() - Extract _locked_save_json to shared utils/io.py utility - Replace deprecated datetime.utcnow() with datetime.now(timezone.utc) - Add encoding="utf-8" to setup.py open() call - Align setup.py with GIMP harness (url, classifiers, extras_require, etc.) - Add Krita brand purple accent color to repl_skin.py Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"name": "cli-anything",
|
||||
"id": "cli-anything",
|
||||
"owner": {
|
||||
"name": "HKUDS"
|
||||
},
|
||||
"metadata": {
|
||||
"description": "Build powerful, stateful CLI interfaces for any GUI application using the cli-anything harness methodology.",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "cli-anything",
|
||||
"source": "./",
|
||||
"description": "Build powerful, stateful CLI interfaces for any GUI application using the cli-anything harness methodology.",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "cli-anything contributors"
|
||||
},
|
||||
"keywords": [
|
||||
"cli",
|
||||
"harness",
|
||||
"gui-automation",
|
||||
"cli-anything"
|
||||
],
|
||||
"category": "tools",
|
||||
"strict": false
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import tempfile
|
||||
import xml.etree.ElementTree as ET
|
||||
import zlib
|
||||
import zipfile
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
@@ -243,7 +243,7 @@ def _build_documentinfo_xml(project: dict) -> bytes:
|
||||
creator_el = ET.SubElement(about, "creator")
|
||||
creator_el.text = author
|
||||
date_el = ET.SubElement(about, "date")
|
||||
date_el.text = datetime.utcnow().isoformat()
|
||||
date_el.text = datetime.now(timezone.utc).isoformat()
|
||||
|
||||
tree = ET.ElementTree(doc)
|
||||
from io import BytesIO
|
||||
|
||||
@@ -10,6 +10,8 @@ import os
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from cli_anything.krita.utils.io import locked_save_json
|
||||
|
||||
|
||||
PROJECT_VERSION = "1.0.0"
|
||||
|
||||
@@ -24,58 +26,19 @@ VALID_LAYER_TYPES = (
|
||||
)
|
||||
|
||||
VALID_FILTERS = (
|
||||
"blur",
|
||||
"sharpen",
|
||||
"desaturate",
|
||||
"levels",
|
||||
"curves",
|
||||
"brightness-contrast",
|
||||
"hue-saturation",
|
||||
"color-balance",
|
||||
"unsharp-mask",
|
||||
"posterize",
|
||||
"threshold",
|
||||
"blur", "gaussian-blur", "motion-blur", "lens-blur",
|
||||
"sharpen", "unsharp-mask",
|
||||
"brightness-contrast", "levels", "curves", "hue-saturation",
|
||||
"color-balance", "desaturate", "invert", "posterize", "threshold",
|
||||
"auto-contrast", "normalize",
|
||||
"emboss", "edge-detection", "oil-paint", "pixelize",
|
||||
"noise-reduction", "halftone",
|
||||
)
|
||||
|
||||
VALID_COLORSPACES = ("RGBA", "RGB", "GRAYA", "GRAY", "CMYKA", "CMYK")
|
||||
VALID_DEPTHS = ("U8", "U16", "F16", "F32")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Atomic file locking helper
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _locked_save_json(path: str, data: dict, **dump_kwargs) -> None:
|
||||
"""Atomically write JSON with exclusive file locking.
|
||||
|
||||
Uses fcntl on Unix; silently falls back to unlocked write on Windows
|
||||
where fcntl is unavailable.
|
||||
"""
|
||||
path = str(path)
|
||||
try:
|
||||
f = open(path, "r+")
|
||||
except FileNotFoundError:
|
||||
os.makedirs(os.path.dirname(os.path.abspath(path)), exist_ok=True)
|
||||
f = open(path, "w")
|
||||
with f:
|
||||
_locked = False
|
||||
try:
|
||||
import fcntl
|
||||
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
|
||||
_locked = True
|
||||
except (ImportError, OSError):
|
||||
pass
|
||||
try:
|
||||
f.seek(0)
|
||||
f.truncate()
|
||||
json.dump(data, f, **dump_kwargs)
|
||||
f.flush()
|
||||
finally:
|
||||
if _locked:
|
||||
import fcntl # noqa: F811
|
||||
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -232,7 +195,7 @@ def save_project(project: Dict[str, Any], path: Optional[str] = None) -> str:
|
||||
path = os.path.join(os.getcwd(), f"{safe_name}.krita.json")
|
||||
|
||||
_touch_modified(project)
|
||||
_locked_save_json(path, project, indent=2, default=str)
|
||||
locked_save_json(path, project, indent=2, default=str)
|
||||
return os.path.abspath(path)
|
||||
|
||||
|
||||
|
||||
@@ -11,30 +11,7 @@ import os
|
||||
import time
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
|
||||
def _locked_save_json(path: str, data: Any, **dump_kwargs: Any) -> None:
|
||||
"""Persist JSON data to *path* using atomic file locking."""
|
||||
try:
|
||||
f = open(path, "r+")
|
||||
except FileNotFoundError:
|
||||
os.makedirs(os.path.dirname(os.path.abspath(path)), exist_ok=True)
|
||||
f = open(path, "w")
|
||||
with f:
|
||||
_locked = False
|
||||
try:
|
||||
import fcntl
|
||||
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
|
||||
_locked = True
|
||||
except (ImportError, OSError):
|
||||
pass
|
||||
try:
|
||||
f.seek(0)
|
||||
f.truncate()
|
||||
json.dump(data, f, **dump_kwargs)
|
||||
f.flush()
|
||||
finally:
|
||||
if _locked:
|
||||
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
|
||||
from cli_anything.krita.utils.io import locked_save_json
|
||||
|
||||
|
||||
class Session:
|
||||
@@ -119,7 +96,7 @@ class Session:
|
||||
for ts, lbl, state in self._snapshots
|
||||
],
|
||||
}
|
||||
_locked_save_json(path, data, indent=2, default=str)
|
||||
locked_save_json(path, data, indent=2, default=str)
|
||||
self._session_path = path
|
||||
|
||||
def load(self, path: str) -> None:
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
"""Shared I/O utilities for the Krita CLI harness."""
|
||||
|
||||
import json
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
|
||||
def locked_save_json(path: str, data: Any, **dump_kwargs: Any) -> None:
|
||||
"""Atomically write JSON with exclusive file locking.
|
||||
|
||||
Uses fcntl on Unix; silently falls back to unlocked write on Windows
|
||||
where fcntl is unavailable.
|
||||
"""
|
||||
path = str(path)
|
||||
try:
|
||||
f = open(path, "r+")
|
||||
except FileNotFoundError:
|
||||
os.makedirs(os.path.dirname(os.path.abspath(path)), exist_ok=True)
|
||||
f = open(path, "w")
|
||||
with f:
|
||||
_locked = False
|
||||
try:
|
||||
import fcntl
|
||||
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
|
||||
_locked = True
|
||||
except (ImportError, OSError):
|
||||
pass
|
||||
try:
|
||||
f.seek(0)
|
||||
f.truncate()
|
||||
json.dump(data, f, **dump_kwargs)
|
||||
f.flush()
|
||||
finally:
|
||||
if _locked:
|
||||
import fcntl # noqa: F811
|
||||
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
|
||||
@@ -176,6 +176,7 @@ def export_file(
|
||||
output_path: str | Path,
|
||||
*,
|
||||
format: Optional[str] = None,
|
||||
export_options: Optional[Dict[str, Any]] = None,
|
||||
timeout: int = 300,
|
||||
) -> Dict[str, Any]:
|
||||
"""Export *input_path* to *output_path* using Krita's CLI.
|
||||
@@ -187,6 +188,8 @@ def export_file(
|
||||
format: If provided, override the output format (e.g. ``"png"``).
|
||||
The extension of *output_path* will still be respected for the
|
||||
filename.
|
||||
export_options: Optional dict of key-value pairs forwarded to Krita
|
||||
via ``--export-option key=value`` flags (e.g. compression, quality).
|
||||
timeout: Maximum seconds to wait for Krita.
|
||||
|
||||
Returns:
|
||||
@@ -200,7 +203,11 @@ def export_file(
|
||||
# Ensure output directory exists
|
||||
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
||||
|
||||
args = [krita, "--export", "--export-filename", output_path, input_path]
|
||||
args = [krita, "--export", "--export-filename", output_path]
|
||||
if export_options:
|
||||
for key, value in export_options.items():
|
||||
args += ["--export-option", f"{key}={value}"]
|
||||
args.append(input_path)
|
||||
|
||||
result = _run(args, timeout=timeout)
|
||||
result["output_path"] = output_path
|
||||
|
||||
@@ -47,6 +47,7 @@ _ACCENT_COLORS = {
|
||||
"obs_studio": "\033[38;5;55m", # purple
|
||||
"kdenlive": "\033[38;5;69m", # slate blue
|
||||
"shotcut": "\033[38;5;35m", # teal green
|
||||
"krita": "\033[38;5;98m", # purple (Krita brand)
|
||||
}
|
||||
_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue
|
||||
|
||||
@@ -515,6 +516,7 @@ _ANSI_256_TO_HEX = {
|
||||
"\033[38;5;55m": "#5f00af", # obs purple
|
||||
"\033[38;5;69m": "#5f87ff", # kdenlive slate blue
|
||||
"\033[38;5;75m": "#5fafff", # default sky blue
|
||||
"\033[38;5;98m": "#875fd7", # krita purple
|
||||
"\033[38;5;80m": "#5fd7d7", # brand cyan
|
||||
"\033[38;5;208m": "#ff8700", # blender deep orange
|
||||
"\033[38;5;214m": "#ffaf00", # gimp warm orange
|
||||
|
||||
@@ -1,25 +1,48 @@
|
||||
from setuptools import setup, find_namespace_packages
|
||||
|
||||
with open("cli_anything/krita/README.md", encoding="utf-8") as fh:
|
||||
long_description = fh.read()
|
||||
|
||||
setup(
|
||||
name="cli-anything-krita",
|
||||
version="1.0.0",
|
||||
description="CLI harness for Krita digital painting application",
|
||||
long_description=open("cli_anything/krita/README.md").read(),
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
author="cli-anything contributors",
|
||||
url="https://github.com/HKUDS/CLI-Anything",
|
||||
license="MIT",
|
||||
packages=find_namespace_packages(include=["cli_anything.*"]),
|
||||
package_data={
|
||||
"cli_anything.krita": ["skills/*.md"],
|
||||
},
|
||||
classifiers=[
|
||||
"Development Status :: 4 - Beta",
|
||||
"Intended Audience :: Developers",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
"Topic :: Multimedia :: Graphics :: Editors",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
],
|
||||
python_requires=">=3.10",
|
||||
install_requires=[
|
||||
"click>=8.0.0",
|
||||
"prompt-toolkit>=3.0.0",
|
||||
],
|
||||
extras_require={
|
||||
"dev": [
|
||||
"pytest>=7.0.0",
|
||||
"pytest-cov>=4.0.0",
|
||||
],
|
||||
},
|
||||
entry_points={
|
||||
"console_scripts": [
|
||||
"cli-anything-krita=cli_anything.krita.krita_cli:main",
|
||||
],
|
||||
},
|
||||
python_requires=">=3.10",
|
||||
package_data={
|
||||
"cli_anything.krita": ["skills/*.md"],
|
||||
},
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user