Skip to content

Commit b4bf691

Browse files
committed
chores: rename project; vulns audit; changelog and releases
1 parent d968b87 commit b4bf691

29 files changed

Lines changed: 399 additions & 78 deletions

.github/workflows/ci.yml

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,41 @@ jobs:
1010
lint:
1111
runs-on: ubuntu-latest
1212
steps:
13-
- uses: actions/checkout@v4
14-
- uses: actions/setup-python@v5
13+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
14+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
1515
with:
1616
python-version: "3.12"
17+
# Stdlib-only, so it runs before any install to fail as fast as possible.
18+
- name: Changelog documents the declared version
19+
run: python scripts/check_changelog.py
1720
- run: pip install ruff
1821
- run: ruff check src scripts tests
1922
- run: ruff format --check src scripts tests
2023

24+
audit:
25+
# Known-vulnerability scan of the runtime dependency surface against the PyPI
26+
# advisory database. Auditing the project (rather than a frozen env) resolves
27+
# dependencies fresh from pyproject.toml, so this reflects what an operator's
28+
# install actually gets — including newer versions our `>=` floors allow.
29+
runs-on: ubuntu-latest
30+
steps:
31+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
32+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
33+
with:
34+
python-version: "3.12"
35+
- run: pip install pip-audit
36+
- name: Audit dependencies for known vulnerabilities
37+
run: pip-audit .
38+
2139
unit-tests:
2240
runs-on: ubuntu-latest
2341
strategy:
2442
fail-fast: false
2543
matrix:
2644
python-version: ["3.11", "3.12", "3.13"]
2745
steps:
28-
- uses: actions/checkout@v4
29-
- uses: actions/setup-python@v5
46+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
47+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
3048
with:
3149
python-version: ${{ matrix.python-version }}
3250
- run: pip install -e ".[dev]"
@@ -36,8 +54,8 @@ jobs:
3654
# Fails if the committed single-file distributables drifted from src/.
3755
runs-on: ubuntu-latest
3856
steps:
39-
- uses: actions/checkout@v4
40-
- uses: actions/setup-python@v5
57+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
58+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
4159
with:
4260
python-version: "3.12"
4361
- run: pip install -e ".[dev]"
@@ -51,8 +69,8 @@ jobs:
5169
runs-on: ubuntu-latest
5270
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
5371
steps:
54-
- uses: actions/checkout@v4
55-
- uses: actions/setup-python@v5
72+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
73+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
5674
with:
5775
python-version: "3.12"
5876
- run: pip install -e ".[dev]"

.github/workflows/release.yml

Lines changed: 142 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,165 @@
11
name: Release
22

3+
# Runs every CI gate, then tags the commit and cuts a GitHub release with both
4+
# single-file distributables attached as assets.
5+
#
6+
# The version is whatever `pyproject.toml` says — bump it on main first (along
7+
# with the other three places `scripts/check_version.py` checks), then dispatch
8+
# this workflow. `dry_run` (on by default) runs every gate and touches neither
9+
# the repo nor any release, so a rehearsal is free.
10+
#
11+
# There is no PyPI publish: these plugins are distributed as single files via the
12+
# Open WebUI community hub and as GitHub Release assets, not as a pip package.
13+
# The release itself is therefore the artifact, and creating it is the last step.
14+
315
on:
4-
release:
5-
types: [published]
616
workflow_dispatch:
717
inputs:
8-
tag:
9-
description: "Release tag to validate against (e.g. v0.1.0)"
10-
required: true
18+
dry_run:
19+
description: Run checks without tagging or creating the release
20+
type: boolean
21+
default: true
22+
23+
permissions:
24+
contents: write # pushes the version tag and creates the release
25+
26+
concurrency:
27+
group: release-${{ github.ref }}
28+
cancel-in-progress: false
1129

