diff --git a/Packaging/Installer/CursorLang.wixproj b/Packaging/Installer/CursorLang.wixproj
new file mode 100644
index 0000000..3877049
--- /dev/null
+++ b/Packaging/Installer/CursorLang.wixproj
@@ -0,0 +1,48 @@
+
+
+
+
+ CursorLang
+ Package
+
+
+ ICE38;ICE64;ICE91
+
+
+
+ Version=$(CursorLangVersion);
+ PayloadDir=$(PayloadDir);
+ IconFile=$(IconFile);
+ LicenseFile=$(LicenseFile)
+
+
+
+
+
+
+
+
+
+
diff --git a/Packaging/PUBLISHING.RU.md b/Packaging/PUBLISHING.RU.md
new file mode 100644
index 0000000..bbe08b3
--- /dev/null
+++ b/Packaging/PUBLISHING.RU.md
@@ -0,0 +1,383 @@
+# Публикация в Microsoft Store
+
+Инструкция с нуля: от регистрации разработчика до отправки пакета на проверку.
+Документ существует только на русском — переводить его не нужно.
+
+Ссылки на первоисточники стоят по ходу текста, сводный список — в конце.
+Правила Store меняются, поэтому перед подачей стоит сверяться с ними, а не с
+пересказом здесь: этот документ отражает положение дел на август 2026 года.
+
+## 1. Аккаунт разработчика
+
+С 2026 года регистрация бесплатна для обоих типов аккаунта. Прежние взносы
+($19 и $99) отменены — [объявление для компаний][free-company],
+[объявление для частных лиц][free-individual]. Новостные заметки за 2025 год,
+где у Company ещё указан взнос $99, устарели.
+
+**Начинать нужно с https://storedeveloper.microsoft.com** — это единственный
+вход в бесплатный процесс регистрации. Если зайти через Partner Center, Visual
+Studio или Xbox, откроется старый порядок со взносом.
+
+### Какой тип выбрать
+
+Критерии обоих типов и порядок регистрации — [«Открытие аккаунта
+разработчика»][open-account].
+
+| | Individual | Company |
+|---|---|---|
+| Кому | частное лицо, хобби, некоммерческие проекты | ИП, ТОО, организация — всё, что связано с предпринимательской деятельностью |
+| Имя издателя в Store | ваше имя | название организации |
+| Вход | только личный аккаунт Microsoft | личный аккаунт или рабочий Microsoft Entra ID |
+| Проверка | документ с фотографией и селфи | D-U-N-S или уставные документы плюс рабочая почта на домене организации |
+| Срок | минуты, если снимки читаемые | от минут до 2–5 рабочих дней при ручной проверке |
+
+Company доступен только тем, у кого уже есть зарегистрированное юридическое лицо
+или ИП: проверка требует либо номера D-U-N-S, либо регистрационных документов.
+Без них остаётся Individual, и для личного бесплатного приложения этого
+достаточно.
+
+Три вещи после регистрации **не меняются** — тип аккаунта, страна и имя издателя
+([FAQ по управлению аккаунтом][account-faq]). Что с этим делать при отсутствии
+компании и планах на платный продукт, разобрано в
+[разделе 9](#9-регистрация-из-казахстана).
+
+### Что подготовить
+
+**Для Individual:** удостоверение личности или паспорт (оригинал, не копия) и
+телефон с камерой — снимок документа и селфи делаются на мобильном.
+
+**Для Company:**
+- номер D-U-N-S, если он есть — тогда данные подтянутся автоматически и проверка
+ пройдёт быстрее. Иначе понадобится справка о регистрации, устав или выписка из
+ государственного реестра;
+- почта на домене организации. Gmail и подобные не принимаются; если домен почты
+ не совпадает с доменом организации, попросят подтвердить владение доменом —
+ счётом от регистратора или записью из реестра доменов.
+
+На каждый вид проверки даётся не более трёх попыток, поэтому данные лучше
+перепроверить до отправки. Ответить на запрос документов нужно в течение 30 дней.
+
+### Налоги и выплаты
+
+Приложение бесплатное, встроенных покупок и рекламы нет — значит платёжный
+профиль и налоговые формы не нужны ([FAQ по управлению аккаунтом][account-faq]).
+Они потребуются, только если появятся платные функции, — тогда см.
+[раздел 8](#8-если-приложение-будет-платным).
+
+После создания аккаунта данные расходятся по Partner Center до получаса. Если
+раздел «Apps & Games» не появился сразу — подождать и обновить страницу.
+
+## 2. Резервирование имени
+
+Partner Center → **Apps and games** → **New product** → **MSIX or PWA app**.
+Ввести имя, нажать **Check availability**, затем **Reserve product name**.
+
+Подробности — [«Резервирование имени приложения MSIX»][reserve-name] и
+[«Управление резервированием имён»][manage-names].
+
+Имя можно занять за три месяца до публикации, даже если приложение ещё не
+готово. Стоит сразу занять запасные написания — например `CursorLang` и
+`Cursor Lang`.
+
+## 3. Данные пакета
+
+После резервирования: **Product management** → **Product identity**. На странице
+будут три значения:
+
+| Значение в Partner Center | Куда подставить |
+|---|---|
+| Package/Identity/Name | параметр `-IdentityName` |
+| Package/Identity/Publisher | параметр `-Publisher`, вида `CN=` и длинный идентификатор |
+| Package/Properties/PublisherDisplayName | параметр `-PublisherDisplayName` |
+
+Эти значения должны совпадать с манифестом пакета до символа. Расхождение даёт
+при загрузке невнятную ошибку, которая не называет поле, — поэтому копировать
+их нужно буквально, а не набирать руками. Разбор таких ошибок —
+[«Устранение ошибок отправки MSIX»][submission-errors].
+
+В сборочном конвейере эти значения лежат в переменных репозитория
+`MSIX_IDENTITY_NAME`, `MSIX_PUBLISHER` и `MSIX_PUBLISHER_DISPLAY_NAME`. Это
+переменные, а не секреты: identity виден в любом опубликованном пакете, а
+маскирование в логах только мешало бы разбирать ошибки сборки.
+
+## 4. Сборка пакета
+
+```powershell
+pwsh -File Packaging\build-msix.ps1 `
+ -Version 1.0.0.0 `
+ -IdentityName "" `
+ -Publisher "" `
+ -PublisherDisplayName ""
+```
+
+Получится `artifacts\packages\CursorLang-1.0.0.0-x64.msix`. Загружать его нужно
+**без подписи**: Store подписывает пакет своим сертификатом.
+
+Версия при каждой следующей отправке должна расти, а последнее число всегда
+остаётся нулём.
+
+## 5. Заявка
+
+**Product release** → **Start submission**. Разделы можно заполнять в любом
+порядке. Полный перечень полей с пометками обязательности —
+[«Создание заявки для приложения MSIX»][create-submission].
+
+### Pricing and availability
+
+Обязательны рынки, аудитория, обнаружимость, расписание и цена. Всё, кроме цены,
+имеет разумные значения по умолчанию — цену выставить **Free**
+([подробности о ценах и доступности][price-availability]).
+
+### Properties
+
+- **Category** — обязательна. Подходит *Productivity* либо *Utilities & tools*.
+- **Privacy policy URL** — обязателен, только если приложение собирает или
+ передаёт персональные данные. CursorLang не собирает ничего: настройки лежат
+ в папке приложения, наружу ничего не уходит. Поле можно оставить пустым, но
+ безопаснее выложить короткую страницу с фразой о том, что данные не
+ собираются, — это снимает вопросы у проверяющих.
+- **Contact details** — обязательны для аккаунта Company.
+
+### Age ratings
+
+Обязательна вся анкета. Приложение не игровое, ничего не собирает — ответы
+однозначные, рейтинг присвоится автоматически.
+
+### Packages
+
+Загрузить `.msix`. Раздел остаётся «Incomplete», пока не заполнены все
+обязательные поля, даже если сам пакет уже помечен как «Validated».
+
+### Store listings
+
+Хотя бы для одного языка. Стоит заполнить и английский, и русский — приложение
+переведено на оба. Основным лучше сделать английский: он работает как запасной
+вариант для рынков, где отдельного перевода нет.
+
+Обязательны описание и **минимум один снимок экрана** (рекомендуется четыре и
+больше). Снимки: PNG, от 1366×768. Показать стоит окно настроек и саму подсказку
+у курсора — вторую снять сложнее, поможет отложенный снимок экрана.
+
+### Submission options
+
+- **Restricted capabilities** — заполнить обязательно, потому что пакет объявляет
+ `runFullTrust`. Для обычной программы рабочего стола это подтверждается без
+ разбирательств; в обосновании достаточно написать, что приложение — обычная
+ программа Windows, а не UWP.
+- **Notes for certification** — не обязательно, но здесь лучше не молчать.
+ Приложение ставит низкоуровневый перехватчик клавиатуры, и без объяснений это
+ выглядит подозрительно. Стоит написать примерно так:
+
+ > The app installs a WH_KEYBOARD_LL hook to offer an optional Caps Lock
+ > shortcut that switches the keyboard layout instead of toggling case. The
+ > hook only inspects the Caps Lock key, is off by default, and is enabled by
+ > the user in the app's settings. No keystrokes are recorded, stored or
+ > transmitted anywhere.
+
+Затем **Submit for certification**.
+
+## 6. Чего ждать от проверки
+
+Проверка занимает обычно от нескольких часов до трёх дней. Проверяющие смотрят
+сам пакет, а не исходники: ссылка на репозиторий им не нужна и, скорее всего,
+останется без внимания. Всё, что важно донести, идёт в Notes for certification.
+
+Слабые места именно этого приложения:
+
+- **Перехватчик клавиатуры.** Главный повод для вопросов. Снимается пояснением в
+ Notes for certification и тем, что настройка выключена по умолчанию.
+- **Права администратора.** Их больше нет — ни в манифесте, ни в поведении.
+ Возможности `allowElevation` в пакете тоже нет. Возвращать их нельзя:
+ «приложения, которым права администратора нужны хоть для какой-то части
+ работы, в Store не принимаются» — [«Подготовка к упаковке»][prepare-package].
+- **Чистое удаление.** Настройки упакованной версии лежат в папке данных пакета
+ и уходят вместе с ним — это проверено.
+
+Общие требования, по которым идёт проверка, — [правила Microsoft Store][policies].
+
+## 7. Обновления
+
+Порядок тот же, короче: поднять версию, собрать пакет теми же значениями
+identity, создать новую заявку, загрузить пакет. Имя резервировать заново не
+нужно.
+
+## 8. Если приложение будет платным
+
+Раздел на будущее: для CursorLang ничего из этого не нужно.
+
+### Нужен ли для этого Company
+
+Не обязательно. Платёжный профиль доступен обоим типам аккаунта, и запрета
+продавать с Individual в документации нет ([FAQ по управлению
+аккаунтом][account-faq]). Критерий Individual против Company описывает характер
+деятельности ([типы аккаунтов][account-types]), а не техническую возможность
+брать деньги.
+
+Практический вывод: заводить ИП **ради требований Microsoft** не нужно. Оно
+скорее понадобится по местному праву — чтобы легально оформлять регулярный доход
+из-за рубежа. Это вопрос к бухгалтеру, а не к Partner Center.
+
+Если всё же понадобится именно Company, помните: сменить тип у существующего
+аккаунта нельзя, придётся заводить второй, а уже опубликованные приложения в
+него штатно не переедут — см. [раздел 9](#9-регистрация-из-казахстана).
+
+### Платёжный и налоговый профиль
+
+Account settings → **Payout and tax profile**
+([порядок настройки][payout-setup]). Без них платное приложение не отправить на
+проверку. Понадобятся банковский счёт и налоговая форма: для тех, кто не платит
+налоги в США, это W-8BEN (частное лицо) или W-8BEN-E (организация)
+([налоговые сведения][tax-info], [налоговые обязанности][tax-details]).
+
+Между Казахстаном и США действует соглашение об избежании двойного
+налогообложения, поэтому удержание с продаж в США ниже базовых 30%. Точную
+ставку показывают при заполнении формы — гадать заранее не нужно.
+
+### Выплаты в Казахстан
+
+Поддерживаются. По [таблице выплат по регионам][payout-regions] для Казахстана
+доступны и Microsoft Store, и PayPal.
+
+| | |
+|---|---|
+| Порог выплаты | 50 USD — ниже суммы накапливаются |
+| PayPal | около одного рабочего дня |
+| ACH/SEPA | два–три рабочих дня |
+| Банковский перевод | семь–десять рабочих дней |
+
+### Доля Microsoft
+
+По действующим условиям — 15% с продаж приложений и 12% с игр; для неигровых
+приложений можно подключить собственную платёжную систему и оставлять себе всю
+выручку. Эти цифры широко приводятся в отраслевых публикациях, но отдельной
+страницы с ними в документации нет: обязывающий документ —
+[соглашение разработчика приложений][developer-agreement], и сверяться нужно с
+его действующей редакцией.
+
+### Что меняется в самом приложении
+
+Почти ничего. Store выдаёт лицензию при покупке и без неё приложение не
+устанавливает, так что проверка в коде не обязательна. При желании лицензию
+читают через [`Windows.Services.Store`][store-api]. Пробный период включается в
+Partner Center и правок в коде не требует.
+
+### Рынки
+
+Для платного приложения список стран стоит выбрать осознанно, а не оставлять
+«все»: где-то автоматически пересчитанная цена окажется неуместной, где-то
+появятся местные налоговые обязанности.
+
+## 9. Регистрация из Казахстана
+
+Раздел для случая, когда разработчик живёт и платит налоги в Казахстане, а
+гражданство у него другое. Юридических советов здесь нет — только то, что
+касается устройства Partner Center.
+
+### Страна аккаунта важнее гражданства
+
+Microsoft смотрит на страну проживания и налогового резидентства, а не на
+паспорт. Страна указывается при регистрации и **после неё не меняется**
+([FAQ по управлению аккаунтом][account-faq]); штатного способа исправить её нет,
+остаётся обращение в поддержку без гарантии результата.
+
+Разница между Казахстаном и Россией существенная и необратимая:
+
+| | Казахстан | Россия |
+|---|---|---|
+| Выплаты Microsoft Store | да | да, но PayPal помечен как приостановленный |
+| Налоговое соглашение с США | [действует][irs-kazakhstan], предел по роялти 10% | [приостановлено с 16 августа 2024][irs-russia] |
+| Удержание в США | 10% при поданной форме W-8BEN | 30%, льготные ставки не применяются |
+
+Данные о выплатах — из [таблицы выплат по регионам][payout-regions], налоговые —
+из перечня [действующих соглашений США][irs-treaties].
+
+### Документ и автозаполнение профиля
+
+Регистрация Individual идёт через снимок документа и селфи, после чего профиль
+**заполняется данными из документа**. Отсюда практическое правило: если есть
+казахстанский документ — вид на жительство или удостоверение личности, —
+верифицироваться лучше по нему. Если под рукой только иностранный паспорт,
+страну и адрес нужно проверить и при необходимости исправить на казахстанские
+**до** подтверждения профиля.
+
+### Имя издателя задаётся один раз
+
+Publisher display name — то, что видят пользователи в карточке приложения, — тоже
+не меняется после регистрации. Если в Store должно значиться название бренда, а
+не ФИО, это задаётся на шаге заполнения профиля. Переименование потом — только
+через поддержку, с вероятным ответом «заводите новый аккаунт».
+
+### Приложения между аккаунтами не переносятся
+
+Штатной процедуры передачи приложения из одного аккаунта в другой в Partner
+Center нет. Значит, если позже завести отдельный Company-аккаунт под платный
+продукт, ранее опубликованное бесплатное приложение останется на первом.
+
+Для бесплатной утилиты это не проблема: она продолжит работать и обновляться там,
+где опубликована. Но переиграть решение задним числом не выйдет, поэтому выбор
+имени издателя и страны стоит сделать вдумчиво с первого раза.
+
+### Порядок действий
+
+1. Начать с https://storedeveloper.microsoft.com — иначе откроется старый платный
+ процесс.
+2. Выбрать **Individual**, если зарегистрированного юридического лица или ИП нет.
+3. Верифицироваться по казахстанскому документу, если он есть.
+4. Проверить, что страна в профиле — Казахстан, а адрес казахстанский.
+5. Проверить имя издателя: оно останется навсегда.
+6. Платёжный и налоговый профиль пропустить — для бесплатного приложения он не
+ нужен.
+
+Когда дойдёт до платного приложения, добавится форма
+[W-8BEN][w8ben] с указанием Казахстана как страны налогового резидентства и ИИН в
+поле иностранного налогового номера. Сертификат налогового резидентства РК
+Microsoft не запрашивает, но он подтверждает право на ставку 10%, если вопрос
+возникнет.
+
+## Источники
+
+- [Открытие аккаунта разработчика][open-account]
+- [Типы аккаунтов разработчика][account-types]
+- [Бесплатная регистрация для компаний][free-company]
+- [Бесплатная регистрация для частных лиц][free-individual]
+- [FAQ по управлению аккаунтом][account-faq]
+- [Резервирование имени приложения MSIX][reserve-name]
+- [Управление резервированием имён][manage-names]
+- [Создание заявки для приложения MSIX][create-submission]
+- [Цены и доступность][price-availability]
+- [Устранение ошибок отправки MSIX][submission-errors]
+- [Подготовка к упаковке программы рабочего стола][prepare-package]
+- [Правила Microsoft Store][policies]
+- [Настройка платёжного и налогового профиля][payout-setup]
+- [Выплаты по регионам][payout-regions]
+- [Налоговые сведения][tax-info]
+- [Налоговые обязанности][tax-details]
+- [Соглашение разработчика приложений][developer-agreement]
+- [API лицензий и покупок Windows.Services.Store][store-api]
+- [Форма W-8BEN, IRS][w8ben]
+- [Действующие налоговые соглашения США, IRS][irs-treaties]
+- [Соглашение США — Казахстан, IRS][irs-kazakhstan]
+- [Соглашение США — Россия и его приостановка, IRS][irs-russia]
+
+[open-account]: https://learn.microsoft.com/en-us/windows/apps/publish/partner-center/open-a-developer-account
+[account-types]: https://learn.microsoft.com/en-us/windows/apps/publish/partner-center/partner-center-developer-account
+[free-company]: https://learn.microsoft.com/en-us/windows/apps/publish/whats-new-company-developer
+[free-individual]: https://learn.microsoft.com/en-us/windows/apps/publish/whats-new-individual-developer
+[account-faq]: https://learn.microsoft.com/en-us/windows/apps/publish/faq/manage-your-account
+[reserve-name]: https://learn.microsoft.com/en-us/windows/apps/publish/publish-your-app/msix/reserve-your-apps-name
+[manage-names]: https://learn.microsoft.com/en-us/windows/apps/publish/partner-center/msix/manage-app-name-reservations
+[create-submission]: https://learn.microsoft.com/en-us/windows/apps/publish/publish-your-app/msix/create-app-submission
+[price-availability]: https://learn.microsoft.com/en-us/windows/apps/publish/publish-your-app/msix/price-and-availability
+[submission-errors]: https://learn.microsoft.com/en-us/windows/apps/publish/publish-your-app/msix/resolve-submission-errors
+[prepare-package]: https://learn.microsoft.com/en-us/windows/msix/desktop/desktop-to-uwp-prepare
+[policies]: https://learn.microsoft.com/en-us/windows/apps/publish/store-policies
+[payout-setup]: https://learn.microsoft.com/en-us/partner-center/account-settings/set-up-your-payout-account
+[payout-regions]: https://learn.microsoft.com/en-us/partner-center/marketplace-offers/payment-thresholds-methods-timeframes
+[tax-info]: https://learn.microsoft.com/en-us/partner-center/marketplace-offers/tax-information-for-commercial-marketplace
+[tax-details]: https://learn.microsoft.com/en-us/partner-center/marketplace-offers/tax-details-marketplace
+[developer-agreement]: https://learn.microsoft.com/en-us/legal/windows/agreements/app-developer-agreement
+[store-api]: https://learn.microsoft.com/en-us/windows/uwp/monetize/in-app-purchases-and-trials
+[w8ben]: https://www.irs.gov/forms-pubs/about-form-w-8-ben
+[irs-treaties]: https://www.irs.gov/businesses/international-businesses/united-states-income-tax-treaties-a-to-z
+[irs-kazakhstan]: https://www.irs.gov/businesses/international-businesses/kazakhstan-tax-treaty-documents
+[irs-russia]: https://www.irs.gov/businesses/international-businesses/russia-tax-treaty-documents
diff --git a/Packaging/build-installer.ps1 b/Packaging/build-installer.ps1
new file mode 100644
index 0000000..61445a2
--- /dev/null
+++ b/Packaging/build-installer.ps1
@@ -0,0 +1,163 @@
+<#
+.SYNOPSIS
+ Builds the CursorLang installer for handing the application round outside
+ the Store.
+
+.DESCRIPTION
+ Nothing has to be installed beyond the .NET SDK: WiX arrives as a NuGet
+ package, the same way makeappx does for the MSIX build.
+
+ Nobody signs the result, so Windows warns about an unknown publisher and the
+ user has to insist. Short of a certificate from a trusted authority there is
+ no way round that, and it is the reason this installer is meant for people who
+ already know where the file came from.
+
+ The application is published with its own copy of .NET: Windows carries no
+ runtime of its own, and a self-contained build is tied to x64.
+
+.PARAMETER Install
+ Runs the installer once it is built, to see the thing through the way a user
+ would.
+
+.EXAMPLE
+ # Build and run it, to see what a user sees
+ pwsh -File Packaging\build-installer.ps1 -Install
+
+.EXAMPLE
+ # What a release needs
+ pwsh -File Packaging\build-installer.ps1 -Version 1.0.1
+#>
+[CmdletBinding()]
+param(
+ # Three numbers: unlike the Store, an installer keeps no place for a fourth
+ [string] $Version = '1.0.0',
+
+ [switch] $Install,
+
+ [string] $OutputPath
+)
+
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+
+$root = $PSScriptRoot
+$repository = Split-Path -Parent $root
+$agentProject = Join-Path $repository 'CursorLang.Agent\CursorLang.Agent.csproj'
+$settingsProject = Join-Path $repository 'CursorLang.Settings\CursorLang.Settings.csproj'
+$installerProject = Join-Path $root 'Installer\CursorLang.wixproj'
+$icon = Join-Path $repository 'CursorLang.Core\Resources\CursorLang.ico'
+$license = Join-Path $repository 'LICENSE'
+
+if (-not $OutputPath) { $OutputPath = Join-Path $repository 'artifacts' }
+$stagingRoot = Join-Path $OutputPath 'installer-staging'
+$installersPath = Join-Path $OutputPath 'installers'
+
+if ($Version -notmatch '^\d+\.\d+\.\d+$') {
+ throw "The version '$Version' does not fit: an installer is versioned by three numbers, for example 1.0.0."
+}
+
+if (-not (Test-Path $icon)) {
+ throw "The icon is missing from '$icon'. Git LFS may have left a pointer in its place: run git lfs pull."
+}
+
+function Invoke-Tool {
+ <#
+ .SYNOPSIS
+ Runs a program and fails the build if it returned an error.
+ .DESCRIPTION
+ A wrapper of our own is needed because PowerShell does not treat the
+ failure of an external program as an error and quietly moves on.
+ #>
+ param(
+ [string] $Path,
+ [string[]] $Arguments
+ )
+
+ & $Path @Arguments
+ if ($LASTEXITCODE -ne 0) {
+ throw "'$([System.IO.Path]::GetFileName($Path))' exited with code $LASTEXITCODE."
+ }
+}
+
+function Write-LicenseRtf {
+ <#
+ .SYNOPSIS
+ Turns the plain-text LICENSE into the RTF the wizard needs.
+ .DESCRIPTION
+ The licence page of a Windows Installer wizard reads RTF and nothing
+ else, and keeping a second copy of the licence in the repository would
+ mean keeping the two in step by hand. The conversion is as plain as it
+ looks: the text carries no formatting to preserve.
+ #>
+ param([string] $Path)
+
+ $text = (Get-Content $license -Raw) -replace '\\', '\\\\' -replace '{', '\{' -replace '}', '\}'
+ $body = ($text -split "`r?`n") -join '\par' + "`r`n"
+
+ Set-Content -Path $Path -Value "{\rtf1\ansi\deff0{\fonttbl{\f0 Segoe UI;}}\fs18 $body}" -Encoding ASCII
+}
+
+Remove-Item $stagingRoot -Recurse -Force -ErrorAction SilentlyContinue
+New-Item -ItemType Directory -Path $installersPath -Force | Out-Null
+
+$licenseRtf = Join-Path $stagingRoot 'License.rtf'
+New-Item -ItemType Directory -Path $stagingRoot -Force | Out-Null
+Write-LicenseRtf -Path $licenseRtf
+
+Write-Host 'Building...' -ForegroundColor Cyan
+
+$staging = Join-Path $stagingRoot 'x64'
+
+# Both halves go into one folder: each of them looks for the other beside
+# itself — the tray menu opens the settings window, and the settings window
+# registers the agent for startup
+foreach ($half in @($agentProject, $settingsProject)) {
+ Invoke-Tool -Path 'dotnet' -Arguments @(
+ 'publish', $half,
+ '--configuration', 'Release',
+ '--runtime', 'win-x64',
+ '--self-contained', 'true',
+ "-p:Version=$Version",
+ '--output', $staging,
+ '--nologo'
+ )
+}
+
+# Debug symbols only make the download bigger; they are of no use to anyone
+# who installs the application
+Get-ChildItem $staging -Recurse -Filter '*.pdb' | Remove-Item -Force
+
+$installer = Join-Path $installersPath "CursorLang-$Version-x64.msi"
+
+Invoke-Tool -Path 'dotnet' -Arguments @(
+ 'build', $installerProject,
+ '--configuration', 'Release',
+ # The architecture of the package is the architecture of what goes in it:
+ # a self-contained build fits nothing else
+ '-p:InstallerPlatform=x64',
+ # Handed over one by one rather than as a ready list of constants: a
+ # semicolon in a property value is where MSBuild stops reading it. The
+ # project gathers them into DefineConstants itself
+ "-p:CursorLangVersion=$Version",
+ "-p:PayloadDir=$staging",
+ "-p:IconFile=$icon",
+ "-p:LicenseFile=$licenseRtf",
+ "-p:OutputPath=$installersPath\",
+ "-p:OutputName=CursorLang-$Version-x64",
+ '--nologo'
+)
+
+if ($Install) {
+ Write-Host "Running $([System.IO.Path]::GetFileName($installer))..." -ForegroundColor Cyan
+
+ # Started the way a user would, with the wizard showing: the point of -Install
+ # is to see what the person on the other end sees
+ Start-Process 'msiexec.exe' -ArgumentList '/i', "`"$installer`"" -Wait
+}
+
+Write-Host ''
+Write-Host 'Done.' -ForegroundColor Green
+Write-Host " $installer"
+
+Write-Host ''
+Write-Host ' Nobody signed it, so Windows warns about an unknown publisher.'