Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
55 changes: 38 additions & 17 deletions .github/workflows/desktop-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -120,13 +138,16 @@ 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
if: github.event_name == 'workflow_dispatch'
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion src/SignsOfAI.Cli/SignsOfAI.Cli.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<PackAsTool>true</PackAsTool>
<ToolCommandName>signsofai</ToolCommandName>
<PackageId>SignsOfAI.Cli</PackageId>
<Version>0.8.0</Version>
<Version>0.8.1</Version>
<Authors>Pedro Hernández (PeopleWorks)</Authors>
<Company>PeopleWorks</Company>
<Description>CLI to detect the signs of AI writing (English &amp; Spanish) and recommend fixes — lint your prose in CI.</Description>
Expand Down
2 changes: 1 addition & 1 deletion src/SignsOfAI.Core/SignsOfAI.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

<!-- Publishable as a library so others can embed the engine. -->
<PackageId>SignsOfAI.Core</PackageId>
<Version>0.8.0</Version>
<Version>0.8.1</Version>
<Authors>Pedro Hernández (PeopleWorks)</Authors>
<Company>PeopleWorks</Company>
<Description>The engine behind Signs of AI Writing: detects the stylometric tells of AI-generated text (English &amp; Spanish) — overused vocabulary, rhetorical crutches, syntactic tells and burstiness — and returns an actionable fix for each finding. No external dependencies.</Description>
Expand Down
74 changes: 74 additions & 0 deletions src/SignsOfAI.Desktop/installer/SignsOfAI.iss
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions src/SignsOfAI.Mcp/.mcp/server.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{
"$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": [
{
"registryType": "nuget",
"registryBaseUrl": "https://api.nuget.org/v3/index.json",
"identifier": "SignsOfAI.Mcp",
"version": "0.8.0",
"version": "0.8.1",
"transport": {
"type": "stdio"
},
Expand Down
2 changes: 1 addition & 1 deletion src/SignsOfAI.Mcp/SignsOfAI.Mcp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<PackAsTool>true</PackAsTool>
<ToolCommandName>signsofai-mcp</ToolCommandName>
<PackageId>SignsOfAI.Mcp</PackageId>
<Version>0.8.0</Version>
<Version>0.8.1</Version>
<Authors>Pedro Hernández (PeopleWorks)</Authors>
<Company>PeopleWorks</Company>
<Description>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.</Description>
Expand Down
13 changes: 10 additions & 3 deletions src/SignsOfAI.UI/Pages/Download.razor
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,16 @@
else
{
<section class="card dl-get">
<a class="dl-cta" href="@DesktopRelease.ZipUrl">
@* 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. *@
<a class="dl-cta" href="@DesktopRelease.SetupUrl">
<Icon Name="download" />
<span class="dl-cta-main">@L.F("dl.get", DesktopRelease.Version)</span>
<span class="dl-cta-sub">@L["dl.for"]</span>
</a>
<p class="hint sub">
<a href="@DesktopRelease.ZipUrl">@L["dl.zip.link"]</a>
·
<a href="@DesktopRelease.ReleaseUrl" target="_blank" rel="noopener">@L["dl.notes"]</a>
·
<a href="@DesktopRelease.AllReleasesUrl" target="_blank" rel="noopener">@L["dl.all"]</a>
Expand Down Expand Up @@ -100,9 +104,12 @@ else
<p>@L.M("dl.warn.body")</p>
<ol class="dl-steps">
<li>@L["dl.warn.step1"]</li>
<li>@L["dl.warn.step2"]</li>
<li>@L.M("dl.warn.step3")</li>
<li>@L.M("dl.warn.step2")</li>
<li>@L["dl.warn.step3"]</li>
</ol>
@* 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. *@
<p class="hint">@L.M("dl.verify")</p>
<p class="hint">@L.M("dl.zip.body")</p>
</section>

Expand Down
15 changes: 12 additions & 3 deletions src/SignsOfAI.UI/Services/DesktopRelease.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,20 @@ namespace SignsOfAI.UI.Services;
public static class DesktopRelease
{
/// <summary>The published version, without the <c>desktop-v</c> prefix its tag carries.</summary>
public const string Version = "0.8.0";
public const string Version = "0.8.1";

/// <summary>
/// The .zip itself, so the button downloads rather than starting a scavenger hunt through a
/// release page. Interpolated from <see cref="Version"/> 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 <see cref="Version"/>, like the zip.
/// </summary>
public const string SetupUrl =
$"https://github.com/peopleworks/SignsofAI/releases/download/desktop-v{Version}/SignsOfAI-Desktop-{Version}-win-x64-Setup.exe";

/// <summary>
/// The .zip itself, for anyone who would rather not run an installer: extract anywhere, delete the
/// folder to remove it. Interpolated from <see cref="Version"/> so the two cannot disagree.
/// </summary>
public const string ZipUrl =
$"https://github.com/peopleworks/SignsofAI/releases/download/desktop-v{Version}/SignsOfAI-Desktop-{Version}-win-x64.zip";
Expand Down
16 changes: 9 additions & 7 deletions src/SignsOfAI.UI/wwwroot/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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}.",
Expand Down Expand Up @@ -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 <strong>not code-signed</strong>a certificate is the one thing this project has no funding for. SmartScreen therefore says <em>Windows protected your PC</em> 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 <code>SignsOfAI.Desktop.exe</code>: on the warning, <em>More info</em> → <em>Run anyway</em>.",
"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 <strong>not code-signed</strong>, and neither is the app inside it: a certificate is the one thing this project has no funding for. SmartScreen therefore says <em>Windows protected your PC</em> 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: <em>More info</em> → <em>Run anyway</em>.",
"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",
Expand Down Expand Up @@ -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 — <strong>{0}</strong> rules in English and <strong>{1}</strong> 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, <code>Get-FileHash</code>."
}
Loading
Loading