12-
# No PyPI publish: these plugins are distributed as single files via the Open
13-
# WebUI community hub and as GitHub Release assets, not as a pip package.
1430
jobs:
15-
release-assets:
31+
release:
1632
runs-on: ubuntu-latest
17-
permissions:
18-
contents: write
1933
steps:
20-
- uses: actions/checkout@v4
21-
- uses: actions/setup-python@v5
34+
- name: Checkout
35+
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
36+
with:
37+
# Tags are needed to tell "already released" from "tag collision".
38+
fetch-depth: 0
39+
40+
- name: Setup Python
41+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
2242
with:
2343
python-version: "3.12"
24-
- run: pip install -e ".[dev]"
44+
45+
# The version about to be released must be documented. First gate, before
46+
# anything is installed: a release whose version isn't in the changelog is
47+
# a release we'd have to redo.
48+
- name: Check the changelog covers this version
49+
run: python scripts/check_changelog.py
50+
51+
- name: Install dependencies
52+
run: pip install -e ".[dev]"
53+
54+
# The same gates as CI, so a release can never ship something CI would have
55+
# rejected — including build-sync, which asserts the committed single-file
56+
# distributables still match the source they're generated from.
57+
58+
- name: Lint
59+
run: ruff check src scripts tests
60+
61+
- name: Check formatting
62+
run: ruff format --check src scripts tests
63+
64+
- name: Test
65+
run: pytest tests/unit -q
66+
67+
- name: Audit dependencies for known vulnerabilities
68+
run: |
69+
pip install pip-audit
70+
pip-audit .
2571
2672
- name: Build distributables
2773
run: python scripts/build.py
2874

2975
- name: Assert committed distributables are in sync
3076
run: git diff --exit-code open-webui/
3177

