Skip to content

Commit 2adfa29

Browse files
authored
Merge branch 'main' into changeset-release/main
2 parents 8ea9b99 + 4111dcf commit 2adfa29

6 files changed

Lines changed: 275 additions & 47 deletions

File tree

.github/workflows/desktop-release.yml

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ jobs:
4646
working-directory: apps/desktop
4747
run: node --import tsx scripts/stage-runtime.ts
4848

49-
# On a desktop-v* tag, --publish always creates or updates the draft-or-release
50-
# for that tag; contents: write makes GITHUB_TOKEN sufficient.
49+
# On a desktop-v* tag, --publish always creates or updates the release
50+
# in pythinker-desktop-releases with the GitHub App token below.
5151
# Without Developer ID signing secrets, electron-builder publishes an
5252
# ad-hoc/self-signed app. macOS auto-update will not accept unsigned updates,
5353
# but this still proves packaging and the feed shape.
@@ -73,11 +73,29 @@ jobs:
7373
echo 'CSC_IDENTITY_AUTO_DISCOVERY=false' >> "$GITHUB_ENV"
7474
echo 'No macOS signing certificate configured; building unsigned.'
7575
fi
76+
- name: Mint releases-repo token
77+
id: releases_token
78+
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # pinned from v3.2.0
79+
with:
80+
app-id: ${{ secrets.DESKTOP_RELEASES_APP_ID }}
81+
private-key: ${{ secrets.DESKTOP_RELEASES_APP_PRIVATE_KEY }}
82+
owner: PyModel
83+
repositories: pythinker-desktop-releases
7684
- name: Package and publish desktop release
7785
working-directory: apps/desktop
7886
run: pnpm exec electron-builder --mac dmg zip --publish always
7987
env:
80-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
88+
GH_TOKEN: ${{ steps.releases_token.outputs.token }}
89+
90+
- name: Verify macOS packaged update configuration
91+
shell: bash
92+
run: |
93+
app_bundle="$(find apps/desktop/dist -maxdepth 2 -type d -name '*.app' -print -quit)"
94+
if [ -z "$app_bundle" ]; then
95+
echo 'macOS application bundle not found' >&2
96+
exit 1
97+
fi
98+
test -f "$app_bundle/Contents/Resources/app-update.yml"
8199
82100
- name: Upload macOS artifacts for manual runs
83101
if: github.event_name == 'workflow_dispatch'
@@ -154,11 +172,23 @@ jobs:
154172
value="${!input:-}"
155173
if [ -n "$value" ]; then printf '%s<<__EOF__\n%s\n__EOF__\n' "$name" "$value" >> "$GITHUB_ENV"; fi
156174
done
175+
- name: Mint releases-repo token
176+
id: releases_token
177+
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # pinned from v3.2.0
178+
with:
179+
app-id: ${{ secrets.DESKTOP_RELEASES_APP_ID }}
180+
private-key: ${{ secrets.DESKTOP_RELEASES_APP_PRIVATE_KEY }}
181+
owner: PyModel
182+
repositories: pythinker-desktop-releases
157183
- name: Package and publish desktop release
158184
working-directory: apps/desktop
159185
run: node --import tsx scripts/package-win.ts --publish always
160186
env:
161-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
187+
GH_TOKEN: ${{ steps.releases_token.outputs.token }}
188+
189+
- name: Verify Windows packaged update configuration
190+
shell: bash
191+
run: test -f apps/desktop/dist/win-unpacked/resources/app-update.yml
162192

163193
- name: Upload Windows artifacts for manual runs
164194
if: github.event_name == 'workflow_dispatch'

apps/desktop/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
{
3333
"provider": "github",
3434
"owner": "PyModel",
35-
"repo": "pythinker-code"
35+
"repo": "pythinker-desktop-releases",
36+
"releaseType": "release"
3637
}
3738
],
3839
"afterPack": "./scripts/verify-packaged-runtime.ts",

