diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 8e2dbaa..c9db176 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -2,7 +2,7 @@ "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "signs-of-ai", "description": "Measure a text for the signs of AI writing and read back the evidence — offline, EN/ES, with the tool's own false-positive rate attached.", - "version": "0.8.0", + "version": "0.8.1", "author": { "name": "Pedro Hernández — PeopleWorks", "url": "https://github.com/peopleworks" diff --git a/.github/workflows/desktop-release.yml b/.github/workflows/desktop-release.yml index 46c7598..dd177e8 100644 --- a/.github/workflows/desktop-release.yml +++ b/.github/workflows/desktop-release.yml @@ -91,27 +91,45 @@ jobs: -o "staging/${{ steps.version.outputs.name }}" ` --nologo - - name: Zip and checksum + # The installer is built from the same staging folder the zip is, so the two can never carry + # different files. Inno Setup is not guaranteed on the runner image, so it is installed when it + # is missing rather than assumed. The version is passed in, never written into the script. + - name: Build the installer + shell: pwsh + run: | + $iscc = Join-Path ${env:ProgramFiles(x86)} 'Inno Setup 6\ISCC.exe' + if (-not (Test-Path $iscc)) { + choco install innosetup --no-progress -y + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + } + $staging = (Resolve-Path "staging/${{ steps.version.outputs.name }}").Path + & $iscc "/DAppVersion=${{ steps.version.outputs.version }}" "/DStaging=$staging" "/O$PWD" ` + src/SignsOfAI.Desktop/installer/SignsOfAI.iss + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + + - name: Package and checksum id: package shell: pwsh run: | $name = "${{ steps.version.outputs.name }}" Compress-Archive -Path "staging/$name" -DestinationPath "$name.zip" -CompressionLevel Optimal - $hash = (Get-FileHash "$name.zip" -Algorithm SHA256).Hash.ToLower() - # The sidecar travels with the release; the summary is for pasting into the notes, which - # have carried the checksum inline since desktop-v0.1.0. - "sha256 $hash" | Set-Content "$name.zip.sha256" -NoNewline - "sha256=$hash" >> $env:GITHUB_OUTPUT - $mb = [math]::Round((Get-Item "$name.zip").Length / 1MB) - @( - "### $name.zip" - "" - "- **$mb MB**" - "- ``sha256 $hash``" - "" - "Paste the checksum line into the release notes." - ) | Add-Content $env:GITHUB_STEP_SUMMARY + # The sidecars travel with the release; the summary is for pasting into the notes, which + # have carried the checksum inline since desktop-v0.1.0. Both artifacts get one: the + # installer is the one most people will run, so it is the one most worth checking. + foreach ($file in @("$name-Setup.exe", "$name.zip")) { + $hash = (Get-FileHash $file -Algorithm SHA256).Hash.ToLower() + "sha256 $hash" | Set-Content "$file.sha256" -NoNewline + $mb = [math]::Round((Get-Item $file).Length / 1MB) + @( + "### $file" + "" + "- **$mb MB**" + "- ``sha256 $hash``" + "" + ) | Add-Content $env:GITHUB_STEP_SUMMARY + } + "Paste both checksum lines into the release notes." | Add-Content $env:GITHUB_STEP_SUMMARY - name: Attach to the release if: github.event_name == 'release' @@ -120,7 +138,8 @@ jobs: GH_TOKEN: ${{ github.token }} run: | $name = "${{ steps.version.outputs.name }}" - gh release upload $env:GITHUB_REF_NAME "$name.zip" "$name.zip.sha256" --clobber + gh release upload $env:GITHUB_REF_NAME ` + "$name-Setup.exe" "$name-Setup.exe.sha256" "$name.zip" "$name.zip.sha256" --clobber # Dispatch runs produce something to download and check by hand before tagging for real. - name: Keep the build as a run artifact @@ -128,5 +147,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: ${{ steps.version.outputs.name }} - path: ${{ steps.version.outputs.name }}.zip + path: | + ${{ steps.version.outputs.name }}-Setup.exe + ${{ steps.version.outputs.name }}.zip retention-days: 7 diff --git a/README.md b/README.md index a6ce595..fb91452 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Claude Code, Codex, Gemini CLI and Cursor, in one line: `npx skills add peopleworks/SignsofAI -g`. It edits by the same rules this engine scores by, and it never invents a number. -**[Download the Windows app →](https://github.com/peopleworks/SignsofAI/releases?q=desktop&expanded=true)** — the same tool in a window. Nothing to install alongside it: the .NET runtime is bundled. +**[Download the Windows app →](https://peopleworks.github.io/SignsofAI/download)** — the same tool in a window. An installer that needs no administrator rights, with the .NET runtime bundled; a .zip too, if you would rather not. ![Signs of AI Writing analyzing text live: the score climbs as AI tells accumulate, then every tell is highlighted with a fix](Docs/screenshots/analyze-live.gif) diff --git a/src/SignsOfAI.Cli/SignsOfAI.Cli.csproj b/src/SignsOfAI.Cli/SignsOfAI.Cli.csproj index f608ad5..f53187f 100644 --- a/src/SignsOfAI.Cli/SignsOfAI.Cli.csproj +++ b/src/SignsOfAI.Cli/SignsOfAI.Cli.csproj @@ -13,7 +13,7 @@ true signsofai SignsOfAI.Cli - 0.8.0 + 0.8.1 Pedro Hernández (PeopleWorks) PeopleWorks CLI to detect the signs of AI writing (English & Spanish) and recommend fixes — lint your prose in CI. diff --git a/src/SignsOfAI.Core/SignsOfAI.Core.csproj b/src/SignsOfAI.Core/SignsOfAI.Core.csproj index ffbcbd1..bdf5fdd 100644 --- a/src/SignsOfAI.Core/SignsOfAI.Core.csproj +++ b/src/SignsOfAI.Core/SignsOfAI.Core.csproj @@ -10,7 +10,7 @@ SignsOfAI.Core - 0.8.0 + 0.8.1 Pedro Hernández (PeopleWorks) PeopleWorks The engine behind Signs of AI Writing: detects the stylometric tells of AI-generated text (English & Spanish) — overused vocabulary, rhetorical crutches, syntactic tells and burstiness — and returns an actionable fix for each finding. No external dependencies. diff --git a/src/SignsOfAI.Desktop/installer/SignsOfAI.iss b/src/SignsOfAI.Desktop/installer/SignsOfAI.iss new file mode 100644 index 0000000..6c8cbf1 --- /dev/null +++ b/src/SignsOfAI.Desktop/installer/SignsOfAI.iss @@ -0,0 +1,74 @@ +; The Windows installer for Signs of AI Writing. +; +; It exists because the audience this app is for — a teacher who wants to open a folder of PDFs — +; was being asked to right-click a zip, tick Unblock, run Get-FileHash in PowerShell, extract it, +; and then keep the folder together, because the app's web assets must sit next to the executable. +; An installer does the last three for them and cannot get the folder wrong. +; +; It does NOT make the app trusted. It is unsigned, exactly like the executable inside it, so +; SmartScreen still warns on first run. Only code signing removes that, and the download page says so. +; +; Built by .github/workflows/desktop-release.yml, which passes the two values that change: +; +; ISCC /DAppVersion=0.8.1 /DStaging=staging\SignsOfAI-Desktop-0.8.1-win-x64 SignsOfAI.iss +; +; No version is written in this file. It is already typed in seven places that ReleaseVersionTests +; holds together, and an eighth copy here is how one of them would end up disagreeing. + +#ifndef AppVersion + #error Pass the version on the command line: /DAppVersion=0.8.1 +#endif +#ifndef Staging + #error Pass the publish folder on the command line: /DStaging=path\to\publish +#endif + +[Setup] +; Never change this. It is how Windows recognises a newer installer as the same app to upgrade, +; rather than installing a second copy beside the first. +AppId={{FDD6B2FD-9E64-4C17-BA52-C0BFB387BA85} +AppName=Signs of AI Writing +AppVersion={#AppVersion} +AppVerName=Signs of AI Writing {#AppVersion} +AppPublisher=PeopleWorks +AppPublisherURL=https://github.com/peopleworks/SignsofAI +AppSupportURL=https://github.com/peopleworks/SignsofAI/issues +AppUpdatesURL=https://peopleworks.github.io/SignsofAI/download + +; For the current user only, into their own profile. School laptops are often managed, and a teacher +; who is not an administrator must still be able to install it without asking IT. +PrivilegesRequired=lowest +DefaultDirName={localappdata}\Programs\SignsOfAI +DisableProgramGroupPage=yes +DisableDirPage=auto + +; Windows 10 1809, the oldest the download page promises; 64-bit only, like the build. +MinVersion=10.0.17763 +ArchitecturesAllowed=x64compatible +ArchitecturesInstallIn64BitMode=x64compatible + +OutputBaseFilename=SignsOfAI-Desktop-{#AppVersion}-win-x64-Setup +SetupIconFile=..\appicon.ico +UninstallDisplayIcon={app}\SignsOfAI.Desktop.exe +UninstallDisplayName=Signs of AI Writing +WizardStyle=modern +Compression=lzma2/max +SolidCompression=yes + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" +Name: "spanish"; MessagesFile: "compiler:Languages\Spanish.isl" + +[Tasks] +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked + +[Files] +; The whole publish tree, subfolders included — wwwroot above all. That is the folder the zip told +; people to keep together, and here it cannot be split. +Source: "{#Staging}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs + +[Icons] +Name: "{autoprograms}\Signs of AI Writing"; Filename: "{app}\SignsOfAI.Desktop.exe" +Name: "{autodesktop}\Signs of AI Writing"; Filename: "{app}\SignsOfAI.Desktop.exe"; Tasks: desktopicon + +[Run] +Filename: "{app}\SignsOfAI.Desktop.exe"; Description: "{cm:LaunchProgram,Signs of AI Writing}"; Flags: nowait postinstall skipifsilent diff --git a/src/SignsOfAI.Mcp/.mcp/server.json b/src/SignsOfAI.Mcp/.mcp/server.json index 13c54b6..eba7a25 100644 --- a/src/SignsOfAI.Mcp/.mcp/server.json +++ b/src/SignsOfAI.Mcp/.mcp/server.json @@ -1,7 +1,7 @@ { "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", "name": "io.github.peopleworks/signs-of-ai", - "version": "0.8.0", + "version": "0.8.1", "title": "Signs of AI Writing", "description": "AI-writing detection (EN/ES) that shows the evidence: named tells, hidden characters, citations.", "packages": [ @@ -9,7 +9,7 @@ "registryType": "nuget", "registryBaseUrl": "https://api.nuget.org/v3/index.json", "identifier": "SignsOfAI.Mcp", - "version": "0.8.0", + "version": "0.8.1", "transport": { "type": "stdio" }, diff --git a/src/SignsOfAI.Mcp/SignsOfAI.Mcp.csproj b/src/SignsOfAI.Mcp/SignsOfAI.Mcp.csproj index 6611367..4bddbfa 100644 --- a/src/SignsOfAI.Mcp/SignsOfAI.Mcp.csproj +++ b/src/SignsOfAI.Mcp/SignsOfAI.Mcp.csproj @@ -12,7 +12,7 @@ true signsofai-mcp SignsOfAI.Mcp - 0.8.0 + 0.8.1 Pedro Hernández (PeopleWorks) PeopleWorks Model Context Protocol (MCP) server for Signs of AI Writing. Exposes the engine as tools — analyze text for AI tells, check originality between documents, search the sign catalog, extract distinctive phrases, and (optionally, via the hosted API) measure perplexity and find paraphrases — to Claude Desktop and any MCP client. diff --git a/src/SignsOfAI.UI/Pages/Download.razor b/src/SignsOfAI.UI/Pages/Download.razor index d112919..bf9e03e 100644 --- a/src/SignsOfAI.UI/Pages/Download.razor +++ b/src/SignsOfAI.UI/Pages/Download.razor @@ -55,12 +55,16 @@ else {
- + @* The installer first. The zip stays one click away for anyone who would rather not run an + installer, but it is no longer what a teacher is handed by default. *@ + @L.F("dl.get", DesktopRelease.Version) @L["dl.for"]

