kohakutui 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.
- kohakutui-0.1.0/.github/workflows/ci.yml +75 -0
- kohakutui-0.1.0/.github/workflows/nightly.yml +60 -0
- kohakutui-0.1.0/.github/workflows/release.yml +220 -0
- kohakutui-0.1.0/.gitignore +40 -0
- kohakutui-0.1.0/CLAUDE.md +106 -0
- kohakutui-0.1.0/Cargo.lock +1275 -0
- kohakutui-0.1.0/Cargo.toml +48 -0
- kohakutui-0.1.0/LICENSE +201 -0
- kohakutui-0.1.0/PKG-INFO +228 -0
- kohakutui-0.1.0/README.md +193 -0
- kohakutui-0.1.0/docs/README.md +21 -0
- kohakutui-0.1.0/docs/components.md +139 -0
- kohakutui-0.1.0/docs/concepts.md +117 -0
- kohakutui-0.1.0/docs/dialogs.md +111 -0
- kohakutui-0.1.0/docs/getting-started.md +137 -0
- kohakutui-0.1.0/docs/images/agent.png +0 -0
- kohakutui-0.1.0/docs/images/approval.png +0 -0
- kohakutui-0.1.0/docs/images/dashboard.png +0 -0
- kohakutui-0.1.0/docs/images/picker.png +0 -0
- kohakutui-0.1.0/docs/images/train.png +0 -0
- kohakutui-0.1.0/docs/releasing.md +78 -0
- kohakutui-0.1.0/docs/testing.md +90 -0
- kohakutui-0.1.0/docs/themes.md +47 -0
- kohakutui-0.1.0/pyproject.toml +65 -0
- kohakutui-0.1.0/rustfmt.toml +10 -0
- kohakutui-0.1.0/scripts/release_version.py +68 -0
- kohakutui-0.1.0/scripts/screenshots.py +361 -0
- kohakutui-0.1.0/src/kohakutui/README.md +27 -0
- kohakutui-0.1.0/src/kohakutui/__init__.py +67 -0
- kohakutui-0.1.0/src/kohakutui/__main__.py +40 -0
- kohakutui-0.1.0/src/kohakutui/_rs.pyi +37 -0
- kohakutui-0.1.0/src/kohakutui/app/README.md +26 -0
- kohakutui-0.1.0/src/kohakutui/app/__init__.py +6 -0
- kohakutui-0.1.0/src/kohakutui/app/app.py +69 -0
- kohakutui-0.1.0/src/kohakutui/app/capture.py +138 -0
- kohakutui-0.1.0/src/kohakutui/app/control.py +145 -0
- kohakutui-0.1.0/src/kohakutui/app/core.py +167 -0
- kohakutui-0.1.0/src/kohakutui/app/dialogs.py +165 -0
- kohakutui-0.1.0/src/kohakutui/app/events.py +216 -0
- kohakutui-0.1.0/src/kohakutui/demo/README.md +14 -0
- kohakutui-0.1.0/src/kohakutui/demo/__init__.py +1 -0
- kohakutui-0.1.0/src/kohakutui/demo/agent.py +308 -0
- kohakutui-0.1.0/src/kohakutui/demo/chat.py +90 -0
- kohakutui-0.1.0/src/kohakutui/demo/dashboard.py +162 -0
- kohakutui-0.1.0/src/kohakutui/demo/parts.py +118 -0
- kohakutui-0.1.0/src/kohakutui/demo/tasks.py +193 -0
- kohakutui-0.1.0/src/kohakutui/demo/train.py +101 -0
- kohakutui-0.1.0/src/kohakutui/py.typed +0 -0
- kohakutui-0.1.0/src/kohakutui/ui/README.md +16 -0
- kohakutui-0.1.0/src/kohakutui/ui/__init__.py +55 -0
- kohakutui-0.1.0/src/kohakutui/ui/components.py +339 -0
- kohakutui-0.1.0/src/kohakutui/ui/node.py +77 -0
- kohakutui-0.1.0/src/kohakutui/ui/widget.py +39 -0
- kohakutui-0.1.0/src/kohakutui-rust/README.md +33 -0
- kohakutui-0.1.0/src/kohakutui-rust/lib.rs +45 -0
- kohakutui-0.1.0/src/kohakutui-rust/model/apply.rs +145 -0
- kohakutui-0.1.0/src/kohakutui-rust/model/mod.rs +83 -0
- kohakutui-0.1.0/src/kohakutui-rust/model/tests.rs +120 -0
- kohakutui-0.1.0/src/kohakutui-rust/model/tree.rs +207 -0
- kohakutui-0.1.0/src/kohakutui-rust/proto/decode.rs +133 -0
- kohakutui-0.1.0/src/kohakutui-rust/proto/encode.rs +158 -0
- kohakutui-0.1.0/src/kohakutui-rust/proto/mod.rs +159 -0
- kohakutui-0.1.0/src/kohakutui-rust/proto/python.rs +103 -0
- kohakutui-0.1.0/src/kohakutui-rust/proto/tests.rs +117 -0
- kohakutui-0.1.0/src/kohakutui-rust/proto/value.rs +93 -0
- kohakutui-0.1.0/src/kohakutui-rust/runtime/engine.rs +515 -0
- kohakutui-0.1.0/src/kohakutui-rust/runtime/input.rs +64 -0
- kohakutui-0.1.0/src/kohakutui-rust/runtime/mod.rs +105 -0
- kohakutui-0.1.0/src/kohakutui-rust/runtime/python.rs +277 -0
- kohakutui-0.1.0/src/kohakutui-rust/runtime/session.rs +148 -0
- kohakutui-0.1.0/src/kohakutui-rust/runtime/tests.rs +243 -0
- kohakutui-0.1.0/src/kohakutui-rust/term/guard.rs +64 -0
- kohakutui-0.1.0/src/kohakutui-rust/term/history.rs +117 -0
- kohakutui-0.1.0/src/kohakutui-rust/term/mod.rs +88 -0
- kohakutui-0.1.0/src/kohakutui-rust/term/out.rs +261 -0
- kohakutui-0.1.0/src/kohakutui-rust/term/screen.rs +357 -0
- kohakutui-0.1.0/src/kohakutui-rust/term/tests.rs +122 -0
- kohakutui-0.1.0/src/kohakutui-rust/text/markdown.rs +461 -0
- kohakutui-0.1.0/src/kohakutui-rust/text/mod.rs +25 -0
- kohakutui-0.1.0/src/kohakutui-rust/text/stream.rs +107 -0
- kohakutui-0.1.0/src/kohakutui-rust/text/tests.rs +244 -0
- kohakutui-0.1.0/src/kohakutui-rust/text/theme.rs +443 -0
- kohakutui-0.1.0/src/kohakutui-rust/text/wrap.rs +170 -0
- kohakutui-0.1.0/src/kohakutui-rust/view/chord.rs +70 -0
- kohakutui-0.1.0/src/kohakutui-rust/view/flush.rs +111 -0
- kohakutui-0.1.0/src/kohakutui-rust/view/input.rs +396 -0
- kohakutui-0.1.0/src/kohakutui-rust/view/layout.rs +534 -0
- kohakutui-0.1.0/src/kohakutui-rust/view/mod.rs +148 -0
- kohakutui-0.1.0/src/kohakutui-rust/view/render.rs +376 -0
- kohakutui-0.1.0/src/kohakutui-rust/view/tests.rs +749 -0
- kohakutui-0.1.0/src/kohakutui-rust/widgets/button.rs +165 -0
- kohakutui-0.1.0/src/kohakutui-rust/widgets/cell.rs +248 -0
- kohakutui-0.1.0/src/kohakutui-rust/widgets/chart.rs +327 -0
- kohakutui-0.1.0/src/kohakutui-rust/widgets/composer.rs +335 -0
- kohakutui-0.1.0/src/kohakutui-rust/widgets/data.rs +248 -0
- kohakutui-0.1.0/src/kohakutui-rust/widgets/dialogs.rs +435 -0
- kohakutui-0.1.0/src/kohakutui-rust/widgets/editor.rs +202 -0
- kohakutui-0.1.0/src/kohakutui-rust/widgets/layout.rs +77 -0
- kohakutui-0.1.0/src/kohakutui-rust/widgets/markdown.rs +103 -0
- kohakutui-0.1.0/src/kohakutui-rust/widgets/mod.rs +183 -0
- kohakutui-0.1.0/src/kohakutui-rust/widgets/picker.rs +897 -0
- kohakutui-0.1.0/src/kohakutui-rust/widgets/props.rs +55 -0
- kohakutui-0.1.0/src/kohakutui-rust/widgets/registry.rs +62 -0
- kohakutui-0.1.0/src/kohakutui-rust/widgets/scroll.rs +188 -0
- kohakutui-0.1.0/src/kohakutui-rust/widgets/status.rs +310 -0
- kohakutui-0.1.0/src/kohakutui-rust/widgets/tests.rs +808 -0
- kohakutui-0.1.0/src/kohakutui-rust/widgets/text.rs +225 -0
- kohakutui-0.1.0/tests/README.md +44 -0
- kohakutui-0.1.0/tests/__init__.py +0 -0
- kohakutui-0.1.0/tests/conftest.py +32 -0
- kohakutui-0.1.0/tests/e2e/__init__.py +0 -0
- kohakutui-0.1.0/tests/e2e/test_chat_journey.py +107 -0
- kohakutui-0.1.0/tests/e2e/test_demos.py +199 -0
- kohakutui-0.1.0/tests/e2e/test_readme.py +45 -0
- kohakutui-0.1.0/tests/integration/__init__.py +0 -0
- kohakutui-0.1.0/tests/integration/test_app.py +364 -0
- kohakutui-0.1.0/tests/static/__init__.py +0 -0
- kohakutui-0.1.0/tests/static/test_file_sizes.py +54 -0
- kohakutui-0.1.0/tests/static/test_layout.py +72 -0
- kohakutui-0.1.0/tests/static/test_stub.py +38 -0
- kohakutui-0.1.0/tests/unit/__init__.py +0 -0
- kohakutui-0.1.0/tests/unit/app/__init__.py +0 -0
- kohakutui-0.1.0/tests/unit/app/test_events.py +29 -0
- kohakutui-0.1.0/tests/unit/test_init.py +32 -0
- kohakutui-0.1.0/tests/unit/test_release_version.py +47 -0
- kohakutui-0.1.0/tests/unit/ui/__init__.py +0 -0
- kohakutui-0.1.0/tests/unit/ui/test_components.py +33 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
concurrency:
|
|
11
|
+
group: ci-${{ github.ref }}
|
|
12
|
+
cancel-in-progress: true
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
lint:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: "3.13"
|
|
22
|
+
- run: pip install ruff black
|
|
23
|
+
- run: ruff check src/ tests/ scripts/
|
|
24
|
+
- run: black --check src/ tests/ scripts/
|
|
25
|
+
|
|
26
|
+
rust:
|
|
27
|
+
name: Rust (fmt, clippy, stage tests)
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
steps:
|
|
30
|
+
- uses: actions/checkout@v4
|
|
31
|
+
- uses: actions/setup-python@v5
|
|
32
|
+
with:
|
|
33
|
+
python-version: "3.13"
|
|
34
|
+
- uses: dtolnay/rust-toolchain@stable
|
|
35
|
+
with:
|
|
36
|
+
components: rustfmt, clippy
|
|
37
|
+
- uses: Swatinem/rust-cache@v2
|
|
38
|
+
- run: cargo fmt --check
|
|
39
|
+
- run: cargo clippy --all-targets -- -D warnings
|
|
40
|
+
- run: cargo clippy --all-targets --features python -- -D warnings
|
|
41
|
+
# Without the python feature the test binary links nothing of Python
|
|
42
|
+
- run: cargo test --no-default-features
|
|
43
|
+
- name: One crossterm in the dependency graph
|
|
44
|
+
run: |
|
|
45
|
+
n=$(cargo tree -e normal -i crossterm --depth 0 2>/dev/null | grep -c '^crossterm' || true)
|
|
46
|
+
test "$n" -le 1 || { cargo tree -d; exit 1; }
|
|
47
|
+
|
|
48
|
+
test:
|
|
49
|
+
runs-on: ${{ matrix.os }}
|
|
50
|
+
strategy:
|
|
51
|
+
fail-fast: false
|
|
52
|
+
matrix:
|
|
53
|
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
54
|
+
python-version: ["3.10", "3.14"]
|
|
55
|
+
steps:
|
|
56
|
+
- uses: actions/checkout@v4
|
|
57
|
+
- uses: actions/setup-python@v5
|
|
58
|
+
with:
|
|
59
|
+
python-version: ${{ matrix.python-version }}
|
|
60
|
+
- uses: dtolnay/rust-toolchain@stable
|
|
61
|
+
- uses: Swatinem/rust-cache@v2
|
|
62
|
+
- uses: astral-sh/setup-uv@v6
|
|
63
|
+
- name: Build the wheel
|
|
64
|
+
shell: bash
|
|
65
|
+
run: |
|
|
66
|
+
uv venv
|
|
67
|
+
uv pip install maturin
|
|
68
|
+
uv run --no-project maturin build --release --out dist
|
|
69
|
+
- name: Install the built wheel
|
|
70
|
+
shell: bash
|
|
71
|
+
run: |
|
|
72
|
+
WHEEL=$(ls dist/*.whl)
|
|
73
|
+
uv pip install "${WHEEL}[dev]"
|
|
74
|
+
- name: Tests
|
|
75
|
+
run: uv run --no-project pytest tests/ -q --tb=short
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
name: Nightly
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
schedule:
|
|
5
|
+
- cron: '0 3 * * *'
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
inputs:
|
|
8
|
+
force_rebuild:
|
|
9
|
+
description: Build even if this commit already has nightly artifacts
|
|
10
|
+
type: boolean
|
|
11
|
+
default: false
|
|
12
|
+
|
|
13
|
+
# Like KohakuVault and KohakuTerrarium: nightly only decides; release.yml owns every build and
|
|
14
|
+
# upload, so there is one matrix and one PyPI trusted publisher.
|
|
15
|
+
concurrency:
|
|
16
|
+
group: nightly-dispatch
|
|
17
|
+
cancel-in-progress: false
|
|
18
|
+
|
|
19
|
+
permissions:
|
|
20
|
+
contents: read
|
|
21
|
+
actions: write
|
|
22
|
+
|
|
23
|
+
jobs:
|
|
24
|
+
dispatch:
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v4
|
|
28
|
+
with:
|
|
29
|
+
ref: main
|
|
30
|
+
- name: Require green CI, skip a commit already published
|
|
31
|
+
id: gate
|
|
32
|
+
env:
|
|
33
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
34
|
+
FORCE: ${{ inputs.force_rebuild || false }}
|
|
35
|
+
shell: bash
|
|
36
|
+
run: |
|
|
37
|
+
set -euo pipefail
|
|
38
|
+
sha=$(git rev-parse HEAD)
|
|
39
|
+
result=$(gh run list --workflow ci.yml --commit "$sha" --event push --limit 1 --json conclusion --jq '.[0].conclusion // "missing"')
|
|
40
|
+
if [ "$result" != success ]; then
|
|
41
|
+
echo "::error::CI for $sha is $result; nightly not dispatched"
|
|
42
|
+
exit 1
|
|
43
|
+
fi
|
|
44
|
+
previous=$(gh release list --limit 100 --json tagName --jq '[.[] | select(.tagName | startswith("nightly-"))][0].tagName // ""')
|
|
45
|
+
if [ -n "$previous" ] && [ "$FORCE" != true ]; then
|
|
46
|
+
body=$(gh release view "$previous" --json body --jq .body)
|
|
47
|
+
if printf '%s' "$body" | grep -Fq "Source commit: $sha"; then
|
|
48
|
+
echo "Commit $sha already published" >> "$GITHUB_STEP_SUMMARY"
|
|
49
|
+
exit 0
|
|
50
|
+
fi
|
|
51
|
+
fi
|
|
52
|
+
echo "sha=$sha" >> "$GITHUB_OUTPUT"
|
|
53
|
+
- name: Dispatch the release builder (nightly channel)
|
|
54
|
+
if: steps.gate.outputs.sha != ''
|
|
55
|
+
env:
|
|
56
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
57
|
+
SOURCE_SHA: ${{ steps.gate.outputs.sha }}
|
|
58
|
+
run: |
|
|
59
|
+
gh workflow run release.yml --ref main -f ref="$SOURCE_SHA" -f channel=nightly
|
|
60
|
+
echo "Dispatched nightly for $SOURCE_SHA." >> "$GITHUB_STEP_SUMMARY"
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
push:
|
|
7
|
+
tags:
|
|
8
|
+
- 'v*'
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
inputs:
|
|
11
|
+
ref:
|
|
12
|
+
description: Exact commit or ref to build
|
|
13
|
+
default: main
|
|
14
|
+
type: string
|
|
15
|
+
channel:
|
|
16
|
+
description: Build only, or publish a nightly (never a stable manual release)
|
|
17
|
+
default: build
|
|
18
|
+
type: choice
|
|
19
|
+
options: [build, nightly]
|
|
20
|
+
|
|
21
|
+
concurrency:
|
|
22
|
+
group: release-${{ inputs.channel || github.ref }}
|
|
23
|
+
cancel-in-progress: false
|
|
24
|
+
|
|
25
|
+
permissions:
|
|
26
|
+
contents: read
|
|
27
|
+
actions: read
|
|
28
|
+
|
|
29
|
+
defaults:
|
|
30
|
+
run:
|
|
31
|
+
shell: bash
|
|
32
|
+
|
|
33
|
+
# The only workflow that uploads kohakutui, so PyPI needs one trusted publisher: this file
|
|
34
|
+
# (release.yml), environment `pypi`. Nightly runs come here too, dispatched by nightly.yml.
|
|
35
|
+
jobs:
|
|
36
|
+
meta:
|
|
37
|
+
name: Resolve the version
|
|
38
|
+
if: github.event_name != 'release' || startsWith(github.event.release.tag_name, 'v')
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
outputs:
|
|
41
|
+
sha: ${{ steps.source.outputs.sha }}
|
|
42
|
+
cargo_version: ${{ steps.version.outputs.cargo_version }}
|
|
43
|
+
python_version: ${{ steps.version.outputs.python_version }}
|
|
44
|
+
tag: ${{ steps.version.outputs.tag }}
|
|
45
|
+
nightly: ${{ steps.version.outputs.nightly }}
|
|
46
|
+
steps:
|
|
47
|
+
- uses: actions/checkout@v4
|
|
48
|
+
with:
|
|
49
|
+
ref: ${{ inputs.ref || github.ref }}
|
|
50
|
+
- uses: actions/setup-python@v5
|
|
51
|
+
with:
|
|
52
|
+
python-version: '3.13'
|
|
53
|
+
- name: Resolve the source; a nightly needs green CI on it
|
|
54
|
+
id: source
|
|
55
|
+
env:
|
|
56
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
57
|
+
CHANNEL: ${{ inputs.channel }}
|
|
58
|
+
run: |
|
|
59
|
+
sha=$(git rev-parse HEAD)
|
|
60
|
+
echo "sha=$sha" >> "$GITHUB_OUTPUT"
|
|
61
|
+
if [ "$CHANNEL" = nightly ]; then
|
|
62
|
+
result=$(gh run list --workflow ci.yml --commit "$sha" --event push --limit 1 --json conclusion --jq '.[0].conclusion // "missing"')
|
|
63
|
+
test "$result" = success || { echo "::error::CI for $sha is $result"; exit 1; }
|
|
64
|
+
fi
|
|
65
|
+
- name: One version for every platform
|
|
66
|
+
id: version
|
|
67
|
+
env:
|
|
68
|
+
CHANNEL: ${{ inputs.channel }}
|
|
69
|
+
run: |
|
|
70
|
+
args=()
|
|
71
|
+
if [ "$CHANNEL" = nightly ]; then args+=(--nightly); fi
|
|
72
|
+
python scripts/release_version.py "${args[@]}" --github-output "$GITHUB_OUTPUT"
|
|
73
|
+
# A tag that disagrees with Cargo.toml is a mis-cut release: stop before anything is built
|
|
74
|
+
- name: The tag matches Cargo.toml
|
|
75
|
+
if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'release'
|
|
76
|
+
env:
|
|
77
|
+
TAG: ${{ github.event.release.tag_name || github.ref_name }}
|
|
78
|
+
EXPECTED: ${{ steps.version.outputs.tag }}
|
|
79
|
+
run: |
|
|
80
|
+
test "$TAG" = "$EXPECTED" || { echo "::error::tag $TAG != Cargo.toml $EXPECTED"; exit 1; }
|
|
81
|
+
|
|
82
|
+
# abi3: one wheel per platform serves every CPython from 3.10 on
|
|
83
|
+
build-wheels:
|
|
84
|
+
name: Wheel for ${{ matrix.target }}
|
|
85
|
+
needs: meta
|
|
86
|
+
runs-on: ${{ matrix.os }}
|
|
87
|
+
strategy:
|
|
88
|
+
fail-fast: false
|
|
89
|
+
matrix:
|
|
90
|
+
include:
|
|
91
|
+
- { os: ubuntu-latest, target: x86_64-unknown-linux-gnu, manylinux: '2_28', native: true }
|
|
92
|
+
- { os: ubuntu-24.04-arm, target: aarch64-unknown-linux-gnu, manylinux: '2_28', native: true }
|
|
93
|
+
- { os: windows-latest, target: x86_64-pc-windows-msvc, manylinux: '', native: true }
|
|
94
|
+
- { os: macos-latest, target: aarch64-apple-darwin, manylinux: '', native: true }
|
|
95
|
+
# Intel macs: cross-built on the arm runner (GitHub retired its Intel macOS images)
|
|
96
|
+
- { os: macos-latest, target: x86_64-apple-darwin, manylinux: '', native: false }
|
|
97
|
+
steps:
|
|
98
|
+
- uses: actions/checkout@v4
|
|
99
|
+
with:
|
|
100
|
+
ref: ${{ needs.meta.outputs.sha }}
|
|
101
|
+
- uses: actions/setup-python@v5
|
|
102
|
+
with:
|
|
103
|
+
python-version: '3.13'
|
|
104
|
+
- name: Stamp the version
|
|
105
|
+
env:
|
|
106
|
+
ARTIFACT_VERSION: ${{ needs.meta.outputs.cargo_version }}
|
|
107
|
+
run: python scripts/release_version.py --stamp "$ARTIFACT_VERSION"
|
|
108
|
+
- uses: PyO3/maturin-action@v1
|
|
109
|
+
with:
|
|
110
|
+
target: ${{ matrix.target }}
|
|
111
|
+
manylinux: ${{ matrix.manylinux || 'auto' }}
|
|
112
|
+
args: --release --out dist
|
|
113
|
+
- name: Smoke-test the wheel in a clean venv
|
|
114
|
+
if: matrix.native
|
|
115
|
+
env:
|
|
116
|
+
EXPECTED_VERSION: ${{ needs.meta.outputs.python_version }}
|
|
117
|
+
run: |
|
|
118
|
+
python -m venv .venv-wheel
|
|
119
|
+
if [ "${RUNNER_OS}" = "Windows" ]; then . .venv-wheel/Scripts/activate; else . .venv-wheel/bin/activate; fi
|
|
120
|
+
pip install dist/*.whl
|
|
121
|
+
# away from src/, so the import is the installed wheel
|
|
122
|
+
cd dist
|
|
123
|
+
python -c "import importlib.metadata as m, os; assert m.version('kohakutui') == os.environ['EXPECTED_VERSION']"
|
|
124
|
+
python -c "import kohakutui; print(kohakutui.version(), kohakutui.PROTOCOL)"
|
|
125
|
+
# a whole demo turn on the emulated terminal: the engine, not just the import
|
|
126
|
+
python -c "
|
|
127
|
+
from kohakutui import App
|
|
128
|
+
from kohakutui.demo.agent import agent
|
|
129
|
+
app = App(backend='test', size=(90, 30), interrupt='event')
|
|
130
|
+
agent(speed=0.0, interactive=False, app=app)
|
|
131
|
+
assert any(l.startswith('● Done.') for l in app.history()), app.history()
|
|
132
|
+
print('demo ok')
|
|
133
|
+
"
|
|
134
|
+
- uses: actions/upload-artifact@v4
|
|
135
|
+
with:
|
|
136
|
+
name: wheels-${{ matrix.target }}
|
|
137
|
+
path: dist/*.whl
|
|
138
|
+
if-no-files-found: error
|
|
139
|
+
retention-days: 7
|
|
140
|
+
|
|
141
|
+
build-sdist:
|
|
142
|
+
name: Build the sdist
|
|
143
|
+
needs: meta
|
|
144
|
+
runs-on: ubuntu-latest
|
|
145
|
+
steps:
|
|
146
|
+
- uses: actions/checkout@v4
|
|
147
|
+
with:
|
|
148
|
+
ref: ${{ needs.meta.outputs.sha }}
|
|
149
|
+
- uses: actions/setup-python@v5
|
|
150
|
+
with:
|
|
151
|
+
python-version: '3.13'
|
|
152
|
+
- name: Stamp the version
|
|
153
|
+
env:
|
|
154
|
+
ARTIFACT_VERSION: ${{ needs.meta.outputs.cargo_version }}
|
|
155
|
+
run: python scripts/release_version.py --stamp "$ARTIFACT_VERSION"
|
|
156
|
+
- name: Build
|
|
157
|
+
run: |
|
|
158
|
+
pip install maturin twine
|
|
159
|
+
maturin sdist --out dist
|
|
160
|
+
twine check dist/*
|
|
161
|
+
- uses: actions/upload-artifact@v4
|
|
162
|
+
with:
|
|
163
|
+
name: sdist
|
|
164
|
+
path: dist/*.tar.gz
|
|
165
|
+
if-no-files-found: error
|
|
166
|
+
retention-days: 7
|
|
167
|
+
|
|
168
|
+
publish-to-pypi:
|
|
169
|
+
name: Publish to PyPI
|
|
170
|
+
needs: [meta, build-wheels, build-sdist]
|
|
171
|
+
runs-on: ubuntu-latest
|
|
172
|
+
if: needs.meta.outputs.nightly == 'true' || startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'release' && startsWith(github.event.release.tag_name, 'v'))
|
|
173
|
+
environment:
|
|
174
|
+
name: pypi
|
|
175
|
+
url: https://pypi.org/p/kohakutui
|
|
176
|
+
permissions:
|
|
177
|
+
contents: write
|
|
178
|
+
id-token: write
|
|
179
|
+
steps:
|
|
180
|
+
- uses: actions/download-artifact@v4
|
|
181
|
+
with:
|
|
182
|
+
path: dist
|
|
183
|
+
merge-multiple: true
|
|
184
|
+
- name: List artifacts
|
|
185
|
+
run: ls -lh dist/
|
|
186
|
+
|
|
187
|
+
- name: Nightly notes and checksums
|
|
188
|
+
if: needs.meta.outputs.nightly == 'true'
|
|
189
|
+
env:
|
|
190
|
+
SOURCE_SHA: ${{ needs.meta.outputs.sha }}
|
|
191
|
+
ARTIFACT_VERSION: ${{ needs.meta.outputs.python_version }}
|
|
192
|
+
run: |
|
|
193
|
+
printf 'Automatic nightly build (not a stable release).\n\nVersion: %s\n\nSource commit: %s\n' "$ARTIFACT_VERSION" "$SOURCE_SHA" > nightly-notes.md
|
|
194
|
+
sha256sum dist/* > dist/SHA256SUMS.txt
|
|
195
|
+
- name: Nightly as a dated prerelease
|
|
196
|
+
if: needs.meta.outputs.nightly == 'true'
|
|
197
|
+
uses: softprops/action-gh-release@v2
|
|
198
|
+
with:
|
|
199
|
+
tag_name: ${{ needs.meta.outputs.tag }}
|
|
200
|
+
target_commitish: ${{ needs.meta.outputs.sha }}
|
|
201
|
+
name: Nightly ${{ needs.meta.outputs.python_version }}
|
|
202
|
+
prerelease: true
|
|
203
|
+
make_latest: false
|
|
204
|
+
body_path: nightly-notes.md
|
|
205
|
+
files: dist/*
|
|
206
|
+
- name: Keep the checksum file off PyPI
|
|
207
|
+
if: needs.meta.outputs.nightly == 'true'
|
|
208
|
+
run: rm dist/SHA256SUMS.txt
|
|
209
|
+
|
|
210
|
+
- name: Upload to the GitHub release
|
|
211
|
+
if: startsWith(github.ref, 'refs/tags/')
|
|
212
|
+
uses: softprops/action-gh-release@v2
|
|
213
|
+
with:
|
|
214
|
+
files: dist/*
|
|
215
|
+
fail_on_unmatched_files: false
|
|
216
|
+
- name: Publish to PyPI
|
|
217
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
218
|
+
with:
|
|
219
|
+
skip-existing: true
|
|
220
|
+
verbose: true
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Local working notes
|
|
2
|
+
/plans/
|
|
3
|
+
.claude/
|
|
4
|
+
temp/
|
|
5
|
+
|
|
6
|
+
# Rust
|
|
7
|
+
/target/
|
|
8
|
+
*.pdb
|
|
9
|
+
|
|
10
|
+
# Built extension (maturin develop / editable installs)
|
|
11
|
+
*.so
|
|
12
|
+
*.pyd
|
|
13
|
+
*.dylib
|
|
14
|
+
|
|
15
|
+
# Python
|
|
16
|
+
__pycache__/
|
|
17
|
+
*.py[codz]
|
|
18
|
+
*$py.class
|
|
19
|
+
build/
|
|
20
|
+
dist/
|
|
21
|
+
wheels/
|
|
22
|
+
*.egg-info/
|
|
23
|
+
.eggs/
|
|
24
|
+
.venv/
|
|
25
|
+
.venv-*/
|
|
26
|
+
venv/
|
|
27
|
+
|
|
28
|
+
# Tests and tooling
|
|
29
|
+
.pytest_cache/
|
|
30
|
+
.ruff_cache/
|
|
31
|
+
.coverage
|
|
32
|
+
.coverage.*
|
|
33
|
+
htmlcov/
|
|
34
|
+
coverage.xml
|
|
35
|
+
*.snap.new
|
|
36
|
+
*.pending-snap
|
|
37
|
+
|
|
38
|
+
# Editors
|
|
39
|
+
.vscode/
|
|
40
|
+
.idea/
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# KohakuTUI
|
|
2
|
+
|
|
3
|
+
A terminal UI engine in Rust, driven from Python through a retained node-tree protocol. Python
|
|
4
|
+
describes what is on screen; Rust owns the terminal, the frame loop and every keystroke.
|
|
5
|
+
|
|
6
|
+
The architecture is `plans/design.md` (local, gitignored, like all of `plans/`). Section references
|
|
7
|
+
such as "design §5" in code and READMEs point into it. Read §1–§5 before changing anything in
|
|
8
|
+
`proto/` or `model/`.
|
|
9
|
+
|
|
10
|
+
## Project layout
|
|
11
|
+
|
|
12
|
+
```text
|
|
13
|
+
src/kohakutui-rust/ engine crate; lib.rs holds the `_rs` pymodule (feature `python`)
|
|
14
|
+
src/kohakutui/ Python SDK over kohakutui._rs
|
|
15
|
+
tests/ static/, e2e/, integration/, unit/ (Python); Rust tests in <module>/tests.rs
|
|
16
|
+
docs/ public docs (protocol, sdk), added as they are written
|
|
17
|
+
plans/ local design and working plans (gitignored)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Development setup
|
|
21
|
+
|
|
22
|
+
- `uv venv`, then `uv pip install -e ".[dev]"`. maturin builds the extension into the venv;
|
|
23
|
+
rerun after any Rust change.
|
|
24
|
+
- **Never use `sys.path.insert` hacks** in examples or tests; import `kohakutui.*` from the
|
|
25
|
+
installed package.
|
|
26
|
+
|
|
27
|
+
## Rust rules
|
|
28
|
+
|
|
29
|
+
1. **Every module is a directory whose only entry point is `mod.rs`**: submodule declarations,
|
|
30
|
+
the module's public types and re-exports. Logic goes in sibling files. Never `name.rs` beside
|
|
31
|
+
`name/` (`tests/static/test_layout.py` enforces this).
|
|
32
|
+
2. Unit tests live in `<module>/tests.rs` (`#[cfg(test)] mod tests;` in `mod.rs`).
|
|
33
|
+
3. pyo3 code lives in `<module>/python.rs` behind `#[cfg(feature = "python")]`, exposes
|
|
34
|
+
`register(m)`, and is registered from `lib.rs`. Pure Rust never imports pyo3, so
|
|
35
|
+
`cargo test --no-default-features` links no Python.
|
|
36
|
+
4. pyclasses are underscore-prefixed (`_Session`) and wrapped by a public Python class.
|
|
37
|
+
5. Errors: one `thiserror` enum per module, with `From<Error> for PyErr` in its `python.rs`.
|
|
38
|
+
The engine never panics on input from Python or the wire; a bad op becomes `Event::Error`.
|
|
39
|
+
6. `//!` docs at the top of every file say what the module is for. `pub(crate)` unless the
|
|
40
|
+
item is part of the crate API.
|
|
41
|
+
7. `cargo fmt` (max width 100) and `cargo clippy --all-targets -- -D warnings`, with and without
|
|
42
|
+
`--features python`.
|
|
43
|
+
8. Code adapted from Codex (Apache-2.0) or ratatui (MIT) keeps its license header.
|
|
44
|
+
|
|
45
|
+
## Python rules
|
|
46
|
+
|
|
47
|
+
### Style
|
|
48
|
+
- Target Python 3.10+ (abi3 wheel). Modern hints: `list`, `dict`, `X | None`.
|
|
49
|
+
- Prefer `match`/`case` over deep `if`/`elif`.
|
|
50
|
+
- No `print()` in library code. CLI entry points (`__main__.py`) may print.
|
|
51
|
+
- black (default width) and `ruff check` over `src/` and `tests/`.
|
|
52
|
+
|
|
53
|
+
### Imports
|
|
54
|
+
1. No imports inside functions (except an optional dependency, such as `tqdm`).
|
|
55
|
+
`tests/static/test_layout.py` enforces this.
|
|
56
|
+
2. Groups: built-in, third-party, `kohakutui`.
|
|
57
|
+
3. Within a group: `import` before `from`, shorter dotted paths first, then a–z.
|
|
58
|
+
|
|
59
|
+
### Files
|
|
60
|
+
- One responsibility per module. Every package folder has a `README.md` with a file table.
|
|
61
|
+
- File size is capped by AST statements (imports and docstrings excluded): over 500 warns,
|
|
62
|
+
over 1000 fails (`tests/static/test_file_sizes.py`).
|
|
63
|
+
|
|
64
|
+
## Comments and docstrings
|
|
65
|
+
|
|
66
|
+
Comment, code and docstring all say **what**. *How* and *why* live in `docs/` and `plans/`.
|
|
67
|
+
|
|
68
|
+
1. No memo, note or editorial comments.
|
|
69
|
+
2. A comment says what the code itself cannot say clearly.
|
|
70
|
+
3. A docstring says what the class or function is *for*, so a reader can place it.
|
|
71
|
+
4. Soft limits: inline comment blocks ≤ 2 lines, docstrings ≤ 14 lines.
|
|
72
|
+
5. Doc comments (`//!`, `///`, docstrings) say what a file, type or function is for. An inline
|
|
73
|
+
comment (1–2 lines) heads a logic block only when the code cannot say what it does (a tricky
|
|
74
|
+
API, a non-obvious algorithm step).
|
|
75
|
+
6. Separate logic blocks inside a function with one blank line.
|
|
76
|
+
7. Formatting is the formatter's: rustfmt for Rust, black for Python (black already wraps long
|
|
77
|
+
expressions in parentheses).
|
|
78
|
+
|
|
79
|
+
## Tests
|
|
80
|
+
|
|
81
|
+
The engine is a pipeline of stages (proto → model → text → widgets/view → term, then the SDK on
|
|
82
|
+
top). **A test proves one stage on the level below it and nothing further.** `tests/README.md`
|
|
83
|
+
has the stage table.
|
|
84
|
+
|
|
85
|
+
- Write tiers **e2e first, then integration, then unit**: the journey fixes what must work, the
|
|
86
|
+
lower tiers pin the parts.
|
|
87
|
+
- `static/`: layout and contract guards (module layout, imports, file sizes, the `_rs.pyi` stub).
|
|
88
|
+
- Behavior asserts, not shape asserts; real collaborators, not mocks. The only seam is the
|
|
89
|
+
terminal: `_Session(backend="test")`.
|
|
90
|
+
- A check that cannot fail on a broken stage is not a check.
|
|
91
|
+
|
|
92
|
+
## Post-implementation
|
|
93
|
+
|
|
94
|
+
1. Check the rules above, especially in-function imports and the `mod.rs` layout.
|
|
95
|
+
2. `black src/ tests/`, `ruff check src/ tests/`, `cargo fmt`, both clippy runs.
|
|
96
|
+
3. `pytest tests/ -q` and `cargo test --no-default-features`.
|
|
97
|
+
4. New behaviour has tests at the right stage and tier.
|
|
98
|
+
5. Logically separate commits (conventional style: `feat(model): ...`). **Do not commit unless
|
|
99
|
+
asked.**
|
|
100
|
+
|
|
101
|
+
## Audit loop (multi-step work: required)
|
|
102
|
+
|
|
103
|
+
Implement → write tests that pin the behaviour (negative cases first) → run every suite and lint
|
|
104
|
+
→ audit the diff for clear bugs, integrity bugs (broken invariants, races, stale caches) and
|
|
105
|
+
behaviour bugs (wrong default, swallowed error) → if the audit finds a bug the tests missed,
|
|
106
|
+
first make a test fail on it, then fix → repeat until the audit is clean and everything is green.
|