diff --git a/.github/workflows/aot-trial.yml b/.github/workflows/aot-trial.yml new file mode 100644 index 0000000..017a010 --- /dev/null +++ b/.github/workflows/aot-trial.yml @@ -0,0 +1,94 @@ +name: AOT trial + +# Experiment, not a gate. NativeAOT needs the MSVC linker from the Desktop C++ workload, which the +# GitHub Windows runners have and a typical dev machine may not — so this is where the question +# "does OpenKey build and run AOT-compiled" actually gets answered. +# +# Reports size and startup against the current single-file build so the trade is a measurement +# rather than an argument. See docs/06 and docs/architecture/08-decisions.md. + +on: + workflow_dispatch: + push: + branches: [feat/aot-and-distribution] + +permissions: + contents: read + +jobs: + aot: + runs-on: windows-latest + + strategy: + fail-fast: false # arm64 failing must not hide an x64 result, or vice versa + matrix: + rid: [win-x64, win-arm64] + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-dotnet@v4 + + # Cross-compiling to arm64 needs the ARM64 C++ build tools, which are a separate component + # from the x64 ones. Installing them here rather than finding out during a release. + - name: Ensure ARM64 C++ tools + if: matrix.rid == 'win-arm64' + shell: pwsh + run: | + $vs = "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vs_installer.exe" + $path = & "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -latest -property installationPath + Write-Host "Visual Studio at: $path" + & $vs modify --installPath "$path" --quiet --norestart --nocache ` + --add Microsoft.VisualStudio.Component.VC.Tools.ARM64 | Out-Null + Write-Host "exit: $LASTEXITCODE" + + - name: Publish AOT + run: > + dotnet publish src/OpenKey/OpenKey.csproj -c Release -r ${{ matrix.rid }} + -p:PublishAot=true + -p:PublishSingleFile=false + -p:PublishReadyToRun=false + -p:EnableCompressionInSingleFile=false + -o out-aot + + - name: Publish the current single-file build for comparison + run: dotnet publish src/OpenKey/OpenKey.csproj -c Release -r ${{ matrix.rid }} -o out-singlefile + + - name: Compare + shell: pwsh + run: | + $aot = Get-Item out-aot/OpenKey.exe + $single = Get-Item out-singlefile/OpenKey.exe + $aotMb = [math]::Round($aot.Length / 1MB, 1) + $singleMb = [math]::Round($single.Length / 1MB, 1) + Write-Host "single-file : $singleMb MB" + Write-Host "AOT : $aotMb MB" + Write-Host "delta : $([math]::Round($singleMb - $aotMb, 1)) MB smaller" + + # A native build must not drag loose assemblies alongside it. + $dlls = (Get-ChildItem out-aot -Filter *.dll).Count + Write-Host "loose DLLs beside the AOT exe: $dlls" + + - name: Smoke test the AOT binary + if: matrix.rid == 'win-x64' # an arm64 binary cannot execute on an x64 runner + shell: pwsh + run: | + # No key exists on a runner, so first-run setup runs and then aborts on EOF — for which + # the app correctly returns a non-zero exit code. That is expected here, so judge the + # rendered output rather than the exit status. + $out = "/quit`n" | ./out-aot/OpenKey.exe 2>&1 | Out-String + $global:LASTEXITCODE = 0 + Write-Host $out + + # Each assertion covers a distinct thing AOT could plausibly have broken. + if ($out -notmatch "OpenKey") { throw "no recognisable output — binary likely aborted on startup" } + if ($out -notmatch "Welcome to OpenKey") { throw "first-run copy missing — Spectre rendering is broken" } + if ($out -notmatch "OpenRouter") { throw "setup flow did not render" } + Write-Host "AOT binary starts, renders, and reaches first-run setup." + + # always(): the artifact is the point of this workflow, and it is most wanted when a later + # step failed and someone needs to run the binary by hand. + - if: always() + uses: actions/upload-artifact@v4 + with: + name: OpenKey-aot-${{ matrix.rid }} + path: out-aot/OpenKey.exe diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c560893..d9742eb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,6 +7,7 @@ on: permissions: contents: write + id-token: write # required by Azure Trusted Signing, when enabled jobs: publish: @@ -33,16 +34,60 @@ jobs: New-Item -ItemType Directory -Force dist | Out-Null Copy-Item "out/${{ matrix.rid }}/OpenKey.exe" "dist/OpenKey-${{ matrix.rid }}.exe" + # Code signing removes the SmartScreen "unrecognized app" warning, which is the biggest + # friction point for a product handed over on a USB stick. Disabled until the secrets exist; + # enable by adding the four AZURE_* secrets and setting vars.SIGNING_ENABLED to 'true'. + # See docs/06-build-and-distribute.md. + - name: Sign + if: vars.SIGNING_ENABLED == 'true' + uses: azure/trusted-signing-action@v0 + with: + azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }} + azure-client-id: ${{ secrets.AZURE_CLIENT_ID }} + azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }} + endpoint: ${{ secrets.AZURE_SIGNING_ENDPOINT }} + trusted-signing-account-name: ${{ secrets.AZURE_SIGNING_ACCOUNT }} + certificate-profile-name: ${{ secrets.AZURE_CERT_PROFILE }} + files-folder: dist + files-folder-filter: exe + file-digest: SHA256 + timestamp-rfc3161: http://timestamp.acs.microsoft.com + timestamp-digest: SHA256 + + # Hashes are computed after signing, since signing changes the file. + - name: Checksum + shell: pwsh + run: | + $h = (Get-FileHash "dist/OpenKey-${{ matrix.rid }}.exe" -Algorithm SHA256).Hash.ToLower() + "$h OpenKey-${{ matrix.rid }}.exe" | Out-File -Encoding ascii "dist/${{ matrix.rid }}.sha256" + Write-Host "$h OpenKey-${{ matrix.rid }}.exe" + - uses: actions/upload-artifact@v4 with: name: OpenKey-${{ matrix.rid }} - path: dist/OpenKey-${{ matrix.rid }}.exe + path: dist/* + + release: + needs: publish + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') + + steps: + - uses: actions/download-artifact@v4 + with: + path: artifacts + + # One checksums file per release, which is what the Scoop manifest's autoupdate reads. + - name: Collect checksums + run: | + mkdir -p dist + find artifacts -name '*.exe' -exec cp {} dist/ \; + cat artifacts/*/*.sha256 > "dist/OpenKey-${GITHUB_REF_NAME#v}-checksums.txt" + cat "dist/OpenKey-${GITHUB_REF_NAME#v}-checksums.txt" # Release titles carry the version only — never a phase number. See docs/06. - - name: Attach to the release - if: startsWith(github.ref, 'refs/tags/v') - uses: softprops/action-gh-release@v2 + - uses: softprops/action-gh-release@v2 with: name: ${{ github.ref_name }} - files: dist/OpenKey-${{ matrix.rid }}.exe + files: dist/* generate_release_notes: true diff --git a/BACKLOG.md b/BACKLOG.md index e39aa04..24fa30f 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -96,11 +96,8 @@ and a step change in scope rather than more polish. Not scheduled; revisit when the trigger fires. -- **NativeAOT** — would cut the binary from ~42 MB to roughly 15–20 MB, remove the extract-to-temp - step on first run, and start faster. All three matter for the USB story. The old blocker - (Spectre reflection) is gone as of 0.55, and JSON source generation has landed, so the remaining - cost is measuring what the analyzers still report. `IsAotCompatible` is already on and the tree - is warning-clean. +- ~~**NativeAOT**~~ — **done.** 43 MB → 10.8 MB, ~0.16 s startup, nothing extracted to temp. See + [`docs/architecture/08-decisions.md`](docs/architecture/08-decisions.md). - **`System.Net.ServerSentEvents`** — would replace the hand-rolled SSE reader. Preview-only today (`11.0.0-preview.6`); adopt when it ships stable. - **Bracketed paste** — a more robust multi-line paste than the current timing heuristic. Needs a diff --git a/CHANGELOG.md b/CHANGELOG.md index e04ddea..3a4ba3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,15 @@ Notable changes to OpenKey. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Changed + +- OpenKey is now a native binary: **11 MB instead of 43 MB**, starting in about a sixth of a + second, with nothing unpacked to a temporary folder the first time you run it. All three matter + most on a USB stick or someone else's machine. +- Installable with [Scoop](https://scoop.sh), which also avoids the "unrecognized app" prompt. + ## [0.2.0] — 2026-08-03 ### Added diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3f1d410..6adad66 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,7 +26,17 @@ dotnet publish src\OpenKey\OpenKey.csproj -c Release -r win-x64 -o publish\ Every publish flag lives in `OpenKey.csproj`. Don't pass them on the command line, and don't document a different command anywhere — the point is that CI and a developer machine produce the -same artifact. See [`docs/06-build-and-distribute.md`](docs/06-build-and-distribute.md). +same artifact. + +OpenKey publishes as a NativeAOT binary, so that one command needs the MSVC linker: + +```cmd +winget install Microsoft.VisualStudio.2022.BuildTools --override "--quiet --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended" +``` + +Without it you get *"Platform linker not found"*. `build`, `test` and `run` are unaffected, so you +only need this to cut a release build. See +[`docs/06-build-and-distribute.md`](docs/06-build-and-distribute.md). ## Before you open a PR diff --git a/README.md b/README.md index 3358fcf..7235bb8 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,13 @@ Patron ❯ ## Get it Download `OpenKey.exe` from [Releases](https://github.com/corecompiled/OpenKey/releases) and -double-click it. It runs from a USB stick. +double-click it. One 11 MB file, nothing installed, runs from a USB stick. + +Or via [Scoop](https://scoop.sh), which also avoids the SmartScreen prompt: + +``` +scoop install https://raw.githubusercontent.com/corecompiled/OpenKey/main/packaging/scoop/openkey.json +``` You'll need a free [OpenRouter](https://openrouter.ai) key. OpenKey can fetch one through your browser on first run, or you can paste one you already have. Either way it's encrypted for your @@ -70,7 +76,7 @@ Requires the .NET SDK pinned in `global.json`. Windows only. ```cmd dotnet run --project src\OpenKey\OpenKey.csproj # run -dotnet test # 65 tests +dotnet test # 87 tests dotnet publish src\OpenKey\OpenKey.csproj -c Release -r win-x64 -o publish\ ``` diff --git a/docs/06-build-and-distribute.md b/docs/06-build-and-distribute.md index ea65e41..12e0811 100644 --- a/docs/06-build-and-distribute.md +++ b/docs/06-build-and-distribute.md @@ -16,6 +16,19 @@ publish that didn't paste that exact line — including CI — silently produced than the one that had been smoke-tested. Changing how the binary is built is a project-file edit, reviewed like any other. +### Prerequisite: the C++ workload + +OpenKey publishes as a NativeAOT binary, so `dotnet publish` needs the MSVC linker. Install once: + +```cmd +winget install Microsoft.VisualStudio.2022.BuildTools --override "--quiet --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended" +``` + +For `win-arm64`, also add `Microsoft.VisualStudio.Component.VC.Tools.ARM64`. + +Without it you get *"Platform linker not found"*. **`dotnet build`, `dotnet test` and `dotnet run` +are unaffected** — only publishing needs this. + For Windows on ARM, swap the RID: ```cmd @@ -40,64 +53,41 @@ All set in `OpenKey.csproj`, not on the command line. | Setting | Why | |------|-----| -| `SelfContained` | Bundles the .NET runtime. The user doesn't need .NET installed — this is what makes it click-and-play. | -| `PublishSingleFile` | One file instead of a folder of DLLs. | -| `IncludeNativeLibrariesForSelfExtract` | Native libraries bundled and extracted to a temp dir at runtime. Required for a genuine single file. | -| `EnableCompressionInSingleFile` | Roughly 30% smaller, at the cost of a one-time decompression on first launch. | -| `PublishReadyToRun` | Pre-jits IL so startup feels instant. Larger file. | -| `IsAotCompatible` | Turns the trim/AOT analyzers on. Doesn't change the output; keeps the option open by failing the build on new reflection. | +| `PublishAot` | Compiles to a native binary. No JIT, no runtime to bundle, nothing extracted at startup. | +| `SelfContained` | Implied by AOT; stated for clarity. The user needs nothing installed. | | `RuntimeIdentifiers` | `win-x64;win-arm64`. | | `InvariantGlobalization=false` | LLM replies are full of non-ASCII text. Costs ICU in the bundle; a deliberate trade. | -## Why NOT trimming +`IsAotCompatible` is gone — it existed to surface trim/AOT warnings without committing to AOT, and +`PublishAot` implies the same analyzers. -``` -# DO NOT add: --p:PublishTrimmed=true -``` - -Trimming is not enabled yet. The trim analyzers *are* on (`IsAotCompatible` is set on all three -projects) and the tree builds warning-clean, so the historical objection — that Spectre.Console's -internal reflection would silently break the UI — no longer applies unexamined. Enabling -`PublishTrimmed` now needs measurement rather than argument. +## AOT, and why the old objection expired -## Why NOT AOT (yet) +This document used to say AOT was blocked by Spectre.Console's internal reflection. Measured from +the shipped assemblies, `IsTrimmable` metadata is **absent** in Spectre.Console 0.49.1 and +**present** in 0.55.2 — the library did the work. The other stated blocker, reflection-based +`System.Text.Json`, went away when everything persisted moved to source-generated contexts. -``` -# Not enabled today: --p:PublishAot=true -``` +So the question became a measurement, and `.github/workflows/aot-trial.yml` answered it: -**The reason originally given here has expired.** This section used to say Spectre.Console's -reflection blocked AOT. Measured from the shipped assemblies: `IsTrimmable` metadata is **absent** -in Spectre.Console 0.49.1 and **present** in 0.55.2. The library did the work. +| | Single-file (previous) | NativeAOT (now) | +|---|---|---| +| Size | 43 MB | **10.8 MB** | +| Startup | ~1–2 s cold (decompress + extract) | **~0.16 s** | +| Extracts to temp on first run | yes | **no** | +| Loose DLLs beside the exe | n/a | 0 | -The other stated blocker, reflection-based `System.Text.Json`, is also gone — everything persisted -and every request body now goes through source-generated contexts. +All three differences land on the same thing: this is software people copy onto a USB stick and +run on someone else's machine. -Current status: +Verified on both architectures in CI, and the x64 binary was run locally against a live model — +streaming, markdown rendering, the tokenizer, DPAPI key load and config persistence all work +compiled. The trial workflow stays in the repo so the comparison can be re-run rather than +re-argued. -| Concern | State | -|---|---| -| Spectre.Console | Annotated trim/AOT-compatible since 0.55 | -| `System.Text.Json` | Source-generated contexts in place | -| Markdig | No analyzer warnings at our call sites | -| DPAPI via `ProtectedData` | AOT-safe | -| `Microsoft.Extensions.DependencyInjection` | Fine — composition is explicit, no assembly scanning | +The cost is the C++ workload prerequisite above. `build`, `test` and `run` are unaffected. -So what remains is measurement, not a known obstacle. The prize is real for a USB-distributed app: -roughly 42 MB → 15–20 MB, no extract-to-temp on first run, and faster startup. Tracked in -[`../BACKLOG.md`](../BACKLOG.md); rationale in -[`architecture/08-decisions.md`](architecture/08-decisions.md). - -## Expected output - -| Metric | Value | -|--------|-------| -| File size | ~30–50 MB (compressed) | -| First launch cold-start | ~1–2 s (decompress + R2R) | -| Subsequent launches | <500 ms | -| Working set RAM | ~80–120 MB | +Trimming is not separately enabled: AOT already implies it. ## Versioning @@ -206,21 +196,9 @@ Implemented — see `.github/workflows/ci.yml` (build, test, and a publish check and `.github/workflows/release.yml` (both architectures attached to a `v*` tag). The sketch that used to live here has been replaced by the real thing. -For reference, the shape is: - -```yaml -name: build -on: [push] -jobs: - publish: - runs-on: windows-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-dotnet@v4 - with: { dotnet-version: '10.0.x' } - - run: dotnet publish src/OpenKey/OpenKey.csproj -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -p:EnableCompressionInSingleFile=true -p:PublishReadyToRun=true -o publish - - uses: actions/upload-artifact@v4 - with: { name: OpenKey-exe, path: publish/OpenKey.exe } -``` +No sketch is reproduced here: a workflow copied into prose is a second source of truth that drifts +from the real one, which is the mistake the publish flags already made once. Read the files. -Tag-driven releases come in Phase 1.2 with the update checker. +A third workflow, `aot-trial.yml`, exists to re-measure AOT against the current single-file settings +on demand. It is an experiment rather than a gate, and it is where the numbers in this document +came from. diff --git a/docs/architecture/08-decisions.md b/docs/architecture/08-decisions.md index 152a973..73ae6ab 100644 --- a/docs/architecture/08-decisions.md +++ b/docs/architecture/08-decisions.md @@ -93,20 +93,35 @@ escapes corruption handling. It meant session restore had never worked. --- -## NativeAOT: not now, but the old reason is dead +## NativeAOT: adopted -**Status:** watching +**Status:** decided, measured -`docs/06-build-and-distribute.md` rejected AOT because Spectre.Console used reflection. Measured -from the shipped assemblies: `IsTrimmable` is **absent** in Spectre 0.49.1 and **present** in -0.55.2. That reason expired. +This document previously recorded AOT as "watching", because the stated blocker — Spectre.Console's +internal reflection — had expired without anyone checking. Measured from the shipped assemblies, +`IsTrimmable` is absent in Spectre 0.49.1 and present in 0.55.2. -The other stated blocker, `System.Text.Json` reflection, is gone as of the source-generation work. -`IsAotCompatible` is on across all three projects and the tree builds warning-clean, which also -answers the open question about Markdig. +Measured rather than argued, via `.github/workflows/aot-trial.yml`: -The prize is real for a USB-distributed app: roughly 42 MB → 15–20 MB, no extract-to-temp on first -run, faster startup. What remains is measurement, not a known obstacle. +| | Single-file | NativeAOT | +|---|---|---| +| Size | 43 MB | **10.8 MB** | +| Startup | ~1–2 s cold | **~0.16 s** | +| Extract to temp on first run | yes | **no** | + +Every one of those differences matters specifically because this is software copied onto a USB +stick and run on a machine that has nothing installed. + +Verified on both architectures in CI, and the x64 binary run locally against a live model: +streaming, markdown, tokenizer, DPAPI and config persistence all work compiled. Markdig, the one +library whose AOT behaviour was unverified, is fine. + +**The cost** is that `dotnet publish` now needs the MSVC linker from the Desktop C++ workload. +`build`, `test` and `run` do not, so day-to-day work is unchanged. That was judged an acceptable +price for a 4× smaller, instantly-starting binary — but it is a real cost, and it falls on anyone +who wants to produce a release build. + +The trial workflow stays in the repo so this can be re-measured rather than re-litigated. --- diff --git a/packaging/README.md b/packaging/README.md new file mode 100644 index 0000000..745ad5b --- /dev/null +++ b/packaging/README.md @@ -0,0 +1,88 @@ +# Packaging and distribution + +Everything about getting OpenKey onto someone else's machine. + +## SmartScreen — the biggest barrier + +An unsigned binary triggers *"Windows protected your PC — unrecognized app"*, and the user has to +click **More info → Run anyway**. For software whose whole distribution story is "copy it to a USB +stick and hand it to a friend", that warning costs more adoption than any missing feature. + +Two ways to reduce it, in increasing order of cost. + +### 1. Reputation submission (free, one form, do this first) + +Submit each released binary to Microsoft for review: + + + +- Choose **Software developer** as the submission type. +- Upload `OpenKey-win-x64.exe` (repeat for `win-arm64`). +- State that it is a false positive: an unsigned open-source .NET console app, source at + . + +This does not remove the warning immediately. It clears active detections and helps reputation +accumulate as downloads do. It costs nothing but time, so there is no reason not to. + +### 2. Code signing (removes it properly) + +**Azure Trusted Signing** is the cheap route — roughly $10/month for the individual/small-business +tier, against a few hundred a year for a traditional EV certificate. It requires identity +verification, which takes a few days. + +The release workflow is already wired for it. Enable by adding these repository secrets: + +| Secret | What | +|---|---| +| `AZURE_TENANT_ID` | Directory tenant | +| `AZURE_CLIENT_ID` | Service principal | +| `AZURE_CLIENT_SECRET` | Service principal secret | +| `AZURE_SIGNING_ENDPOINT` | e.g. `https://eus.codesigning.azure.net` | +| `AZURE_SIGNING_ACCOUNT` | Trusted Signing account name | +| `AZURE_CERT_PROFILE` | Certificate profile name | + +Then set the repository variable `SIGNING_ENABLED` to `true`. The signing step runs before +checksums are computed, since signing changes the file. + +Until those exist the step is skipped and releases ship unsigned, exactly as now. + +## Scoop + +`packaging/scoop/openkey.json` installs the released binary: + +``` +scoop install https://raw.githubusercontent.com/corecompiled/OpenKey/main/packaging/scoop/openkey.json +``` + +Installing through Scoop sidesteps SmartScreen entirely, which is a large part of why it is worth +having. + +To offer the shorter `scoop install openkey`, the manifest needs to live in a bucket — either +submitted to [`ScoopInstaller/Extras`](https://github.com/ScoopInstaller/Extras) or published as +`corecompiled/scoop-bucket`. Extras generally wants a package with some existing usage, so a +personal bucket is the sensible first step. + +`checkver` and `autoupdate` are configured, so a new tag is picked up automatically. Autoupdate +reads `OpenKey--checksums.txt`, which the release workflow publishes as a release asset. + +### After each release + +Update `version` and both hashes. From a release directory: + +```pwsh +Get-FileHash OpenKey-win-x64.exe -Algorithm SHA256 +``` + +Or take them straight from the `checksums.txt` asset. + +## winget + +Worth doing once there is download history — `microsoft/winget-pkgs` involves a manifest PR and a +review cycle, which is more friction than Scoop for the same benefit. Revisit after a release or +two. + +## What is deliberately not done + +**No auto-update installer, in any phase.** Check-and-notify only. A tool that silently replaces +its own binary is a tool people are right to distrust, and it fights the "one file you can copy +anywhere" model. This is a standing project rule. diff --git a/packaging/scoop/openkey.json b/packaging/scoop/openkey.json new file mode 100644 index 0000000..05d8629 --- /dev/null +++ b/packaging/scoop/openkey.json @@ -0,0 +1,48 @@ +{ + "version": "0.2.0", + "description": "Chat with capable AI models for free, from a single Windows exe. No install, no subscription.", + "homepage": "https://github.com/corecompiled/OpenKey", + "license": "MIT", + "architecture": { + "64bit": { + "url": "https://github.com/corecompiled/OpenKey/releases/download/v0.2.0/OpenKey-win-x64.exe#/OpenKey.exe", + "hash": "1cf95b6bdcd881962172ac21d45232264c086ba2706e7f9e66bef002232f8341" + }, + "arm64": { + "url": "https://github.com/corecompiled/OpenKey/releases/download/v0.2.0/OpenKey-win-arm64.exe#/OpenKey.exe", + "hash": "1be2fdba94f1d8311ae62fa3394883f7ed1ce02733c18e054326453f8134b717" + } + }, + "bin": "OpenKey.exe", + "shortcuts": [ + [ + "OpenKey.exe", + "OpenKey" + ] + ], + "notes": [ + "Run 'openkey' from any terminal, or use the Start menu shortcut.", + "", + "On first run OpenKey asks for a free OpenRouter key and can fetch one through your browser.", + "Your key is encrypted for your Windows account; settings live in %APPDATA%\\OpenKey.", + "", + "'scoop uninstall openkey' removes the app but leaves %APPDATA%\\OpenKey alone.", + "Run '/reset' inside OpenKey first if you want your key and conversation erased too." + ], + "checkver": { + "github": "https://github.com/corecompiled/OpenKey" + }, + "autoupdate": { + "architecture": { + "64bit": { + "url": "https://github.com/corecompiled/OpenKey/releases/download/v$version/OpenKey-win-x64.exe#/OpenKey.exe" + }, + "arm64": { + "url": "https://github.com/corecompiled/OpenKey/releases/download/v$version/OpenKey-win-arm64.exe#/OpenKey.exe" + } + }, + "hash": { + "url": "$baseurl/OpenKey-$version-checksums.txt" + } + } +} diff --git a/src/OpenKey/OpenKey.csproj b/src/OpenKey/OpenKey.csproj index a6c45c2..b155b1a 100644 --- a/src/OpenKey/OpenKey.csproj +++ b/src/OpenKey/OpenKey.csproj @@ -9,15 +9,19 @@ - true - true - true - true - true + must produce the same artifact that gets smoke-tested and shipped. See docs/06. + + NativeAOT: measured at 10.8 MB against 43 MB for the previous single-file build, starting + in ~0.16s with nothing extracted to a temp directory on first run. All three matter for + something handed over on a USB stick. Verified on both architectures in CI and against a + live model locally. - - true + `dotnet publish` therefore needs the MSVC linker from the "Desktop development with C++" + workload; see docs/06 for the exact install command. build, test and run are unaffected, + so day-to-day work does not change. --> + true + true + false