penpot-local-stack 0.1.0__tar.gz

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.
@@ -0,0 +1,23 @@
1
+ # Agents Configs
2
+ .claude/
3
+ .gemini/
4
+ CLAUDE.local.md
5
+ GEMINI.md
6
+ .mcp.json
7
+
8
+ # IDE
9
+ .venv
10
+ .idea
11
+
12
+ # Python build and test artifacts
13
+ __pycache__/
14
+ *.py[cod]
15
+ *.egg-info/
16
+ dist/
17
+ .pytest_cache/
18
+
19
+ # pre-commit cache, kept in-tree by PRE_COMMIT_HOME
20
+ .pre-commit
21
+
22
+ # Local stack settings, written by install.sh
23
+ .penpot.yaml
@@ -0,0 +1,9 @@
1
+ # Penpot local stack. Copy to .penpot.yaml and edit; install.sh does that for
2
+ # you. Remove a key to fall back to its default, or override it in the
3
+ # environment as PENPOT_PORT, PENPOT_TEMPLATES and so on.
4
+
5
+ templates: templates
6
+ host: 127.0.0.1
7
+ port: 9001
8
+ project: penpot-local
9
+ version: 2.17.2
@@ -0,0 +1,121 @@
1
+ # Contributing
2
+
3
+ Everything in the checkout runs through `make`, on the system `python3`:
4
+ there is no virtualenv here and nothing installs itself. Python 3.12 or newer
5
+ with the packages from `requirements.txt`, Docker with the Compose plugin, and
6
+ [uv](https://docs.astral.sh/uv/) for `make build`.
7
+
8
+ - [Layout](#layout)
9
+ - [Make targets](#make-targets)
10
+ - [Working on the stack](#working-on-the-stack)
11
+ - [Tests and linters](#tests-and-linters)
12
+ - [Continuous integration](#continuous-integration)
13
+ - [Releases](#releases)
14
+
15
+ ## Layout
16
+
17
+ ```text
18
+ bin/penpot-stack launcher, runs the code straight from the checkout
19
+ libs/ the modules: cli, settings, compose, penpot, templates, prompts
20
+ config/ compose.yaml and autologin.conf
21
+ install/ the curl installer and its documentation
22
+ tests/ pytest suite, no network and no Docker
23
+ ```
24
+
25
+ The wheel carries `libs/` and `config/` under one importable name: the build
26
+ maps `libs` to `penpot_stack` and `config` to `penpot_stack/config`, so the
27
+ Compose file ships with the code and `penpot-stack` runs from anywhere. That
28
+ mapping lives in `[tool.hatch.build.targets.wheel]` in `pyproject.toml`.
29
+
30
+ Modules import each other relatively, which is what lets the same files work
31
+ as `libs` in the checkout and as `penpot_stack` once installed.
32
+
33
+ `libs/settings.py` is the only place that decides anything configurable: a
34
+ pydantic-settings model filled from defaults, then `.penpot.yaml` in the
35
+ working directory, then `PENPOT_*` variables. `config/compose.yaml` reads the
36
+ same names through `${PENPOT_PORT:-9001}` style defaults, so the file still
37
+ runs under a bare `docker compose`. `.penpot.yaml.example` in the repository
38
+ root is the documented shape of that file and what `install.sh` copies into a
39
+ directory; `.penpot.yaml` itself is gitignored, so the checkout runs on the
40
+ defaults until you copy the example over.
41
+
42
+ Nothing in the checkout installs that package, so an editable install never
43
+ comes up - which is just as well, because hatchling rejects a dev-mode install
44
+ whose `sources` rewrite replaces a prefix.
45
+
46
+ ## Make targets
47
+
48
+ | Target | What it does |
49
+ | -------------- | ---------------------------------------------------------- |
50
+ | `make` | Open an interactive subshell with `PRE_COMMIT_HOME` set |
51
+ | `make test` | Run the pytest suite |
52
+ | `make lint` | Run the pre-commit hooks over every file |
53
+ | `make up` | Start the stack from the checkout |
54
+ | `make down` | Offer the export, confirm the wipe, stop the stack |
55
+ | `make import` | Import a template into the running stack |
56
+ | `make export` | Export a file back into `templates/` |
57
+ | `make extract` | Unpack a `.penpot` file into `templates/`, no stack needed |
58
+ | `make convert` | Pack a template into a `.penpot` file, no stack needed |
59
+ | `make build` | Build the wheel and the sdist into `dist/` |
60
+ | `make clean` | Remove the build artifacts |
61
+
62
+ Start by giving your `python3` what `requirements.txt` lists, however this
63
+ machine installs packages, then wire up the hooks once:
64
+
65
+ ```bash
66
+ pre-commit install
67
+ ```
68
+
69
+ ## Working on the stack
70
+
71
+ `make up` and the rest run `python3 bin/penpot-stack`, so they use the working
72
+ tree against `templates/` in the repository root. Point `PENPOT_TEMPLATES` somewhere else to keep the repository clean:
73
+
74
+ ```bash
75
+ PENPOT_TEMPLATES=~/mockups make up
76
+ ```
77
+
78
+ The stack itself is `config/compose.yaml`: Penpot frontend, backend, exporter
79
+ and MCP, with Postgres on tmpfs and Valkey without persistence. Nothing in it
80
+ is meant to survive `down`.
81
+
82
+ ## Tests and linters
83
+
84
+ ```bash
85
+ make test
86
+ make lint
87
+ ```
88
+
89
+ The tests cover the parts that do not need Penpot: template discovery, the
90
+ export filter that drops thumbnails and refuses raster images, the Compose
91
+ command, and every confirm in the command flow. Anything that talks to the
92
+ API is mocked, so the suite needs neither network nor Docker.
93
+
94
+ `make lint` runs ruff, shellcheck, shfmt, markdownlint and the commit-message
95
+ checks through pre-commit. Both are expected to pass before a commit.
96
+
97
+ ## Continuous integration
98
+
99
+ `.github/workflows/ci.yml` runs on every pull request and on pushes to `main`:
100
+ one job runs the pre-commit hooks over all files, the other runs the suite on
101
+ Python 3.12, 3.13 and 3.14. Actions are pinned by commit, with the version in
102
+ a trailing comment. Nothing is published from CI.
103
+
104
+ Dependabot watches three ecosystems weekly: `pip` for `requirements.txt`,
105
+ `pre-commit` for the hook revisions and `github-actions` for those pins.
106
+
107
+ ## Releases
108
+
109
+ The version lives in `pyproject.toml` and is bumped with commitizen, which
110
+ writes the changelog and the tag:
111
+
112
+ ```bash
113
+ cz bump
114
+ git push --follow-tags
115
+ ```
116
+
117
+ Users upgrade by re-running `install/install.sh`, which unfolds a tag with
118
+ `--ref`, so a release is the tag itself.
119
+
120
+ `make build` puts the wheel and the sdist in `dist/`. Uploading them is not
121
+ wired into this repository, by hand or from CI.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 oberon-systems
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,84 @@
1
+ # Developer entry points for penpot-local-stack. Run `make help` for the list.
2
+
3
+ VENV ?= .venv
4
+ PYTHON := $(VENV)/bin/python
5
+ PIP := $(VENV)/bin/pip
6
+ UV ?= uv
7
+ STACK := $(PYTHON) bin/penpot-stack
8
+
9
+ VERSION := $(shell sed -n 's/^version = "\(.*\)"/\1/p' pyproject.toml)
10
+
11
+ # Hook environments live in the repository, not in ~/.cache/pre-commit.
12
+ export PRE_COMMIT_HOME := $(CURDIR)/.pre-commit
13
+
14
+ .DEFAULT_GOAL := shell
15
+
16
+ .PHONY: help install lint test shell up down import export extract convert
17
+ .PHONY: build clean require-uv
18
+
19
+ help: ## Show the current version and the available targets
20
+ @echo "penpot-local-stack $(VERSION)"
21
+ @echo
22
+ @echo "Targets:"
23
+ @awk 'BEGIN {FS = ":.*## "} /^[a-z-]+:.*## / {printf " %-9s %s\n", $$1, $$2}' \
24
+ $(MAKEFILE_LIST)
25
+
26
+
27
+ # development
28
+ install: ## Create the virtualenv, install everything and wire up the hooks
29
+ python3 -m venv --prompt penpot-local-stack $(VENV)
30
+ $(PIP) install --upgrade pip
31
+ $(PIP) install -r requirements.txt
32
+ $(VENV)/bin/pre-commit install
33
+
34
+ lint: ## Run the pre-commit hooks over every file
35
+ $(VENV)/bin/pre-commit run --all-files
36
+
37
+ test: ## Run the test suite
38
+ $(VENV)/bin/pytest
39
+
40
+
41
+ # stack
42
+ up: ## Start the stack, import a template and open the browser logged in
43
+ $(STACK) up
44
+
45
+ down: ## Offer to export, then stop the stack and wipe everything it holds
46
+ $(STACK) down
47
+
48
+ import: ## Import a template from templates/ into the running stack
49
+ $(STACK) import
50
+
51
+ export: ## Export a file from the running stack back into templates/
52
+ $(STACK) export
53
+
54
+
55
+ # archives
56
+ extract: ## Unpack a .penpot file from this directory into templates/
57
+ $(STACK) extract
58
+
59
+ convert: ## Pack a template from templates/ into a .penpot file
60
+ $(STACK) convert
61
+
62
+
63
+ # packaging
64
+ build: require-uv clean ## Build the wheel and the sdist into dist/
65
+ $(UV) build
66
+
67
+ clean: ## Remove the build artifacts from dist/
68
+ rm -f dist/*.whl dist/*.tar.gz
69
+
70
+ require-uv:
71
+ @command -v $(UV) >/dev/null 2>&1 || { \
72
+ echo "$(UV) not found, install it first:" >&2; \
73
+ echo " https://docs.astral.sh/uv/getting-started/installation/" >&2; \
74
+ exit 1; \
75
+ }
76
+
77
+
78
+ # defaults
79
+ shell: ## Open an interactive subshell with the virtualenv activated
80
+ @rc="$$(mktemp)"; \
81
+ trap 'rm -f "$$rc"' EXIT; \
82
+ cat ~/.bashrc 2> /dev/null > "$$rc" || true; \
83
+ echo 'source $(CURDIR)/$(VENV)/bin/activate' >> "$$rc"; \
84
+ bash --rcfile "$$rc" -i || true
@@ -0,0 +1,180 @@
1
+ Metadata-Version: 2.5
2
+ Name: penpot-local-stack
3
+ Version: 0.1.0
4
+ Summary: A disposable local Penpot stack: import a template, draw, export it back
5
+ Project-URL: Repository, https://github.com/oberon-systems/penpot-local-stack
6
+ Project-URL: Issues, https://github.com/oberon-systems/penpot-local-stack/issues
7
+ Author-email: zombig <me@zombig.name>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Operating System :: POSIX
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: Multimedia :: Graphics
17
+ Classifier: Topic :: Utilities
18
+ Requires-Python: >=3.12
19
+ Requires-Dist: httpx>=0.27
20
+ Requires-Dist: pydantic-settings>=2.4
21
+ Requires-Dist: pydantic>=2
22
+ Requires-Dist: pyyaml>=6
23
+ Requires-Dist: questionary>=2
24
+ Description-Content-Type: text/markdown
25
+
26
+ # penpot-local-stack
27
+
28
+ A disposable [Penpot](https://penpot.app) stack for drawing UI mockups, built
29
+ for AI-driven UI/UX work. Penpot itself is open source; its code is at
30
+ [penpot/penpot](https://github.com/penpot/penpot).
31
+
32
+ The stack exists so a design session leaves nothing behind but the drawing.
33
+ It runs from a Compose file with no persistent state: the database lives in
34
+ tmpfs and goes away with the containers, the profile is created on every
35
+ start, and the only thing that survives is `templates/` in your directory.
36
+ Mockups are stored there as unpacked Penpot exports, JSON plus SVG, so git
37
+ shows readable diffs and an agent can read a mockup directly instead of
38
+ looking at a picture of it.
39
+
40
+ - [Install](#install)
41
+ - [Settings](#settings)
42
+ - [Design loop](#design-loop)
43
+ - [Commands](#commands)
44
+ - [Templates](#templates)
45
+ - [Connect Penpot MCP](#connect-penpot-mcp)
46
+
47
+ ## Install
48
+
49
+ Docker with the Compose plugin and Python 3.12 or newer have to be there
50
+ already, and that `python3` needs `httpx`, `questionary`, `pydantic-settings`
51
+ and `PyYAML`. The installer checks and tells you what is missing; it never
52
+ installs anything itself.
53
+
54
+ Run it in the directory that will hold the mockups:
55
+
56
+ ```bash
57
+ curl -fsSL https://raw.githubusercontent.com/oberon-systems/penpot-local-stack/main/install/install.sh | bash
58
+ ```
59
+
60
+ It downloads the stack and lays it out:
61
+
62
+ ```text
63
+ bin/ libs/ config/ the stack, run by the system python3
64
+ .penpot.yaml settings, read from the directory you run in
65
+ Makefile up, down, import, export, extract, convert
66
+ templates/ unpacked Penpot exports, empty at first
67
+ ```
68
+
69
+ Re-running it replaces `bin/`, `libs/` and `config/`, which is the upgrade,
70
+ and keeps `.penpot.yaml` and `templates/`. `--ref` unfolds a branch or a tag
71
+ instead of `main`. See [install/README.md](install/README.md) for the details.
72
+
73
+ ## Settings
74
+
75
+ The installer copies `.penpot.yaml.example` from the repository to
76
+ `.penpot.yaml` next to the `Makefile`, and every command reads that file from
77
+ the directory it runs in:
78
+
79
+ ```yaml
80
+ templates: templates
81
+ host: 127.0.0.1
82
+ port: 9001
83
+ project: penpot-local
84
+ version: 2.17.2
85
+ ```
86
+
87
+ | Key | Default | What it sets |
88
+ | ----------- | ---------------------- | ------------------------------- |
89
+ | `templates` | `templates` | Where the unpacked exports live |
90
+ | `host` | `127.0.0.1` | The address Penpot binds to |
91
+ | `port` | `9001` | The port Penpot binds to |
92
+ | `project` | `penpot-local` | The Compose project name |
93
+ | `version` | `2.17.2` | The Penpot image tag |
94
+ | `email` | `designer@example.com` | The throwaway profile |
95
+ | `password` | `penpot-local` | Its password |
96
+
97
+ Drop a key to fall back to its default. Every key also answers to an
98
+ environment variable with a `PENPOT_` prefix, and the variable wins over the
99
+ file:
100
+
101
+ ```bash
102
+ PENPOT_PORT=9100 make up
103
+ ```
104
+
105
+ ## Design loop
106
+
107
+ Start the stack and pick a template or `(empty)`:
108
+
109
+ ```bash
110
+ make up
111
+ ```
112
+
113
+ `up` creates a throwaway profile and opens the browser already logged in
114
+ through the printed `/autologin` link. If that session is lost, log in as
115
+ `designer@example.com` with the password `penpot-local`.
116
+
117
+ Draw in the browser at `http://localhost:9001`, or wherever `port` points. When you are done, pick the
118
+ file to export and the template to write it to, then let the stack go down:
119
+
120
+ ```bash
121
+ make down
122
+ ```
123
+
124
+ `down` writes the export into `templates/<name>/` and runs
125
+ `docker compose down -v`, which drops the database and the assets. Pick
126
+ `(skip export)` to throw the work away; the wipe is confirmed first whenever
127
+ the stack still holds a file.
128
+
129
+ ## Commands
130
+
131
+ | Target | What it does |
132
+ | -------------- | --------------------------------------------------------- |
133
+ | `make up` | Start the stack, import a template, open the browser |
134
+ | `make down` | Offer the export, confirm the wipe, stop the stack |
135
+ | `make import` | Import a template into the running stack, after a confirm |
136
+ | `make export` | Export a file into `templates/`, after a confirm |
137
+ | `make extract` | Unpack a `.penpot` file into `templates/` |
138
+ | `make convert` | Pack a template into a `.penpot` file |
139
+
140
+ `import` and `export` work against a running stack, so a template can be
141
+ swapped in or a file saved off without ending the session. Both ask before
142
+ they touch anything: `import` names the template it is about to load, `export`
143
+ says whether it writes a new template or overwrites one that is already there.
144
+
145
+ `extract` and `convert` are the offline pair and need no stack at all.
146
+ `extract` lists the `.penpot` files next to you, asks which template to write
147
+ and unpacks the archive under the same rules as `export`: frame thumbnails are
148
+ dropped, JSON is reformatted, and an archive holding raster images is refused
149
+ with the offending files listed. `convert` goes the other way and packs
150
+ `templates/<name>/` into `<name>.penpot`, which is what you hand to a Penpot
151
+ that is not this one. Both confirm before writing, and say whether they are
152
+ creating something or overwriting it.
153
+
154
+ ## Templates
155
+
156
+ - One directory per template: `manifest.json`, `files/` and `objects/`.
157
+ - Templates are read from `templates/` in the current directory. Set
158
+ `PENPOT_TEMPLATES` to read them from somewhere else.
159
+ - Images and icons must be SVG. An export that holds raster images is refused
160
+ and the offending files are listed, so you can replace them and export
161
+ again.
162
+ - Frame thumbnails are raster renders Penpot rebuilds on its own, so the
163
+ export leaves them out.
164
+ - Import keeps the Penpot file id, so a round trip changes only what you drew.
165
+
166
+ ## Connect Penpot MCP
167
+
168
+ `up` enables MCP for the throwaway profile, issues its key and serves it
169
+ behind the fixed `/mcp/claude` URL, so an agent needs the server added only
170
+ once. Add it, then restart the agent:
171
+
172
+ ```bash
173
+ claude mcp add --transport http penpot http://localhost:9001/mcp/claude
174
+ ```
175
+
176
+ After later `up` runs, reconnect `penpot` from `/mcp` instead of restarting.
177
+ While the stack is down, the server just shows as failed.
178
+
179
+ The MCP plugin runs inside the Penpot browser tab. Keep a file open in the
180
+ workspace while the agent works; a hidden or unloaded tab stops MCP.
@@ -0,0 +1,155 @@
1
+ # penpot-local-stack
2
+
3
+ A disposable [Penpot](https://penpot.app) stack for drawing UI mockups, built
4
+ for AI-driven UI/UX work. Penpot itself is open source; its code is at
5
+ [penpot/penpot](https://github.com/penpot/penpot).
6
+
7
+ The stack exists so a design session leaves nothing behind but the drawing.
8
+ It runs from a Compose file with no persistent state: the database lives in
9
+ tmpfs and goes away with the containers, the profile is created on every
10
+ start, and the only thing that survives is `templates/` in your directory.
11
+ Mockups are stored there as unpacked Penpot exports, JSON plus SVG, so git
12
+ shows readable diffs and an agent can read a mockup directly instead of
13
+ looking at a picture of it.
14
+
15
+ - [Install](#install)
16
+ - [Settings](#settings)
17
+ - [Design loop](#design-loop)
18
+ - [Commands](#commands)
19
+ - [Templates](#templates)
20
+ - [Connect Penpot MCP](#connect-penpot-mcp)
21
+
22
+ ## Install
23
+
24
+ Docker with the Compose plugin and Python 3.12 or newer have to be there
25
+ already, and that `python3` needs `httpx`, `questionary`, `pydantic-settings`
26
+ and `PyYAML`. The installer checks and tells you what is missing; it never
27
+ installs anything itself.
28
+
29
+ Run it in the directory that will hold the mockups:
30
+
31
+ ```bash
32
+ curl -fsSL https://raw.githubusercontent.com/oberon-systems/penpot-local-stack/main/install/install.sh | bash
33
+ ```
34
+
35
+ It downloads the stack and lays it out:
36
+
37
+ ```text
38
+ bin/ libs/ config/ the stack, run by the system python3
39
+ .penpot.yaml settings, read from the directory you run in
40
+ Makefile up, down, import, export, extract, convert
41
+ templates/ unpacked Penpot exports, empty at first
42
+ ```
43
+
44
+ Re-running it replaces `bin/`, `libs/` and `config/`, which is the upgrade,
45
+ and keeps `.penpot.yaml` and `templates/`. `--ref` unfolds a branch or a tag
46
+ instead of `main`. See [install/README.md](install/README.md) for the details.
47
+
48
+ ## Settings
49
+
50
+ The installer copies `.penpot.yaml.example` from the repository to
51
+ `.penpot.yaml` next to the `Makefile`, and every command reads that file from
52
+ the directory it runs in:
53
+
54
+ ```yaml
55
+ templates: templates
56
+ host: 127.0.0.1
57
+ port: 9001
58
+ project: penpot-local
59
+ version: 2.17.2
60
+ ```
61
+
62
+ | Key | Default | What it sets |
63
+ | ----------- | ---------------------- | ------------------------------- |
64
+ | `templates` | `templates` | Where the unpacked exports live |
65
+ | `host` | `127.0.0.1` | The address Penpot binds to |
66
+ | `port` | `9001` | The port Penpot binds to |
67
+ | `project` | `penpot-local` | The Compose project name |
68
+ | `version` | `2.17.2` | The Penpot image tag |
69
+ | `email` | `designer@example.com` | The throwaway profile |
70
+ | `password` | `penpot-local` | Its password |
71
+
72
+ Drop a key to fall back to its default. Every key also answers to an
73
+ environment variable with a `PENPOT_` prefix, and the variable wins over the
74
+ file:
75
+
76
+ ```bash
77
+ PENPOT_PORT=9100 make up
78
+ ```
79
+
80
+ ## Design loop
81
+
82
+ Start the stack and pick a template or `(empty)`:
83
+
84
+ ```bash
85
+ make up
86
+ ```
87
+
88
+ `up` creates a throwaway profile and opens the browser already logged in
89
+ through the printed `/autologin` link. If that session is lost, log in as
90
+ `designer@example.com` with the password `penpot-local`.
91
+
92
+ Draw in the browser at `http://localhost:9001`, or wherever `port` points. When you are done, pick the
93
+ file to export and the template to write it to, then let the stack go down:
94
+
95
+ ```bash
96
+ make down
97
+ ```
98
+
99
+ `down` writes the export into `templates/<name>/` and runs
100
+ `docker compose down -v`, which drops the database and the assets. Pick
101
+ `(skip export)` to throw the work away; the wipe is confirmed first whenever
102
+ the stack still holds a file.
103
+
104
+ ## Commands
105
+
106
+ | Target | What it does |
107
+ | -------------- | --------------------------------------------------------- |
108
+ | `make up` | Start the stack, import a template, open the browser |
109
+ | `make down` | Offer the export, confirm the wipe, stop the stack |
110
+ | `make import` | Import a template into the running stack, after a confirm |
111
+ | `make export` | Export a file into `templates/`, after a confirm |
112
+ | `make extract` | Unpack a `.penpot` file into `templates/` |
113
+ | `make convert` | Pack a template into a `.penpot` file |
114
+
115
+ `import` and `export` work against a running stack, so a template can be
116
+ swapped in or a file saved off without ending the session. Both ask before
117
+ they touch anything: `import` names the template it is about to load, `export`
118
+ says whether it writes a new template or overwrites one that is already there.
119
+
120
+ `extract` and `convert` are the offline pair and need no stack at all.
121
+ `extract` lists the `.penpot` files next to you, asks which template to write
122
+ and unpacks the archive under the same rules as `export`: frame thumbnails are
123
+ dropped, JSON is reformatted, and an archive holding raster images is refused
124
+ with the offending files listed. `convert` goes the other way and packs
125
+ `templates/<name>/` into `<name>.penpot`, which is what you hand to a Penpot
126
+ that is not this one. Both confirm before writing, and say whether they are
127
+ creating something or overwriting it.
128
+
129
+ ## Templates
130
+
131
+ - One directory per template: `manifest.json`, `files/` and `objects/`.
132
+ - Templates are read from `templates/` in the current directory. Set
133
+ `PENPOT_TEMPLATES` to read them from somewhere else.
134
+ - Images and icons must be SVG. An export that holds raster images is refused
135
+ and the offending files are listed, so you can replace them and export
136
+ again.
137
+ - Frame thumbnails are raster renders Penpot rebuilds on its own, so the
138
+ export leaves them out.
139
+ - Import keeps the Penpot file id, so a round trip changes only what you drew.
140
+
141
+ ## Connect Penpot MCP
142
+
143
+ `up` enables MCP for the throwaway profile, issues its key and serves it
144
+ behind the fixed `/mcp/claude` URL, so an agent needs the server added only
145
+ once. Add it, then restart the agent:
146
+
147
+ ```bash
148
+ claude mcp add --transport http penpot http://localhost:9001/mcp/claude
149
+ ```
150
+
151
+ After later `up` runs, reconnect `penpot` from `/mcp` instead of restarting.
152
+ While the stack is down, the server just shows as failed.
153
+
154
+ The MCP plugin runs inside the Penpot browser tab. Keep a file open in the
155
+ workspace while the agent works; a hidden or unloaded tab stops MCP.
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env python3
2
+ """Run the stack from a checkout, without installing the package."""
3
+
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
8
+
9
+ from libs.cli import main # noqa: E402
10
+
11
+ if __name__ == "__main__":
12
+ main()
@@ -0,0 +1,5 @@
1
+ location = /autologin {
2
+ absolute_redirect off;
3
+ add_header Set-Cookie "auth-token=$arg_token; Path=/; HttpOnly; SameSite=Lax";
4
+ return 302 /;
5
+ }