apps/desktop/tests/packaging-config.spec.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ interface DesktopPackage {
2828
readonly perMachine: boolean
2929
readonly shortcutName: string
3030
}
31+
readonly publish: readonly {
32+
readonly owner: string
33+
readonly provider: string
34+
readonly releaseType: string
35+
readonly repo: string
36+
}[]
3137
readonly productName: string
3238
readonly win: {
3339
readonly icon: string
@@ -58,6 +64,15 @@ describe('desktop packaging configuration', () => {
5864
expect(desktopPackage.build.productName).toBe('Pythinker')
5965
})
6066

67+
it('publishes updates to the dedicated desktop release repository', () => {
68+
expect(desktopPackage.build.publish).toContainEqual({
69+
owner: 'PyModel',
70+
provider: 'github',
71+
releaseType: 'release',
72+
repo: 'pythinker-desktop-releases',
73+
})
74+
})
75+
6176
it('keeps desktop download URLs derived from their published release version', () => {
6277
const siteSource = readFileSync(resolve(repositoryRoot, 'apps/site/src/App.vue'), 'utf8')
6378
const desktopVersionMatch = siteSource.match(/const DESKTOP_VERSION = '([^']+)'/)

apps/site/src/App.vue

Lines changed: 65 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { onMounted, onUnmounted, ref } from 'vue';
33
import AgentLoop from './components/AgentLoop.vue';
44
import InstallCommand from './components/InstallCommand.vue';
55
import LegacyDownloadsPopup from './components/LegacyDownloadsPopup.vue';
6+
import ParticleField from './components/ParticleField.vue';
67
import PythinkerMascot from './components/PythinkerMascot.vue';
78
89
const version = __PYTHINKER_VERSION__;
@@ -20,6 +21,18 @@ const desktopDownloads = {
2021
windows: `${DESKTOP_RELEASE_BASE}/Pythinker-${DESKTOP_VERSION}-x64-Setup.exe`,
2122
};
2223
24+
const isWindows = /Win/i.test(navigator.platform || navigator.userAgent);
25+
const heroDownloads = (isWindows
26+
? [
27+
{ id: 'windows', label: 'Download for Windows', note: 'Windows x64', icon: '/brand/windows11.svg', href: desktopDownloads.windows },
28+
{ id: 'mac', label: 'Download for macOS', note: 'Apple Silicon', icon: '/brand/apple.svg', href: desktopDownloads.mac },
29+
]
30+
: [
31+
{ id: 'mac', label: 'Download for macOS', note: 'Apple Silicon', icon: '/brand/apple.svg', href: desktopDownloads.mac },
32+
{ id: 'windows', label: 'Download for Windows', note: 'Windows x64', icon: '/brand/windows11.svg', href: desktopDownloads.windows },
33+
]);
34+
const heroDownloadsPage = `https://github.com/PyModel/pythinker-code/releases/tag/v${DESKTOP_VERSION}`;
35+
2336
const installRows = [
2437
['macOS / Linux', 'curl -fsSL https://code.pythinker.com/pythinker-code/install.sh | bash', '/brand/apple.svg'],
2538
['Windows (PowerShell)', 'irm https://code.pythinker.com/pythinker-code/install.ps1 | iex', '/brand/windows11.svg'],
@@ -211,6 +224,7 @@ onUnmounted(() => {
211224
</script>
212225
213226
<template>
227+
<ParticleField />
214228
<nav class="site-nav" aria-label="Primary navigation">
215229
<div class="container nav-inner">
216230
<a href="/" class="nav-brand"><PythinkerMascot :width="26" :height="32" /><span>Pythinker Code</span></a>
@@ -245,27 +259,29 @@ onUnmounted(() => {
245259
<PythinkerMascot class="hero-mascot" :width="164" :height="205" />
246260
<h1>Pythinker Code</h1>
247261
<p class="hero-accent">Think first, then code.</p>
248-
<p class="hero-lead">An open-source AI engineering agent for your terminal. It reads your repo, edits files, runs commands, and iterates until the job is done.</p>
249-
<a
250-
class="hero-npm-badge"
251-
href="https://www.npmjs.com/package/@pymodel/pythinker-code"
252-
target="_blank"
253-
rel="noopener"
254-
>
255-
<img
256-
src="https://img.shields.io/npm/dm/%40pymodel%2Fpythinker-code?style=flat&logo=npm&logoColor=white&color=2b89ff&label=downloads"
257-
alt="npm downloads per month for @pymodel/pythinker-code"
258-
width="161"
259-
height="20"
260-
loading="lazy"
261-
/>
262-
</a>
262+
<p class="hero-lead">Pythinker Code is an open-source AI engineering agent. It reads your repo, edits files, runs commands, and iterates until the job is done &mdash; in a native desktop app, your terminal, or VS Code.</p>
263+
<div class="hero-download">
264+
<div class="hero-download-actions">
265+
<a
266+
v-for="(download, index) in heroDownloads"
267+
:key="download.id"
268+
:class="index === 0 ? 'button button-primary' : 'button button-secondary'"
269+
:href="download.href"
270+
rel="noopener"
271+
>
272+
<img :src="download.icon" alt="" aria-hidden="true" class="button-platform-icon" />
273+
{{ download.label }}
274+
</a>
275+
</div>
276+
<p class="hero-download-note">
277+
{{ heroDownloads.map((download) => download.note).join(' · ') }} ·
278+
<a :href="heroDownloadsPage" target="_blank" rel="noopener">All downloads</a>
279+
</p>
280+
</div>
281+
<p class="hero-install-label">Or install the CLI</p>
263282
<div class="hero-install">
264283
<InstallCommand />
265284
</div>
266-
<div class="hero-download-milestone">
267-
<LegacyDownloadsPopup />
268-
</div>
269285
<p class="hero-caption">Free and open source. MIT licensed. macOS, Linux, and Windows.</p>
270286
</div>
271287
</header>
@@ -531,6 +547,10 @@ onUnmounted(() => {
531547
</div>
532548
</div>
533549
</section>
550+
551+
<div class="container milestone-footnote">
552+
<LegacyDownloadsPopup />
553+
</div>
534554
</main>
535555
536556
<footer class="site-footer">
@@ -728,40 +748,46 @@ onUnmounted(() => {
728748
line-height: 1.5;
729749
}
730750
751+
.hero-download {
752+
margin-top: 24px;
753+
}
754+
755+
.hero-download-actions {
756+
display: flex;
757+
flex-wrap: wrap;
758+
justify-content: center;
759+
gap: 10px;
760+
}
761+
731762
.hero-install {
732763
max-width: 720px;
733764
margin: 10px auto 0;
734765
}
735766
736-
.hero-download-milestone {
767+
.milestone-footnote {
737768
display: flex;
738-
margin-top: 14px;
739769
justify-content: center;
770+
margin-block: 48px;
740771
}
741772
742-
.hero-npm-badge {
743-
display: inline-flex;
744-
margin-top: 14px;
745-
border-radius: var(--radius);
746-
opacity: 0.85;
747-
transition: opacity 0.2s ease;
773+
.hero-download-note,
774+
.hero-install-label,
775+
.hero-caption {
776+
color: var(--ink-subtle);
777+
font-size: 13px;
748778
}
749779
750-
.hero-npm-badge:hover {
751-
opacity: 1;
780+
.hero-download-note {
781+
margin-top: 10px;
782+
line-height: 1.38;
752783
}
753784
754-
/* ponytail: width:auto lets the badge grow as the download count does; the width attr only reserves space */
755-
.hero-npm-badge img {
756-
display: block;
757-
width: auto;
758-
height: 20px;
785+
.hero-install-label {
786+
margin-top: 20px;
759787
}
760788
761789
.hero-caption {
762790
margin-top: 12px;
763-
color: var(--ink-subtle);
764-
font-size: 13px;
765791
line-height: 1.38;
766792
}
767793
@@ -830,11 +856,13 @@ onUnmounted(() => {
830856
height: 16px;
831857
}
832858
833-
.desktop-showcase-actions .button {
859+
.desktop-showcase-actions .button,
860+
.hero-download-actions .button {
834861
gap: 8px;
835862
}
836863
837-
.desktop-showcase-actions .button-primary .button-platform-icon {
864+
.desktop-showcase-actions .button-primary .button-platform-icon,
865+
.hero-download-actions .button-primary .button-platform-icon {
838866
filter: brightness(0) invert(1);
839867
}
840868

0 commit comments

Comments
 (0)