screengraft 0.65.0 → 0.67.0
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.
- package/package.json +1 -1
- package/scripts/ui.py +29 -0
- package/skills/inject-screenshot/SKILL.md +1 -1
- package/ui/index.html +686 -136
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "screengraft",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.67.0",
|
|
4
4
|
"description": "Put a UI screenshot or screen recording onto a photographed device screen with the perspective exactly right \u2014 a homography you confirm by hand, not a generative guess.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mockup",
|
package/scripts/ui.py
CHANGED
|
@@ -1077,6 +1077,35 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
1077
1077
|
except RenderBusy as exc:
|
|
1078
1078
|
return self._json({"error": str(exc)}, 409)
|
|
1079
1079
|
|
|
1080
|
+
if u.path == "/api/reveal":
|
|
1081
|
+
# Show a saved file in the desktop's file manager. The PAGE
|
|
1082
|
+
# cannot do this -- a browser will not open a Finder window
|
|
1083
|
+
# from an http origin -- and this server is the half of
|
|
1084
|
+
# screengraft that runs on the user's machine, so it is the
|
|
1085
|
+
# only thing that can.
|
|
1086
|
+
#
|
|
1087
|
+
# _safe_local_path is the whole security story and it already
|
|
1088
|
+
# guards /file: it expands ~, resolves symlinks, refuses any
|
|
1089
|
+
# path outside HOME, and requires a file that exists. The
|
|
1090
|
+
# command is an argument LIST handed to the OS, never a string
|
|
1091
|
+
# through a shell, so a path cannot become an argument or a
|
|
1092
|
+
# second command however it is spelled.
|
|
1093
|
+
try:
|
|
1094
|
+
p = _safe_local_path(b.get("path") or "")
|
|
1095
|
+
except (PermissionError, FileNotFoundError, TypeError) as exc:
|
|
1096
|
+
return self._json({"error": str(exc)}, 400)
|
|
1097
|
+
if sys.platform == "darwin":
|
|
1098
|
+
cmd = ["open", "-R", p] # reveals AND selects it
|
|
1099
|
+
elif os.name == "nt":
|
|
1100
|
+
cmd = ["explorer", "/select,", p] # exits 1 even on success
|
|
1101
|
+
else:
|
|
1102
|
+
cmd = ["xdg-open", os.path.dirname(p)]
|
|
1103
|
+
try:
|
|
1104
|
+
subprocess.run(cmd, capture_output=True, timeout=10, check=False)
|
|
1105
|
+
except (OSError, subprocess.SubprocessError) as exc:
|
|
1106
|
+
return self._json({"error": f"could not open the folder: {exc}"}, 500)
|
|
1107
|
+
return self._json({"ok": True})
|
|
1108
|
+
|
|
1080
1109
|
if u.path == "/api/figma":
|
|
1081
1110
|
return self._json(SESSION.enqueue({
|
|
1082
1111
|
"type": "figma_export", "url": b["url"],
|
|
@@ -5,7 +5,7 @@ description: Injects a UI screenshot OR a screen recording onto a photographed d
|
|
|
5
5
|
|
|
6
6
|
# Inject a screenshot onto a photographed device
|
|
7
7
|
|
|
8
|
-
**What ships (v0.
|
|
8
|
+
**What ships (v0.67):** a local browser UI (`scripts/ui.py`) that walks the designer through the whole job — pick the photo and the screen source, which may be an image **or a video** (the images you have used before, drag-drop, browse, path, or a **Figma frame link**), auto-detect the screen as a starting position — and when detection cannot tell which region is a screen, **Point at screen**: one click inside it and the detector uses that point — or, if this photograph has been fitted before, **the fit it was saved with comes back** as the starting position instead of a detection, recognised by the photo's own pixels so a rename or a drag-drop still match — and every save also writes a **portable `.fit.json` beside the mockup** that can be dropped back onto the page later, which is how a fit survives a re-export, another machine, or someone else's hands — then **match the four edges** (drag an edge's middle to slide it, near an end to pivot; corners still draggable) with canvas navigation that follows the usual conventions — **hold ⌘ and scroll to zoom to the pointer, hold space and drag to pan** — and a rectified strip loupe. The fit and the composite sit **side by side and always have** — the result pane re-renders as you drag, which is how a corner gets judged, so it is the layout rather than a mode you can switch off. Then an on-by-default realism pass that colour-matches the source to the photo's light, **Save** (or **Render**, for a video) into the project folder (`--out-dir`), and a **Send to Claude** button that reaches you through the plugin's own MCP server. The UI is a hand port of the project's Figma design file — dark only.
|
|
9
9
|
|
|
10
10
|
The geometry is exact (`warp.py`); the detection is advisory (`detect.py`) and the human corrects it. **When a detection is wrong and you want to know why**, ask for the candidate list: `POST /api/detect {"trace": true}` writes `<session>/candidates.json`, or run `python3 scripts/detect.py --photo P --out-corners /tmp/c.json --trace /tmp/t.json` (add `--click X,Y`). Every candidate quad is in there with its score and whether it was accepted, rejected, never reached, or filtered out by the click — which is what separates "the screen was never proposed" from "it was proposed and something else won".
|
|
11
11
|
|