ai-browser-toolkit 0.1.2__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.
- ai_browser_toolkit-0.1.2/.claude/settings.local.json +8 -0
- ai_browser_toolkit-0.1.2/.gitattributes +9 -0
- ai_browser_toolkit-0.1.2/.github/workflows/check-secrets.yml +51 -0
- ai_browser_toolkit-0.1.2/.github/workflows/ci.yml +54 -0
- ai_browser_toolkit-0.1.2/.github/workflows/release.yml +448 -0
- ai_browser_toolkit-0.1.2/.gitignore +31 -0
- ai_browser_toolkit-0.1.2/.sheet-url +1 -0
- ai_browser_toolkit-0.1.2/AGENTS.md +179 -0
- ai_browser_toolkit-0.1.2/CLAUDE.md +14 -0
- ai_browser_toolkit-0.1.2/GEMINI.md +14 -0
- ai_browser_toolkit-0.1.2/LICENSE +201 -0
- ai_browser_toolkit-0.1.2/PKG-INFO +940 -0
- ai_browser_toolkit-0.1.2/README.md +921 -0
- ai_browser_toolkit-0.1.2/docs/TODO.md +18 -0
- ai_browser_toolkit-0.1.2/docs/engine-choice-for-profile-sessions.md +168 -0
- ai_browser_toolkit-0.1.2/docs/known-issues.md +395 -0
- ai_browser_toolkit-0.1.2/docs/packaging.md +104 -0
- ai_browser_toolkit-0.1.2/docs/playwright-spike-2026-08-19.md +205 -0
- ai_browser_toolkit-0.1.2/docs/session-review-20260806-linkedin.md +389 -0
- ai_browser_toolkit-0.1.2/docs/superpowers/plans/2026-08-19-browser-server-decoupling.md +2089 -0
- ai_browser_toolkit-0.1.2/docs/superpowers/plans/2026-08-20-packaging-and-distribution.md +2538 -0
- ai_browser_toolkit-0.1.2/docs/superpowers/specs/2026-08-03-selenium-browser-api-design.md +302 -0
- ai_browser_toolkit-0.1.2/docs/superpowers/specs/2026-08-19-browser-server-decoupling-design.md +459 -0
- ai_browser_toolkit-0.1.2/docs/superpowers/specs/2026-08-19-profile-sessions-design.md +350 -0
- ai_browser_toolkit-0.1.2/docs/superpowers/specs/2026-08-20-packaging-and-distribution-design.md +489 -0
- ai_browser_toolkit-0.1.2/guidelines/README.md +55 -0
- ai_browser_toolkit-0.1.2/guidelines/fojik-mlwbd.md +147 -0
- ai_browser_toolkit-0.1.2/guidelines/google-docs.md +101 -0
- ai_browser_toolkit-0.1.2/guidelines/google-forms-apps-script.md +210 -0
- ai_browser_toolkit-0.1.2/guidelines/google-sheets.md +163 -0
- ai_browser_toolkit-0.1.2/guidelines/kayoanime.md +138 -0
- ai_browser_toolkit-0.1.2/guidelines/messenger.md +222 -0
- ai_browser_toolkit-0.1.2/guidelines/toolkit-workflow.md +433 -0
- ai_browser_toolkit-0.1.2/packaging/aur/PKGBUILD.template +32 -0
- ai_browser_toolkit-0.1.2/packaging/aur/aibrowsertoolkit-bin.install +22 -0
- ai_browser_toolkit-0.1.2/packaging/bundle.py +228 -0
- ai_browser_toolkit-0.1.2/packaging/homebrew/formula.rb.template +43 -0
- ai_browser_toolkit-0.1.2/packaging/nfpm.yaml +28 -0
- ai_browser_toolkit-0.1.2/packaging/postinstall.sh +14 -0
- ai_browser_toolkit-0.1.2/packaging/preremove.sh +4 -0
- ai_browser_toolkit-0.1.2/packaging/scoop/manifest.json.template +31 -0
- ai_browser_toolkit-0.1.2/packaging/smoke.sh +54 -0
- ai_browser_toolkit-0.1.2/packaging/windows/abt.iss +124 -0
- ai_browser_toolkit-0.1.2/pyproject.toml +39 -0
- ai_browser_toolkit-0.1.2/src/abt/__init__.py +3 -0
- ai_browser_toolkit-0.1.2/src/abt/__main__.py +10 -0
- ai_browser_toolkit-0.1.2/src/abt/autostart.py +405 -0
- ai_browser_toolkit-0.1.2/src/abt/browser.py +851 -0
- ai_browser_toolkit-0.1.2/src/abt/cli.py +828 -0
- ai_browser_toolkit-0.1.2/src/abt/diff.py +652 -0
- ai_browser_toolkit-0.1.2/src/abt/doctor.py +220 -0
- ai_browser_toolkit-0.1.2/src/abt/engine.py +241 -0
- ai_browser_toolkit-0.1.2/src/abt/errors.py +34 -0
- ai_browser_toolkit-0.1.2/src/abt/frames.py +134 -0
- ai_browser_toolkit-0.1.2/src/abt/launch.py +64 -0
- ai_browser_toolkit-0.1.2/src/abt/mcp.py +438 -0
- ai_browser_toolkit-0.1.2/src/abt/messenger.py +782 -0
- ai_browser_toolkit-0.1.2/src/abt/ops/__init__.py +306 -0
- ai_browser_toolkit-0.1.2/src/abt/ops/control.py +165 -0
- ai_browser_toolkit-0.1.2/src/abt/ops/inspect.py +228 -0
- ai_browser_toolkit-0.1.2/src/abt/ops/interact.py +665 -0
- ai_browser_toolkit-0.1.2/src/abt/ops/navigate.py +41 -0
- ai_browser_toolkit-0.1.2/src/abt/ops/read.py +124 -0
- ai_browser_toolkit-0.1.2/src/abt/ops/tabs.py +24 -0
- ai_browser_toolkit-0.1.2/src/abt/paths.py +131 -0
- ai_browser_toolkit-0.1.2/src/abt/proc.py +208 -0
- ai_browser_toolkit-0.1.2/src/abt/pwdriver.py +1277 -0
- ai_browser_toolkit-0.1.2/src/abt/recorder.py +235 -0
- ai_browser_toolkit-0.1.2/src/abt/refs.py +91 -0
- ai_browser_toolkit-0.1.2/src/abt/schema.py +450 -0
- ai_browser_toolkit-0.1.2/src/abt/server.py +457 -0
- ai_browser_toolkit-0.1.2/src/abt/shadow.py +127 -0
- ai_browser_toolkit-0.1.2/src/abt/shots.py +148 -0
- ai_browser_toolkit-0.1.2/src/abt/targeting.py +359 -0
- ai_browser_toolkit-0.1.2/src/abt/viewer.py +453 -0
- ai_browser_toolkit-0.1.2/start-server.bat +155 -0
- ai_browser_toolkit-0.1.2/start-server.sh +178 -0
- ai_browser_toolkit-0.1.2/tests/conftest.py +202 -0
- ai_browser_toolkit-0.1.2/tests/fixtures/actionable.html +72 -0
- ai_browser_toolkit-0.1.2/tests/fixtures/canvas.html +42 -0
- ai_browser_toolkit-0.1.2/tests/fixtures/cards.html +17 -0
- ai_browser_toolkit-0.1.2/tests/fixtures/chainfetch.html +33 -0
- ai_browser_toolkit-0.1.2/tests/fixtures/controlled.html +41 -0
- ai_browser_toolkit-0.1.2/tests/fixtures/controlled_trusted.html +35 -0
- ai_browser_toolkit-0.1.2/tests/fixtures/delayed.html +13 -0
- ai_browser_toolkit-0.1.2/tests/fixtures/form.html +24 -0
- ai_browser_toolkit-0.1.2/tests/fixtures/framed_button.html +13 -0
- ai_browser_toolkit-0.1.2/tests/fixtures/framed_preload.html +11 -0
- ai_browser_toolkit-0.1.2/tests/fixtures/frames.html +19 -0
- ai_browser_toolkit-0.1.2/tests/fixtures/frames_cross.html +25 -0
- ai_browser_toolkit-0.1.2/tests/fixtures/frames_order.html +50 -0
- ai_browser_toolkit-0.1.2/tests/fixtures/frames_swap.html +29 -0
- ai_browser_toolkit-0.1.2/tests/fixtures/inspect.html +48 -0
- ai_browser_toolkit-0.1.2/tests/fixtures/late.html +25 -0
- ai_browser_toolkit-0.1.2/tests/fixtures/links.html +6 -0
- ai_browser_toolkit-0.1.2/tests/fixtures/messenger.html +130 -0
- ai_browser_toolkit-0.1.2/tests/fixtures/nav.html +24 -0
- ai_browser_toolkit-0.1.2/tests/fixtures/overlay.html +40 -0
- ai_browser_toolkit-0.1.2/tests/fixtures/repeated.html +41 -0
- ai_browser_toolkit-0.1.2/tests/fixtures/shadow.html +45 -0
- ai_browser_toolkit-0.1.2/tests/fixtures/slowfetch.html +28 -0
- ai_browser_toolkit-0.1.2/tests/test_actionable.py +225 -0
- ai_browser_toolkit-0.1.2/tests/test_autostart.py +166 -0
- ai_browser_toolkit-0.1.2/tests/test_bundle.py +110 -0
- ai_browser_toolkit-0.1.2/tests/test_click_at.py +109 -0
- ai_browser_toolkit-0.1.2/tests/test_diff.py +448 -0
- ai_browser_toolkit-0.1.2/tests/test_doctor.py +147 -0
- ai_browser_toolkit-0.1.2/tests/test_engine.py +147 -0
- ai_browser_toolkit-0.1.2/tests/test_frames.py +316 -0
- ai_browser_toolkit-0.1.2/tests/test_hit_test.py +112 -0
- ai_browser_toolkit-0.1.2/tests/test_inspect.py +389 -0
- ai_browser_toolkit-0.1.2/tests/test_launch.py +120 -0
- ai_browser_toolkit-0.1.2/tests/test_lifecycle.py +414 -0
- ai_browser_toolkit-0.1.2/tests/test_lifecycle_live.py +58 -0
- ai_browser_toolkit-0.1.2/tests/test_mcp.py +271 -0
- ai_browser_toolkit-0.1.2/tests/test_messenger.py +298 -0
- ai_browser_toolkit-0.1.2/tests/test_ops_click_options.py +128 -0
- ai_browser_toolkit-0.1.2/tests/test_ops_interact.py +212 -0
- ai_browser_toolkit-0.1.2/tests/test_ops_navigate.py +118 -0
- ai_browser_toolkit-0.1.2/tests/test_ops_read.py +108 -0
- ai_browser_toolkit-0.1.2/tests/test_ops_tabs.py +75 -0
- ai_browser_toolkit-0.1.2/tests/test_paths.py +132 -0
- ai_browser_toolkit-0.1.2/tests/test_proc.py +77 -0
- ai_browser_toolkit-0.1.2/tests/test_recorder.py +133 -0
- ai_browser_toolkit-0.1.2/tests/test_schema.py +62 -0
- ai_browser_toolkit-0.1.2/tests/test_server.py +131 -0
- ai_browser_toolkit-0.1.2/tests/test_server_logs.py +132 -0
- ai_browser_toolkit-0.1.2/tests/test_settle.py +143 -0
- ai_browser_toolkit-0.1.2/tests/test_shadow.py +211 -0
- ai_browser_toolkit-0.1.2/tests/test_shots.py +148 -0
- ai_browser_toolkit-0.1.2/tests/test_shots_live.py +113 -0
- ai_browser_toolkit-0.1.2/tests/test_targeting.py +101 -0
- ai_browser_toolkit-0.1.2/tests/test_workflows.py +143 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Line endings are load-bearing for the start scripts.
|
|
2
|
+
#
|
|
3
|
+
# This repo is developed on Windows with core.autocrlf=true, which would hand a
|
|
4
|
+
# CRLF start-server.sh to anyone cloning on Linux or macOS -- where bash reads
|
|
5
|
+
# the trailing \r as part of the command and every line fails. Pin each script
|
|
6
|
+
# to the endings its interpreter needs, whatever the cloning machine prefers.
|
|
7
|
+
* text=auto
|
|
8
|
+
*.sh text eol=lf
|
|
9
|
+
*.bat text eol=crlf
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
name: Check secrets
|
|
2
|
+
|
|
3
|
+
# Answers one question definitively: which secrets can a workflow in this
|
|
4
|
+
# repository actually see? An empty secret is not an error in Actions -- it
|
|
5
|
+
# interpolates as nothing, and the tool downstream reports it as bad
|
|
6
|
+
# credentials, which sends you looking in the wrong place. Every publisher
|
|
7
|
+
# failure on v0.1.1 read that way.
|
|
8
|
+
#
|
|
9
|
+
# Prints only set/empty and a length. Never a value, so the log is safe to
|
|
10
|
+
# paste anywhere.
|
|
11
|
+
#
|
|
12
|
+
# Worth knowing: Settings -> Secrets and variables has three separate stores
|
|
13
|
+
# -- Actions, Dependabot and Codespaces. They look identical in the UI and
|
|
14
|
+
# only the Actions one is visible here. Secrets attached to an *environment*
|
|
15
|
+
# are also invisible to any job that does not declare that environment.
|
|
16
|
+
|
|
17
|
+
on:
|
|
18
|
+
workflow_dispatch:
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
check:
|
|
22
|
+
runs-on: ubuntu-24.04
|
|
23
|
+
steps:
|
|
24
|
+
- name: Report which secrets are visible
|
|
25
|
+
env:
|
|
26
|
+
TAP_TOKEN: ${{ secrets.TAP_TOKEN }}
|
|
27
|
+
WINGET_TOKEN: ${{ secrets.WINGET_TOKEN }}
|
|
28
|
+
AUR_SSH_KEY: ${{ secrets.AUR_SSH_KEY }}
|
|
29
|
+
FURY_TOKEN: ${{ secrets.FURY_TOKEN }}
|
|
30
|
+
run: |
|
|
31
|
+
for name in TAP_TOKEN WINGET_TOKEN AUR_SSH_KEY FURY_TOKEN; do
|
|
32
|
+
value="${!name}"
|
|
33
|
+
if [ -z "$value" ]; then
|
|
34
|
+
echo "$name: EMPTY -- not visible to Actions in this repository"
|
|
35
|
+
else
|
|
36
|
+
echo "$name: set, ${#value} characters"
|
|
37
|
+
fi
|
|
38
|
+
done
|
|
39
|
+
|
|
40
|
+
- name: Sanity-check the shapes
|
|
41
|
+
env:
|
|
42
|
+
AUR_SSH_KEY: ${{ secrets.AUR_SSH_KEY }}
|
|
43
|
+
run: |
|
|
44
|
+
# The most common AUR mistake is pasting the .pub half, which fails
|
|
45
|
+
# later with an unhelpful permission error rather than here.
|
|
46
|
+
case "$AUR_SSH_KEY" in
|
|
47
|
+
"") echo "AUR_SSH_KEY: empty, nothing to check" ;;
|
|
48
|
+
ssh-*) echo "AUR_SSH_KEY: looks like a PUBLIC key -- the private half is wanted" ; exit 1 ;;
|
|
49
|
+
*"BEGIN OPENSSH PRIVATE KEY"*) echo "AUR_SSH_KEY: looks like a private key" ;;
|
|
50
|
+
*) echo "AUR_SSH_KEY: set, but not in a shape I recognise" ;;
|
|
51
|
+
esac
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
strategy:
|
|
11
|
+
fail-fast: false
|
|
12
|
+
matrix:
|
|
13
|
+
os: [ubuntu-24.04, windows-latest, macos-14]
|
|
14
|
+
python: ["3.11", "3.13"]
|
|
15
|
+
runs-on: ${{ matrix.os }}
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
# Deliberately not astral-sh/setup-uv here. uv is what the *bundle*
|
|
20
|
+
# build needs -- it knows how to fetch python-build-standalone -- but in
|
|
21
|
+
# a test job it introduces a second interpreter: setup-uv with a
|
|
22
|
+
# python-version creates and activates .venv, while `uv pip install
|
|
23
|
+
# --system` installs somewhere else, and the test step then reports
|
|
24
|
+
# "No module named pytest". One interpreter, named explicitly, end to
|
|
25
|
+
# end.
|
|
26
|
+
- uses: actions/setup-python@v5
|
|
27
|
+
with:
|
|
28
|
+
python-version: ${{ matrix.python }}
|
|
29
|
+
|
|
30
|
+
- name: Install
|
|
31
|
+
run: python -m pip install -e ".[dev]"
|
|
32
|
+
|
|
33
|
+
- name: Show what this machine has
|
|
34
|
+
# Dogfooding the dependency check, and the fastest way to read a
|
|
35
|
+
# browser-related failure below. Never fatal: doctor exits non-zero
|
|
36
|
+
# when no browser is present, which is information, not a broken job.
|
|
37
|
+
run: python -m abt doctor || true
|
|
38
|
+
|
|
39
|
+
- name: Test
|
|
40
|
+
# The live tests need a real browser on a real profile; everything
|
|
41
|
+
# else runs anywhere.
|
|
42
|
+
env:
|
|
43
|
+
# This runner is several times slower than a developer machine, and
|
|
44
|
+
# it blew a different budget each run: first a click timeout
|
|
45
|
+
# ("ElementHandle.click: Timeout 3000ms exceeded", already at
|
|
46
|
+
# "performing click action" -- slow, not stuck), then a settle grace
|
|
47
|
+
# that called a page idle mid-fetch and snapshotted the spinner.
|
|
48
|
+
# One multiplier scales them together rather than chasing each.
|
|
49
|
+
# Not a retry: a retry would hide a genuine hang just as well.
|
|
50
|
+
ABT_TEST_SLOWDOWN: "5"
|
|
51
|
+
run: >
|
|
52
|
+
python -m pytest -q
|
|
53
|
+
--deselect tests/test_lifecycle_live.py
|
|
54
|
+
--deselect tests/test_shots_live.py
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
inputs:
|
|
8
|
+
dry_run:
|
|
9
|
+
description: "Build and smoke-test everything, publish nothing"
|
|
10
|
+
type: boolean
|
|
11
|
+
default: true
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
contents: write
|
|
15
|
+
id-token: write
|
|
16
|
+
attestations: write
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
test:
|
|
20
|
+
runs-on: ubuntu-24.04
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
- uses: actions/setup-python@v5
|
|
24
|
+
with:
|
|
25
|
+
python-version: "3.13"
|
|
26
|
+
- run: python -m pip install -e ".[dev]"
|
|
27
|
+
- env:
|
|
28
|
+
# Same multiplier as ci.yml. A release must not fail on a budget
|
|
29
|
+
# that only a loaded runner blows.
|
|
30
|
+
ABT_TEST_SLOWDOWN: "5"
|
|
31
|
+
run: >
|
|
32
|
+
python -m pytest -q
|
|
33
|
+
--deselect tests/test_lifecycle_live.py
|
|
34
|
+
--deselect tests/test_shots_live.py
|
|
35
|
+
|
|
36
|
+
wheel:
|
|
37
|
+
needs: test
|
|
38
|
+
runs-on: ubuntu-24.04
|
|
39
|
+
outputs:
|
|
40
|
+
version: ${{ steps.v.outputs.version }}
|
|
41
|
+
steps:
|
|
42
|
+
- uses: actions/checkout@v4
|
|
43
|
+
- uses: actions/setup-python@v5
|
|
44
|
+
with:
|
|
45
|
+
python-version: "3.13"
|
|
46
|
+
|
|
47
|
+
- name: Read the version from pyproject
|
|
48
|
+
id: v
|
|
49
|
+
run: |
|
|
50
|
+
version=$(python -c "import tomllib,pathlib;print(tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['version'])")
|
|
51
|
+
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
52
|
+
echo "pyproject says $version"
|
|
53
|
+
|
|
54
|
+
- name: Guard the tag against the version
|
|
55
|
+
if: startsWith(github.ref, 'refs/tags/')
|
|
56
|
+
run: |
|
|
57
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
58
|
+
if [ "$tag" != "${{ steps.v.outputs.version }}" ]; then
|
|
59
|
+
echo "tag $GITHUB_REF_NAME does not match pyproject version ${{ steps.v.outputs.version }}"
|
|
60
|
+
echo "bump pyproject.toml first, commit, then tag -- never the reverse."
|
|
61
|
+
exit 1
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
- name: Build wheel and sdist
|
|
65
|
+
run: |
|
|
66
|
+
python -m pip install build
|
|
67
|
+
python -m build
|
|
68
|
+
|
|
69
|
+
- uses: actions/upload-artifact@v4
|
|
70
|
+
with:
|
|
71
|
+
name: dist
|
|
72
|
+
path: dist/
|
|
73
|
+
|
|
74
|
+
bundle:
|
|
75
|
+
needs: wheel
|
|
76
|
+
strategy:
|
|
77
|
+
fail-fast: false
|
|
78
|
+
matrix:
|
|
79
|
+
include:
|
|
80
|
+
- target: linux-x86_64
|
|
81
|
+
os: ubuntu-24.04
|
|
82
|
+
- target: linux-aarch64
|
|
83
|
+
os: ubuntu-24.04-arm
|
|
84
|
+
- target: macos-arm64
|
|
85
|
+
os: macos-14
|
|
86
|
+
# macos-x86_64 is deliberately absent. macos-13 is GitHub's last
|
|
87
|
+
# Intel image and is being retired, so it queues badly and held up
|
|
88
|
+
# every release behind it. bundle.py still knows the target, so an
|
|
89
|
+
# Intel Mac can build one locally; nothing ships it automatically.
|
|
90
|
+
# The Homebrew formula is arm64-only as a result -- see
|
|
91
|
+
# docs/packaging.md.
|
|
92
|
+
- target: windows-x86_64
|
|
93
|
+
os: windows-latest
|
|
94
|
+
runs-on: ${{ matrix.os }}
|
|
95
|
+
steps:
|
|
96
|
+
- uses: actions/checkout@v4
|
|
97
|
+
|
|
98
|
+
# setup-python runs bundle.py; setup-uv supplies the `uv` binary the
|
|
99
|
+
# builder shells out to. Deliberately kept apart: setup-uv given a
|
|
100
|
+
# python-version creates and activates its own .venv, and then two
|
|
101
|
+
# interpreters disagree about what is installed.
|
|
102
|
+
- uses: actions/setup-python@v5
|
|
103
|
+
with:
|
|
104
|
+
python-version: "3.13"
|
|
105
|
+
- uses: astral-sh/setup-uv@v5
|
|
106
|
+
|
|
107
|
+
- uses: actions/download-artifact@v4
|
|
108
|
+
with:
|
|
109
|
+
name: dist
|
|
110
|
+
path: dist
|
|
111
|
+
|
|
112
|
+
- name: Build the bundle
|
|
113
|
+
shell: bash
|
|
114
|
+
run: |
|
|
115
|
+
wheel=$(ls dist/*.whl)
|
|
116
|
+
python packaging/bundle.py \
|
|
117
|
+
--version "${{ needs.wheel.outputs.version }}" \
|
|
118
|
+
--target "${{ matrix.target }}" \
|
|
119
|
+
--wheel "$wheel" \
|
|
120
|
+
--dest dist/
|
|
121
|
+
|
|
122
|
+
- name: Smoke test
|
|
123
|
+
shell: bash
|
|
124
|
+
run: |
|
|
125
|
+
bash packaging/smoke.sh \
|
|
126
|
+
"dist/aibrowsertoolkit-${{ needs.wheel.outputs.version }}-${{ matrix.target }}"
|
|
127
|
+
|
|
128
|
+
# Named exactly, not globbed. `python -m build` puts an sdist .tar.gz in
|
|
129
|
+
# dist/ too, and this job downloads it -- so `dist/*.tar.gz` would sweep
|
|
130
|
+
# the sdist into all four Unix bundle artifacts, and the release job
|
|
131
|
+
# would then publish it five times over.
|
|
132
|
+
- uses: actions/upload-artifact@v4
|
|
133
|
+
with:
|
|
134
|
+
name: bundle-${{ matrix.target }}
|
|
135
|
+
if-no-files-found: error
|
|
136
|
+
path: |
|
|
137
|
+
dist/aibrowsertoolkit-${{ needs.wheel.outputs.version }}-${{ matrix.target }}.tar.gz
|
|
138
|
+
dist/aibrowsertoolkit-${{ needs.wheel.outputs.version }}-${{ matrix.target }}.zip
|
|
139
|
+
|
|
140
|
+
# Built here rather than in a job of its own. GitHub Actions cannot
|
|
141
|
+
# depend on a single matrix cell, so `needs: bundle` waits for all five
|
|
142
|
+
# -- which left the Windows installer blocked behind the macOS builds
|
|
143
|
+
# despite consuming only the Windows tree. Inside the cell it is ready
|
|
144
|
+
# the moment Windows is.
|
|
145
|
+
#
|
|
146
|
+
# No unzip step either: bundle.py leaves the unpacked tree beside the
|
|
147
|
+
# archive, so Inno points straight at it.
|
|
148
|
+
- name: Compile the installer
|
|
149
|
+
if: matrix.target == 'windows-x86_64'
|
|
150
|
+
shell: bash
|
|
151
|
+
env:
|
|
152
|
+
# `shell: bash` on a Windows runner is Git Bash, which rewrites any
|
|
153
|
+
# argument beginning with `/` into a Windows path. That turns
|
|
154
|
+
# ISCC's own switches -- /DAppVersion, /DPayloadDir, /O -- into
|
|
155
|
+
# filenames, and it fails with the wonderfully misleading "You may
|
|
156
|
+
# not specify more than one script filename". Found by compiling
|
|
157
|
+
# locally in Git Bash, which reproduces the runner exactly.
|
|
158
|
+
MSYS2_ARG_CONV_EXCL: "*"
|
|
159
|
+
run: |
|
|
160
|
+
# Forward slashes throughout: Win32 accepts them, and cmd-style
|
|
161
|
+
# backslashes in YAML are a quoting trap for no benefit.
|
|
162
|
+
iscc="/c/Program Files (x86)/Inno Setup 6/ISCC.exe"
|
|
163
|
+
if [ ! -x "$iscc" ]; then iscc=$(command -v iscc || true); fi
|
|
164
|
+
if [ -z "$iscc" ]; then echo "Inno Setup not found on this runner"; exit 1; fi
|
|
165
|
+
root=$(pwd -W)
|
|
166
|
+
tree="$root/dist/aibrowsertoolkit-${{ needs.wheel.outputs.version }}-windows-x86_64"
|
|
167
|
+
"$iscc" "/DAppVersion=${{ needs.wheel.outputs.version }}" "/DPayloadDir=$tree" "/O$root/out" packaging/windows/abt.iss
|
|
168
|
+
|
|
169
|
+
- name: Upload the installer
|
|
170
|
+
if: matrix.target == 'windows-x86_64'
|
|
171
|
+
uses: actions/upload-artifact@v4
|
|
172
|
+
with:
|
|
173
|
+
name: installer
|
|
174
|
+
if-no-files-found: error
|
|
175
|
+
path: out/*.exe
|
|
176
|
+
|
|
177
|
+
release:
|
|
178
|
+
needs: [wheel, bundle]
|
|
179
|
+
# Both halves matter. The ref check keeps a branch dispatch from
|
|
180
|
+
# publishing; the dry_run check keeps a *tag* dispatch from publishing
|
|
181
|
+
# when the person running it asked for a rehearsal. Without the second,
|
|
182
|
+
# the input would be decorative and the surprise would be a real release.
|
|
183
|
+
if: startsWith(github.ref, 'refs/tags/') && github.event.inputs.dry_run != 'true'
|
|
184
|
+
runs-on: ubuntu-24.04
|
|
185
|
+
steps:
|
|
186
|
+
- uses: actions/download-artifact@v4
|
|
187
|
+
with:
|
|
188
|
+
path: staging
|
|
189
|
+
|
|
190
|
+
- name: Collect every artifact into one directory
|
|
191
|
+
run: |
|
|
192
|
+
set -eu
|
|
193
|
+
mkdir -p out
|
|
194
|
+
find staging -type f \( -name '*.tar.gz' -o -name '*.zip' \
|
|
195
|
+
-o -name '*.whl' -o -name '*.exe' \) -exec cp -n {} out/ \;
|
|
196
|
+
ls -l out
|
|
197
|
+
# Four bundles + wheel + sdist + installer. A missing one means a job
|
|
198
|
+
# was skipped, and shipping a partial release is worse than failing
|
|
199
|
+
# here -- the publishers downstream would each pin a URL that never
|
|
200
|
+
# gets an asset.
|
|
201
|
+
count=$(ls out | wc -l)
|
|
202
|
+
if [ "$count" -lt 7 ]; then
|
|
203
|
+
echo "expected at least 7 artifacts, found $count"
|
|
204
|
+
exit 1
|
|
205
|
+
fi
|
|
206
|
+
|
|
207
|
+
- name: Checksums
|
|
208
|
+
# Generated before the attestation so checksums.txt is itself attested.
|
|
209
|
+
run: cd out && sha256sum * > checksums.txt && cat checksums.txt
|
|
210
|
+
|
|
211
|
+
- uses: actions/attest-build-provenance@v2
|
|
212
|
+
with:
|
|
213
|
+
subject-path: "out/*"
|
|
214
|
+
|
|
215
|
+
- uses: softprops/action-gh-release@v2
|
|
216
|
+
with:
|
|
217
|
+
files: out/*
|
|
218
|
+
generate_release_notes: true
|
|
219
|
+
prerelease: ${{ contains(github.ref_name, '-rc') || contains(github.ref_name, '-beta') }}
|
|
220
|
+
|
|
221
|
+
pypi:
|
|
222
|
+
needs: release
|
|
223
|
+
if: startsWith(github.ref, 'refs/tags/') && github.event.inputs.dry_run != 'true'
|
|
224
|
+
runs-on: ubuntu-24.04
|
|
225
|
+
# Trusted Publishing: no API token is stored anywhere. The environment
|
|
226
|
+
# name and workflow filename below must match the pending publisher
|
|
227
|
+
# registered on PyPI exactly, or this fails with "not a trusted
|
|
228
|
+
# publisher" -- and that can only be found by releasing, which is what
|
|
229
|
+
# the dry_run input exists to avoid doing twice.
|
|
230
|
+
environment: pypi
|
|
231
|
+
permissions:
|
|
232
|
+
id-token: write
|
|
233
|
+
steps:
|
|
234
|
+
- uses: actions/download-artifact@v4
|
|
235
|
+
with:
|
|
236
|
+
name: dist
|
|
237
|
+
path: dist
|
|
238
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
239
|
+
|
|
240
|
+
# ---------------------------------------------------------------------
|
|
241
|
+
# Publishers. Independent by design: each reads the finished release, so
|
|
242
|
+
# one failing channel leaves the other five shipped and the fix is a
|
|
243
|
+
# re-run of a single job rather than of the release.
|
|
244
|
+
# ---------------------------------------------------------------------
|
|
245
|
+
|
|
246
|
+
winget:
|
|
247
|
+
needs: [wheel, release]
|
|
248
|
+
# Release candidates are excluded: the winget community repository does
|
|
249
|
+
# not want them.
|
|
250
|
+
if: startsWith(github.ref, 'refs/tags/') && !contains(github.ref_name, '-rc')
|
|
251
|
+
runs-on: ubuntu-24.04
|
|
252
|
+
# winget-releaser only *updates* an identifier that already exists in
|
|
253
|
+
# microsoft/winget-pkgs; it cannot create one. Until the first version is
|
|
254
|
+
# submitted by hand with `wingetcreate new`, this fails with "Package
|
|
255
|
+
# skssmd.AIBrowserToolkit does not exist in the winget-pkgs repository".
|
|
256
|
+
#
|
|
257
|
+
# Non-fatal so one manual prerequisite does not colour every release red.
|
|
258
|
+
# REMOVE THIS LINE once the package exists upstream -- leaving it there
|
|
259
|
+
# would hide a genuine winget failure later.
|
|
260
|
+
continue-on-error: true
|
|
261
|
+
steps:
|
|
262
|
+
# Reads the finished release and derives the three manifests from the
|
|
263
|
+
# installer asset. WINGET_TOKEN must be a *classic* PAT with
|
|
264
|
+
# public_repo -- this action does not work with fine-grained tokens.
|
|
265
|
+
- uses: vedantmgoyal9/winget-releaser@main
|
|
266
|
+
with:
|
|
267
|
+
identifier: skssmd.AIBrowserToolkit
|
|
268
|
+
version: ${{ needs.wheel.outputs.version }}
|
|
269
|
+
installers-regex: '-setup\.exe$'
|
|
270
|
+
fork-user: skssmd
|
|
271
|
+
token: ${{ secrets.WINGET_TOKEN }}
|
|
272
|
+
|
|
273
|
+
scoop:
|
|
274
|
+
needs: [wheel, release]
|
|
275
|
+
if: startsWith(github.ref, 'refs/tags/')
|
|
276
|
+
runs-on: ubuntu-24.04
|
|
277
|
+
steps:
|
|
278
|
+
- uses: actions/checkout@v4
|
|
279
|
+
- uses: actions/download-artifact@v4
|
|
280
|
+
with:
|
|
281
|
+
name: bundle-windows-x86_64
|
|
282
|
+
path: dl
|
|
283
|
+
|
|
284
|
+
- name: Render the manifest
|
|
285
|
+
run: |
|
|
286
|
+
set -eu
|
|
287
|
+
version="${{ needs.wheel.outputs.version }}"
|
|
288
|
+
hash=$(sha256sum dl/*.zip | cut -d' ' -f1)
|
|
289
|
+
sed -e "s/@VERSION@/$version/g" -e "s/@HASH@/$hash/g" \
|
|
290
|
+
packaging/scoop/manifest.json.template > aibrowsertoolkit.json
|
|
291
|
+
cat aibrowsertoolkit.json
|
|
292
|
+
|
|
293
|
+
- name: Push to the bucket
|
|
294
|
+
env:
|
|
295
|
+
TOKEN: ${{ secrets.TAP_TOKEN }}
|
|
296
|
+
run: |
|
|
297
|
+
set -eu
|
|
298
|
+
git clone "https://x-access-token:$TOKEN@github.com/skssmd/scoop-bucket.git" bucket
|
|
299
|
+
mkdir -p bucket/bucket
|
|
300
|
+
cp aibrowsertoolkit.json bucket/bucket/aibrowsertoolkit.json
|
|
301
|
+
cd bucket
|
|
302
|
+
git config user.name "skssmd"
|
|
303
|
+
git config user.email "skssmd78475@gmail.com"
|
|
304
|
+
git add bucket/aibrowsertoolkit.json
|
|
305
|
+
git diff --cached --quiet && exit 0
|
|
306
|
+
git commit -m "aibrowsertoolkit: ${{ needs.wheel.outputs.version }}"
|
|
307
|
+
# Graft's release workflow writes to this repo too; a simultaneous
|
|
308
|
+
# release would otherwise lose this push to a non-fast-forward.
|
|
309
|
+
# Push first, rebase only when the push is rejected. `git pull
|
|
310
|
+
# --rebase` fails outright against a repository with no commits --
|
|
311
|
+
# "no such ref was fetched" -- which is exactly the state a freshly
|
|
312
|
+
# created bucket is in, so pulling first meant the very first
|
|
313
|
+
# release could never land. The rebase still covers the real case
|
|
314
|
+
# it was written for: a concurrent write to a shared repo.
|
|
315
|
+
for attempt in 1 2 3; do
|
|
316
|
+
git push -u origin HEAD:main && exit 0
|
|
317
|
+
git pull --rebase origin main || true
|
|
318
|
+
sleep 5
|
|
319
|
+
done
|
|
320
|
+
exit 1
|
|
321
|
+
|
|
322
|
+
brew:
|
|
323
|
+
needs: [wheel, release]
|
|
324
|
+
if: startsWith(github.ref, 'refs/tags/')
|
|
325
|
+
runs-on: ubuntu-24.04
|
|
326
|
+
steps:
|
|
327
|
+
- uses: actions/checkout@v4
|
|
328
|
+
- uses: actions/download-artifact@v4
|
|
329
|
+
with:
|
|
330
|
+
name: bundle-macos-arm64
|
|
331
|
+
path: dl
|
|
332
|
+
|
|
333
|
+
- name: Render the formula
|
|
334
|
+
run: |
|
|
335
|
+
set -eu
|
|
336
|
+
version="${{ needs.wheel.outputs.version }}"
|
|
337
|
+
arm=$(sha256sum dl/*.tar.gz | cut -d' ' -f1)
|
|
338
|
+
sed -e "s/@VERSION@/$version/g" -e "s/@SHA_ARM@/$arm/g" \
|
|
339
|
+
packaging/homebrew/formula.rb.template > aibrowsertoolkit.rb
|
|
340
|
+
cat aibrowsertoolkit.rb
|
|
341
|
+
|
|
342
|
+
- name: Push to the tap
|
|
343
|
+
env:
|
|
344
|
+
TOKEN: ${{ secrets.TAP_TOKEN }}
|
|
345
|
+
run: |
|
|
346
|
+
set -eu
|
|
347
|
+
git clone "https://x-access-token:$TOKEN@github.com/skssmd/homebrew-tap.git" tap
|
|
348
|
+
mkdir -p tap/Formula
|
|
349
|
+
cp aibrowsertoolkit.rb tap/Formula/aibrowsertoolkit.rb
|
|
350
|
+
cd tap
|
|
351
|
+
git config user.name "skssmd"
|
|
352
|
+
git config user.email "skssmd78475@gmail.com"
|
|
353
|
+
git add Formula/aibrowsertoolkit.rb
|
|
354
|
+
git diff --cached --quiet && exit 0
|
|
355
|
+
git commit -m "aibrowsertoolkit ${{ needs.wheel.outputs.version }}"
|
|
356
|
+
# Graft writes Formula/graft.rb in this same repo.
|
|
357
|
+
# Push first, rebase only when the push is rejected. `git pull
|
|
358
|
+
# --rebase` fails outright against a repository with no commits --
|
|
359
|
+
# "no such ref was fetched" -- which is exactly the state a freshly
|
|
360
|
+
# created bucket is in, so pulling first meant the very first
|
|
361
|
+
# release could never land. The rebase still covers the real case
|
|
362
|
+
# it was written for: a concurrent write to a shared repo.
|
|
363
|
+
for attempt in 1 2 3; do
|
|
364
|
+
git push -u origin HEAD:main && exit 0
|
|
365
|
+
git pull --rebase origin main || true
|
|
366
|
+
sleep 5
|
|
367
|
+
done
|
|
368
|
+
exit 1
|
|
369
|
+
|
|
370
|
+
aur:
|
|
371
|
+
needs: [wheel, release]
|
|
372
|
+
if: startsWith(github.ref, 'refs/tags/') && !contains(github.ref_name, '-rc')
|
|
373
|
+
runs-on: ubuntu-24.04
|
|
374
|
+
steps:
|
|
375
|
+
- uses: actions/checkout@v4
|
|
376
|
+
- uses: actions/download-artifact@v4
|
|
377
|
+
with:
|
|
378
|
+
pattern: bundle-linux-*
|
|
379
|
+
path: dl
|
|
380
|
+
|
|
381
|
+
- name: Render the PKGBUILD
|
|
382
|
+
run: |
|
|
383
|
+
set -eu
|
|
384
|
+
version="${{ needs.wheel.outputs.version }}"
|
|
385
|
+
x86=$(sha256sum dl/bundle-linux-x86_64/*.tar.gz | cut -d' ' -f1)
|
|
386
|
+
arm=$(sha256sum dl/bundle-linux-aarch64/*.tar.gz | cut -d' ' -f1)
|
|
387
|
+
mkdir -p aurpkg
|
|
388
|
+
sed -e "s/@VERSION@/$version/g" -e "s/@SHA_X86@/$x86/g" -e "s/@SHA_ARM@/$arm/g" \
|
|
389
|
+
packaging/aur/PKGBUILD.template > aurpkg/PKGBUILD
|
|
390
|
+
cp packaging/aur/aibrowsertoolkit-bin.install aurpkg/
|
|
391
|
+
cat aurpkg/PKGBUILD
|
|
392
|
+
|
|
393
|
+
- uses: KSXGitHub/github-actions-deploy-aur@v3
|
|
394
|
+
with:
|
|
395
|
+
pkgname: aibrowsertoolkit-bin
|
|
396
|
+
pkgbuild: aurpkg/PKGBUILD
|
|
397
|
+
assets: aurpkg/aibrowsertoolkit-bin.install
|
|
398
|
+
commit_username: skssmd
|
|
399
|
+
commit_email: skssmd78475@gmail.com
|
|
400
|
+
ssh_private_key: ${{ secrets.AUR_SSH_KEY }}
|
|
401
|
+
commit_message: "aibrowsertoolkit-bin ${{ needs.wheel.outputs.version }}"
|
|
402
|
+
|
|
403
|
+
fury:
|
|
404
|
+
needs: [wheel, release]
|
|
405
|
+
if: startsWith(github.ref, 'refs/tags/')
|
|
406
|
+
runs-on: ubuntu-24.04
|
|
407
|
+
steps:
|
|
408
|
+
- uses: actions/checkout@v4
|
|
409
|
+
- uses: actions/download-artifact@v4
|
|
410
|
+
with:
|
|
411
|
+
pattern: bundle-linux-*
|
|
412
|
+
path: dl
|
|
413
|
+
|
|
414
|
+
- name: Install nfpm
|
|
415
|
+
env:
|
|
416
|
+
# Pinned on purpose: a release pipeline that silently picks up a new
|
|
417
|
+
# packaging tool is one that breaks on a day you changed nothing.
|
|
418
|
+
NFPM_VERSION: "2.41.3"
|
|
419
|
+
run: |
|
|
420
|
+
set -eu
|
|
421
|
+
curl -fsSL -o nfpm.tar.gz \
|
|
422
|
+
"https://github.com/goreleaser/nfpm/releases/download/v${NFPM_VERSION}/nfpm_${NFPM_VERSION}_Linux_x86_64.tar.gz"
|
|
423
|
+
tar xf nfpm.tar.gz nfpm && sudo mv nfpm /usr/local/bin/
|
|
424
|
+
nfpm --version
|
|
425
|
+
|
|
426
|
+
- name: Build and push every package
|
|
427
|
+
env:
|
|
428
|
+
PKG_VERSION: ${{ needs.wheel.outputs.version }}
|
|
429
|
+
FURY_TOKEN: ${{ secrets.FURY_TOKEN }}
|
|
430
|
+
run: |
|
|
431
|
+
set -eu
|
|
432
|
+
chmod +x packaging/postinstall.sh packaging/preremove.sh
|
|
433
|
+
mkdir -p out
|
|
434
|
+
for pair in "x86_64:amd64" "aarch64:arm64"; do
|
|
435
|
+
target="${pair%%:*}"
|
|
436
|
+
PKG_ARCH="${pair##*:}"
|
|
437
|
+
export PKG_ARCH
|
|
438
|
+
rm -rf payload && mkdir payload
|
|
439
|
+
tar xzf dl/bundle-linux-$target/*.tar.gz -C payload --strip-components=1
|
|
440
|
+
for fmt in deb rpm apk; do
|
|
441
|
+
nfpm pkg --config packaging/nfpm.yaml --packager "$fmt" --target out/
|
|
442
|
+
done
|
|
443
|
+
done
|
|
444
|
+
ls -l out
|
|
445
|
+
for pkg in out/*; do
|
|
446
|
+
echo "pushing $pkg"
|
|
447
|
+
curl -fsS -F package=@"$pkg" "https://$FURY_TOKEN@push.fury.io/skssmd/"
|
|
448
|
+
done
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
.venv/
|
|
2
|
+
# Browser profiles. One directory per named profile, and every one of them
|
|
3
|
+
# holds live logins -- see docs/superpowers/specs on profile sessions.
|
|
4
|
+
profiles/
|
|
5
|
+
logs/
|
|
6
|
+
.e2e-profile/
|
|
7
|
+
.demo-profile/
|
|
8
|
+
logs-demo/
|
|
9
|
+
.e2e.log
|
|
10
|
+
.e2e.err
|
|
11
|
+
server.log
|
|
12
|
+
server.err
|
|
13
|
+
server-*.log
|
|
14
|
+
server-*.err
|
|
15
|
+
demo.log
|
|
16
|
+
__pycache__/
|
|
17
|
+
*.py[cod]
|
|
18
|
+
*.egg-info/
|
|
19
|
+
.pytest_cache/
|
|
20
|
+
build/
|
|
21
|
+
dist/
|
|
22
|
+
/guidelines/onehr
|
|
23
|
+
|
|
24
|
+
# OneHr client material. The playbooks and session write-ups name a real
|
|
25
|
+
# organisation, its routes and its staff records, so they stay on disk and out
|
|
26
|
+
# of the repo. Name-matched rather than path-matched so a new write-up is
|
|
27
|
+
# covered the moment it is created, not the next time someone remembers.
|
|
28
|
+
# Case variants because git's ignore matching is case-sensitive by default.
|
|
29
|
+
*onehr*
|
|
30
|
+
*OneHr*
|
|
31
|
+
*ONEHR*
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
https://docs.google.com/spreadsheets/d/1-y9x7VJLL7bTN4rSnazxMB8DRdZtOpX8_YlW8PKHZQM/edit?gid=0#gid=0
|