+ @L["dl.zip.link"] + · @L["dl.notes"] · @L["dl.all"] @@ -100,9 +104,12 @@ else

@L.M("dl.warn.body")

  1. @L["dl.warn.step1"]
  2. -
  3. @L["dl.warn.step2"]
  4. -
  5. @L.M("dl.warn.step3")
  6. +
  7. @L.M("dl.warn.step2")
  8. +
  9. @L["dl.warn.step3"]
+ @* Checking the file is still worth doing and still explained — it just no longer stands between a + teacher and the app. Before the installer it was step two of three. *@ +

@L.M("dl.verify")

@L.M("dl.zip.body")

diff --git a/src/SignsOfAI.UI/Services/DesktopRelease.cs b/src/SignsOfAI.UI/Services/DesktopRelease.cs index 8dc8974..1c3fad7 100644 --- a/src/SignsOfAI.UI/Services/DesktopRelease.cs +++ b/src/SignsOfAI.UI/Services/DesktopRelease.cs @@ -20,11 +20,20 @@ namespace SignsOfAI.UI.Services; public static class DesktopRelease { /// The published version, without the desktop-v prefix its tag carries. - public const string Version = "0.8.0"; + public const string Version = "0.8.1"; /// - /// The .zip itself, so the button downloads rather than starting a scavenger hunt through a - /// release page. Interpolated from so the two cannot disagree. + /// The installer, which is what the page offers first. It installs for the current user only, so + /// a teacher on a managed laptop needs no administrator — and it cannot split the folder the app + /// needs kept together, which the zip left to the person extracting it. Still unsigned, so it + /// warns exactly as the executable does. Interpolated from , like the zip. + /// + public const string SetupUrl = + $"https://github.com/peopleworks/SignsofAI/releases/download/desktop-v{Version}/SignsOfAI-Desktop-{Version}-win-x64-Setup.exe"; + + /// + /// The .zip itself, for anyone who would rather not run an installer: extract anywhere, delete the + /// folder to remove it. Interpolated from so the two cannot disagree. /// public const string ZipUrl = $"https://github.com/peopleworks/SignsofAI/releases/download/desktop-v{Version}/SignsOfAI-Desktop-{Version}-win-x64.zip"; diff --git a/src/SignsOfAI.UI/wwwroot/i18n/en.json b/src/SignsOfAI.UI/wwwroot/i18n/en.json index 1cc5d56..98db033 100644 --- a/src/SignsOfAI.UI/wwwroot/i18n/en.json +++ b/src/SignsOfAI.UI/wwwroot/i18n/en.json @@ -36,7 +36,7 @@ "dl.h1": "Signs of AI Writing for Windows", "dl.tagline": "The same tool as this page, in a window: it opens the file formats a browser tab cannot, reads a whole folder at once, and measures on your own machine.", "dl.get": "Download version {0}", - "dl.for": "Windows 10 (1809) or newer · 64-bit · about 80 MB · no installer", + "dl.for": "Windows 10 (1809) or newer · 64-bit · under 100 MB · no administrator rights needed", "dl.notes": "Release notes and checksum", "dl.all": "All desktop releases", "dl.running": "You are running the desktop app, version {0}.", @@ -98,11 +98,11 @@ "wa.cta.code": "Read the code", "nav.word": "Word", "dl.warn.title": "Windows will warn you, and it is right to", - "dl.warn.body": "The download is not code-signed — a certificate is the one thing this project has no funding for. SmartScreen therefore says Windows protected your PC the first time you run it, and names an unknown publisher. The warning is doing its job. Check the file instead of clicking through it:", - "dl.warn.step1": "Right-click the downloaded .zip → Properties, and tick Unblock if it is offered.", - "dl.warn.step2": "Compare its SHA-256 against the one printed in the release notes — in PowerShell, Get-FileHash.", - "dl.warn.step3": "Only then extract it and run SignsOfAI.Desktop.exe: on the warning, More infoRun anyway.", - "dl.zip.body": "There is no installer. It is a .zip you extract wherever you like — keep the folder together, because the app reads files next to the executable. To uninstall it, delete the folder.", + "dl.warn.body": "The installer is not code-signed, and neither is the app inside it: a certificate is the one thing this project has no funding for. SmartScreen therefore says Windows protected your PC the first time and names an unknown publisher. The warning is doing its job, and an installer does not change that.", + "dl.warn.step1": "Run the installer you downloaded. It installs for you only, so it needs no administrator rights.", + "dl.warn.step2": "On the warning: More infoRun anyway.", + "dl.warn.step3": "Signs of AI Writing is now in the Start menu. To remove it, uninstall it from Settings → Apps, like any other program.", + "dl.zip.body": "Rather not run an installer? The .zip is still published. Extract it anywhere and keep the folder together, because the app's web files must sit next to the executable. To remove it, delete the folder.", "update.ask": "Check for new versions?", "update.ask.detail": "This app does not update itself. A check asks GitHub which build is newest — no text, no account and no identifier are sent, and nothing is ever downloaded for you. You can change this later on the Windows app page.", "update.yes": "Yes, check", @@ -506,5 +506,7 @@ "skill.rules.title": "The same taxonomy, not a second opinion", "skill.rules.body": "It is a human-readable distillation of the rule packs this build ships — {0} rules in English and {1} in Spanish — so what it edits and what the engine scores are the same catalogue. The Spanish pack is written from scratch, not machine-translated.", "skill.cta.read": "Read the skill", - "skill.cta.catalog": "Browse the rules" + "skill.cta.catalog": "Browse the rules", + "dl.zip.link": "Or the .zip, without an installer", + "dl.verify": "Want to check the file before you run it? Compare its SHA-256 with the one printed in the release notes. In PowerShell, Get-FileHash." } diff --git a/src/SignsOfAI.UI/wwwroot/i18n/es.json b/src/SignsOfAI.UI/wwwroot/i18n/es.json index ec7a861..938613b 100644 --- a/src/SignsOfAI.UI/wwwroot/i18n/es.json +++ b/src/SignsOfAI.UI/wwwroot/i18n/es.json @@ -36,7 +36,7 @@ "dl.h1": "Señales de escritura IA para Windows", "dl.tagline": "La misma herramienta de esta página, en una ventana: abre los formatos que una pestaña no puede, lee una carpeta entera de una vez y mide en tu propia máquina.", "dl.get": "Descargar la versión {0}", - "dl.for": "Windows 10 (1809) o posterior · 64 bits · unos 80 MB · sin instalador", + "dl.for": "Windows 10 (1809) o posterior · 64 bits · menos de 100 MB · sin permisos de administrador", "dl.notes": "Notas de la versión y checksum", "dl.all": "Todas las versiones de escritorio", "dl.running": "Estás usando la app de escritorio, versión {0}.", @@ -98,11 +98,11 @@ "wa.cta.code": "Ver el código", "nav.word": "Word", "dl.warn.title": "Windows te va a avisar, y hace bien", - "dl.warn.body": "La descarga no está firmada — un certificado es justo lo que este proyecto no puede costear. Por eso SmartScreen dice Windows protegió su PC la primera vez y habla de un editor desconocido. El aviso está haciendo su trabajo. Comprueba el archivo en vez de saltártelo:", - "dl.warn.step1": "Clic derecho en el .zip descargado → Propiedades, y marca Desbloquear si aparece.", - "dl.warn.step2": "Compara su SHA-256 con el que está impreso en las notas de la versión — en PowerShell, Get-FileHash.", - "dl.warn.step3": "Solo entonces descomprímelo y ejecuta SignsOfAI.Desktop.exe: en el aviso, Más informaciónEjecutar de todas formas.", - "dl.zip.body": "No hay instalador. Es un .zip que descomprimes donde quieras — mantén la carpeta junta, porque la app lee archivos que están al lado del ejecutable. Para desinstalarla, borra la carpeta.", + "dl.warn.body": "El instalador no está firmado, ni la app que lleva dentro: un certificado es justo lo que este proyecto no puede costear. Por eso SmartScreen dice Windows protegió su PC la primera vez y habla de un editor desconocido. El aviso está haciendo su trabajo, y un instalador no cambia eso.", + "dl.warn.step1": "Ejecuta el instalador que descargaste. Se instala solo para ti, así que no necesita permisos de administrador.", + "dl.warn.step2": "En el aviso: Más informaciónEjecutar de todas formas.", + "dl.warn.step3": "Signs of AI Writing ya está en el menú Inicio. Para quitarlo, desinstálalo desde Configuración → Aplicaciones, como cualquier otro programa.", + "dl.zip.body": "¿Prefieres no ejecutar un instalador? El .zip se sigue publicando. Descomprímelo donde quieras y mantén la carpeta junta, porque los archivos web de la app tienen que estar al lado del ejecutable. Para quitarla, borra la carpeta.", "update.ask": "¿Compruebo si hay versiones nuevas?", "update.ask.detail": "Esta app no se actualiza sola. La comprobación le pregunta a GitHub cuál es la compilación más reciente — no se envía texto, ni cuenta, ni identificador, y nunca se descarga nada por ti. Puedes cambiarlo después en la página de la app de Windows.", "update.yes": "Sí, comprueba", @@ -506,5 +506,7 @@ "skill.rules.title": "La misma taxonomía, no una segunda opinión", "skill.rules.body": "Es una destilación legible de los packs de reglas que trae esta compilación —{0} reglas en inglés y {1} en español—, así que lo que edita y lo que el motor puntúa son el mismo catálogo. El pack español está escrito desde cero, no traducido a máquina.", "skill.cta.read": "Leer el skill", - "skill.cta.catalog": "Ver las reglas" + "skill.cta.catalog": "Ver las reglas", + "dl.zip.link": "O el .zip, sin instalador", + "dl.verify": "¿Quieres comprobar el archivo antes de ejecutarlo? Compara su SHA-256 con el que está impreso en las notas de la versión. En PowerShell, Get-FileHash." }