32-
- name: Assert version matches the release tag
78+
# Notes come from the changelog rather than --generate-notes, so the release
79+
# reads as something written for operators instead of a commit list. This
80+
# also rejects an empty section, which check_changelog.py accepts.
81+
- name: Extract release notes from the changelog
82+
run: python scripts/changelog_notes.py | tee "${RUNNER_TEMP}/notes.md"
83+
84+
- name: Resolve version
85+
id: version
86+
run: |
87+
set -euo pipefail
88+
VERSION="$(python -c 'import re,pathlib; print(re.search(r"^version\s*=\s*\"([^\"]+)\"", pathlib.Path("pyproject.toml").read_text(), re.M).group(1))')"
89+
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
90+
echo "tag=v${VERSION}" >> "$GITHUB_OUTPUT"
91+
92+
- name: Assert every declared version agrees with the tag
93+
run: python scripts/check_version.py "${{ steps.version.outputs.tag }}"
94+
95+
# Fail before tagging rather than after: a tag on some other commit means
96+
# the version was not bumped.
97+
- name: Check the version tag is free
98+
env:
99+
TAG: ${{ steps.version.outputs.tag }}
33100
run: |
34-
TAG="${{ github.event.release.tag_name || github.event.inputs.tag }}"
35-
python scripts/check_version.py "$TAG"
101+
set -euo pipefail
102+
if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
103+
if [ "$(git rev-list -n1 "${TAG}")" != "${GITHUB_SHA}" ]; then
104+
echo "${TAG} already exists and points at a different commit — bump the version" >&2
105+
exit 1
106+
fi
107+
echo "${TAG} already points at this commit"
108+
fi
36109
37-
- name: Upload distributables as release assets
38-
if: github.event_name == 'release'
110+
- name: Tag the release
39111
env:
112+
DRY_RUN: ${{ inputs.dry_run }}
113+
TAG: ${{ steps.version.outputs.tag }}
114+
run: |
115+
set -euo pipefail
116+
117+
if [ "${DRY_RUN}" = "true" ]; then
118+
echo "dry run: would tag ${GITHUB_SHA} as ${TAG}"
119+
exit 0
120+
fi
121+
122+
if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
123+
echo "${TAG} already exists, leaving it alone"
124+
exit 0
125+
fi
126+
127+
git -c user.name='github-actions[bot]' \
128+
-c user.email='41898282+github-actions[bot]@users.noreply.github.com' \
129+
tag -a "${TAG}" -m "${TAG}"
130+
git push origin "${TAG}"
131+
132+
# Creating the release comes last, so a failed gate leaves no tag and no
133+
# release behind. The reverse gap — tagged but no release — is recoverable:
134+
# this step is idempotent, so re-running the workflow finishes the job.
135+
- name: Create the GitHub release
136+
env:
137+
DRY_RUN: ${{ inputs.dry_run }}
40138
GH_TOKEN: ${{ github.token }}
139+
TAG: ${{ steps.version.outputs.tag }}
41140
run: |
42-
gh release upload "${{ github.event.release.tag_name }}" \
43-
open-webui/tools/tenki_code_execution.py \
44-
open-webui/functions/tenki_run_code.py \
45-
--clobber
141+
set -euo pipefail
142+
143+
ASSETS=(
144+
open-webui/tools/tenki_code_execution.py
145+
open-webui/functions/tenki_run_code.py
146+
)
147+
148+
if [ "${DRY_RUN}" = "true" ]; then
149+
echo "dry run: would create release ${TAG} with assets: ${ASSETS[*]}"
150+
exit 0
151+
fi
152+
153+
# Assets are refreshed but existing notes are left alone, in case they
154+
# were edited by hand after the release was cut.
155+
if gh release view "${TAG}" >/dev/null 2>&1; then
156+
echo "release ${TAG} already exists, refreshing its assets"
157+
gh release upload "${TAG}" "${ASSETS[@]}" --clobber
158+
exit 0
159+
fi
160+
161+
gh release create "${TAG}" \
162+
--title "${TAG}" \
163+
--notes-file "${RUNNER_TEMP}/notes.md" \
164+
--verify-tag \
165+
"${ASSETS[@]}"

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Changelog
2+
3+
All notable changes to this project are documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
Versions track the `version:` frontmatter of both single-file distributables;
9+
`scripts/check_version.py` keeps those in step with `pyproject.toml`, and
10+
`scripts/check_changelog.py` requires the declared version to appear below.
11+
12+
## 0.1.0 — 2026-07-31
13+
14+
First release. Ships two self-contained Open WebUI plugins that run Python and
15+
shell code in isolated, ephemeral [Tenki](https://tenki.cloud) microVM sandboxes:
16+
a **Tool** (`tenki_code_execution`) the model calls itself to write code, read the
17+
output — stdout, stderr, tracebacks, and inline charts — and iterate, and an
18+
**Action Function** (`tenki_run_code`) that adds a **Run code** button under any
19+
message with a code block. Both share one async execution core, so every run gets
20+
a fresh sandbox with no state carried between runs, a timeout, and guaranteed
21+
teardown. Network egress is off by default; the API key lives in Open WebUI's
22+
plugin settings and is never logged, and users may supply their own key to run
23+
executions on their own Tenki account.

CONTRIBUTING.md

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Development
44

5-
The logic is written once under `src/open_webui_tenki/` and inlined into the two
5+
The logic is written once under `src/tenki_open_webui/` and inlined into the two
66
single-file distributables under `open-webui/` by `scripts/build.py`. **Never edit
77
the files under `open-webui/` by hand** — they are generated.
88

@@ -14,6 +14,9 @@ ruff check src scripts tests # lint
1414
ruff format src scripts tests # format
1515
pytest tests/unit # unit tests (mocked SDK, no credentials)
1616
python scripts/build.py # regenerate the distributables
17+
python scripts/check_changelog.py # declared version has a CHANGELOG entry
18+
python scripts/changelog_notes.py # preview the release notes for that version
19+
pipx run pip-audit . # known-vulnerability scan (as CI runs it)
1720
```
1821

1922
After changing anything in `src/`, run `python scripts/build.py` and commit the
@@ -32,15 +35,29 @@ python scripts/try_live.py # ad-hoc single run; images saved to ./liv
3235
## Cutting a release
3336

3437
1. Bump the version in these places (they must match — `scripts/check_version.py`
35-
enforces it): `pyproject.toml`, `src/open_webui_tenki/__init__.py`
38+
enforces it): `pyproject.toml`, `src/tenki_open_webui/__init__.py`
3639
(`__version__`), and the `version:` frontmatter for both distributables (which
3740
lives in `scripts/build.py`).
38-
2. `python scripts/build.py` and commit the regenerated files.
39-
3. `python scripts/check_version.py vX.Y.Z` to confirm consistency.
40-
4. Create a GitHub Release tagged `vX.Y.Z`. The `release` workflow verifies the
41-
tag matches the declared version and attaches both distributables as release
41+
2. Add the matching `CHANGELOG.md` section — `## X.Y.Z — YYYY-MM-DD`, newest
42+
first, changes grouped under Keep a Changelog headings (`### Added`,
43+
`### Changed`, `### Security`, …). This section becomes the release notes
44+
verbatim, so write it for operators. `scripts/check_changelog.py` requires the
45+
declared version to have an entry: CI runs it on every pull request, and the
46+
release workflow runs it as its first gate.
47+
3. `python scripts/build.py` and commit the regenerated files.
48+
4. `python scripts/check_version.py vX.Y.Z` to confirm consistency.
49+
5. Run the **Release** workflow with `dry_run` left on. It runs every CI gate
50+
plus the version and changelog checks, and reports what it would tag and
51+
publish without touching the repo — a free rehearsal.
52+
6. Run it again with `dry_run` off. It tags `vX.Y.Z`, creates the GitHub Release
53+
with the changelog section as its notes, and attaches both distributables as
4254
assets. (No PyPI publish — distribution is single-file via the hub.)
4355

56+
The workflow derives the version from `pyproject.toml`, so there's no tag to
57+
create by hand; it refuses to run if `vX.Y.Z` already points at another commit.
58+
Actions are pinned by commit SHA with the version in a trailing comment — update
59+
both together.
60+
4461
## Publishing to the Open WebUI Community site
4562

4663
Both plugins are published to the [Open WebUI Community site](https://openwebui.com/)

README.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Open WebUI Tenki — Code Execution in Isolated microVM Sandboxes
1+
# Tenki for Open WebUI — Code Execution in Isolated microVM Sandboxes
22

33
Run **Python and shell code** from [Open WebUI](https://openwebui.com) inside
44
**isolated, ephemeral [Tenki](https://tenki.cloud) microVM sandboxes** — a secure
@@ -138,8 +138,8 @@ For a system package on an image with `apt`, the equivalent is
138138
## Develop & test
139139

140140
```bash
141-
git clone https://github.com/LuxorLabs/open-webui-tenki
142-
cd open-webui-tenki
141+
git clone https://github.com/LuxorLabs/tenki-open-webui
142+
cd tenki-open-webui
143143
python -m venv .venv && source .venv/bin/activate
144144
pip install -e ".[dev]"
145145

@@ -156,13 +156,18 @@ python scripts/build.py
156156

157157
### Architecture
158158

159-
The logic lives once in `src/open_webui_tenki/` (`config.py`, `result.py`,
159+
The logic lives once in `src/tenki_open_webui/` (`config.py`, `result.py`,
160160
`languages.py`, `core.py`, plus the `tool.py` entry point). `scripts/build.py`
161161
**inlines** that core into each single-file distributable under `open-webui/`,
162162
so hub users can one-click import a self-contained file. Edit `src/`, then run
163163
`python scripts/build.py` — CI verifies the committed distributables match the
164164
source.
165165

166+
## Changelog
167+
168+
See [CHANGELOG.md](./CHANGELOG.md). Tagged versions are published as GitHub
169+
Releases with both single-file distributables attached as assets.
170+
166171
## License
167172

168173
MIT — see [LICENSE](./LICENSE).

open-webui/functions/tenki_run_code.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
"""
22
title: Tenki Sandbox — Run Code
33
author: LuxorLabs
4-
author_url: https://github.com/LuxorLabs/open-webui-tenki
5-
funding_url: https://github.com/LuxorLabs/open-webui-tenki
4+
author_url: https://github.com/LuxorLabs/tenki-open-webui
5+
funding_url: https://github.com/LuxorLabs/tenki-open-webui
66
description: A "Run code" button that executes a message's Python or shell code block in an isolated, ephemeral Tenki microVM sandbox — a secure code interpreter / code execution sandbox for Open WebUI.
77
version: 0.1.0
88
license: MIT
@@ -11,7 +11,7 @@
1111

1212
# ---------------------------------------------------------------------------
1313
# AUTO-GENERATED by scripts/build.py — do not edit directly.
14-
# Source of truth: src/open_webui_tenki/. Rebuild: python scripts/build.py
14+
# Source of truth: src/tenki_open_webui/. Rebuild: python scripts/build.py
1515
# ---------------------------------------------------------------------------
1616

1717
from __future__ import annotations

0 commit comments

Comments
 (0)