From 813ede2ddee165f4fb081b419e2a903b3b5643ac Mon Sep 17 00:00:00 2001 From: Cody Seibert Date: Fri, 12 Dec 2025 10:39:12 -0500 Subject: [PATCH] chore: update .gitignore and add R2 upload script for artifact management - Added .automaker/images/ to .gitignore to prevent tracking of generated images. - Deleted obsolete agent-output.md and feature.json files related to removed "Agent Tools" feature. - Introduced a new script for uploading build artifacts to R2 and updating releases.json. - Enhanced GitHub Actions workflow to include artifact uploads for different platforms. --- .../agent-output.md | 33 -- .../feature.json | 14 - .github/scripts/upload-to-r2.js | 148 ++++++ .github/workflows/release.yml | 59 +++ .gitignore | 1 + apps/app/src/app/page.tsx | 25 +- apps/app/src/lib/electron.ts | 17 +- apps/marketing/public/index.html | 129 ++++++ apps/marketing/public/releases.html | 422 ++++++++++++++++++ apps/server/src/services/auto-mode-service.ts | 10 +- 10 files changed, 797 insertions(+), 61 deletions(-) delete mode 100644 .automaker/features/feature-1765525566442-wsdrtodkc/agent-output.md delete mode 100644 .automaker/features/feature-1765525566442-wsdrtodkc/feature.json create mode 100644 .github/scripts/upload-to-r2.js create mode 100644 apps/marketing/public/releases.html diff --git a/.automaker/features/feature-1765525566442-wsdrtodkc/agent-output.md b/.automaker/features/feature-1765525566442-wsdrtodkc/agent-output.md deleted file mode 100644 index d192970c..00000000 --- a/.automaker/features/feature-1765525566442-wsdrtodkc/agent-output.md +++ /dev/null @@ -1,33 +0,0 @@ -The navigation now shows only: -- **Project** section: Kanban Board, Agent Runner -- **Tools** section: Spec Editor, Context, AI Profiles - -The "Agent Tools" item has been successfully removed from the side navigation. - -## Summary - -I've successfully removed "Agent Tools" from the side navigation by making the following changes: - -### Files Modified: - -1. **`apps/app/src/components/layout/sidebar.tsx`** - - Removed the navigation item for "Agent Tools" (id: "tools") - - Removed the unused `Wrench` icon import from lucide-react - -2. **`apps/app/src/components/ui/keyboard-map.tsx`** - - Removed `tools: "Agent Tools"` from `SHORTCUT_LABELS` - - Removed `tools: "navigation"` from `SHORTCUT_CATEGORIES` - -3. **`apps/app/src/store/app-store.ts`** - - Removed `"tools"` from the `ViewMode` type union - - Removed `tools: string` from the `KeyboardShortcuts` interface - - Removed `tools: "T"` from `DEFAULT_KEYBOARD_SHORTCUTS` - -4. **`apps/app/src/app/page.tsx`** - - Removed the import for `AgentToolsView` - - Removed the `case "tools": return ` from the view switch statement - -### Notes: -- The `agent-tools-view.tsx` component file was **not** deleted, as the user only requested to remove it from the navigation. The component remains in the codebase in case it's needed later. -- The keyboard shortcut "T" is now available for other features if needed. -- All lint errors found are pre-existing in the codebase and not related to these changes. \ No newline at end of file diff --git a/.automaker/features/feature-1765525566442-wsdrtodkc/feature.json b/.automaker/features/feature-1765525566442-wsdrtodkc/feature.json deleted file mode 100644 index 3505b448..00000000 --- a/.automaker/features/feature-1765525566442-wsdrtodkc/feature.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "category": "Uncategorized", - "description": "for now, just remove agent tools from the side navigation, they are useless", - "steps": [], - "status": "completed", - "images": [], - "imagePaths": [], - "skipTests": true, - "model": "opus", - "thinkingLevel": "none", - "id": "feature-1765525566442-wsdrtodkc", - "startedAt": "2025-12-12T07:46:08.507Z", - "updatedAt": "2025-12-12T07:48:34.967Z" -} \ No newline at end of file diff --git a/.github/scripts/upload-to-r2.js b/.github/scripts/upload-to-r2.js new file mode 100644 index 00000000..336069cb --- /dev/null +++ b/.github/scripts/upload-to-r2.js @@ -0,0 +1,148 @@ +const { S3Client, PutObjectCommand, GetObjectCommand } = require('@aws-sdk/client-s3'); +const fs = require('fs'); +const path = require('path'); + +const s3Client = new S3Client({ + region: 'auto', + endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`, + credentials: { + accessKeyId: process.env.R2_ACCESS_KEY_ID, + secretAccessKey: process.env.R2_SECRET_ACCESS_KEY, + }, +}); + +const BUCKET = process.env.R2_BUCKET_NAME; +const PUBLIC_URL = process.env.R2_PUBLIC_URL; +const VERSION = process.env.RELEASE_VERSION; +const GITHUB_REPO = process.env.GITHUB_REPOSITORY; + +async function fetchExistingReleases() { + try { + const response = await s3Client.send(new GetObjectCommand({ + Bucket: BUCKET, + Key: 'releases.json', + })); + const body = await response.Body.transformToString(); + return JSON.parse(body); + } catch (error) { + if (error.name === 'NoSuchKey' || error.$metadata?.httpStatusCode === 404) { + console.log('No existing releases.json found, creating new one'); + return { latestVersion: null, releases: [] }; + } + throw error; + } +} + +async function uploadFile(localPath, r2Key, contentType) { + const fileBuffer = fs.readFileSync(localPath); + const stats = fs.statSync(localPath); + + await s3Client.send(new PutObjectCommand({ + Bucket: BUCKET, + Key: r2Key, + Body: fileBuffer, + ContentType: contentType, + })); + + console.log(`Uploaded: ${r2Key} (${stats.size} bytes)`); + return stats.size; +} + +function findArtifacts(dir, pattern) { + if (!fs.existsSync(dir)) return []; + const files = fs.readdirSync(dir); + return files.filter(f => pattern.test(f)).map(f => path.join(dir, f)); +} + +async function main() { + const artifactsDir = 'artifacts'; + + // Find all artifacts + const artifacts = { + windows: findArtifacts( + path.join(artifactsDir, 'windows-builds'), + /\.exe$/ + ), + macos: findArtifacts( + path.join(artifactsDir, 'macos-builds'), + /-x64\.dmg$/ + ), + macosArm: findArtifacts( + path.join(artifactsDir, 'macos-builds'), + /-arm64\.dmg$/ + ), + linux: findArtifacts( + path.join(artifactsDir, 'linux-builds'), + /\.AppImage$/ + ), + }; + + console.log('Found artifacts:'); + for (const [platform, files] of Object.entries(artifacts)) { + console.log(` ${platform}: ${files.length > 0 ? files.map(f => path.basename(f)).join(', ') : 'none'}`); + } + + // Upload each artifact to R2 + const assets = {}; + const contentTypes = { + windows: 'application/x-msdownload', + macos: 'application/x-apple-diskimage', + macosArm: 'application/x-apple-diskimage', + linux: 'application/x-executable', + }; + + for (const [platform, files] of Object.entries(artifacts)) { + if (files.length === 0) { + console.warn(`Warning: No artifact found for ${platform}`); + continue; + } + + // Use the first matching file for each platform + const localPath = files[0]; + const filename = path.basename(localPath); + const r2Key = `releases/${VERSION}/${filename}`; + const size = await uploadFile(localPath, r2Key, contentTypes[platform]); + + assets[platform] = { + url: `${PUBLIC_URL}/releases/${VERSION}/${filename}`, + filename, + size, + arch: platform === 'macosArm' ? 'arm64' : 'x64', + }; + } + + // Fetch and update releases.json + const releasesData = await fetchExistingReleases(); + + const newRelease = { + version: VERSION, + date: new Date().toISOString(), + assets, + githubReleaseUrl: `https://github.com/${GITHUB_REPO}/releases/tag/${VERSION}`, + }; + + // Remove existing entry for this version if re-running + releasesData.releases = releasesData.releases.filter(r => r.version !== VERSION); + + // Prepend new release + releasesData.releases.unshift(newRelease); + releasesData.latestVersion = VERSION; + + // Upload updated releases.json + await s3Client.send(new PutObjectCommand({ + Bucket: BUCKET, + Key: 'releases.json', + Body: JSON.stringify(releasesData, null, 2), + ContentType: 'application/json', + CacheControl: 'public, max-age=60', + })); + + console.log('Successfully updated releases.json'); + console.log(`Latest version: ${VERSION}`); + console.log(`Total releases: ${releasesData.releases.length}`); +} + +main().catch(err => { + console.error('Failed to upload to R2:', err); + process.exit(1); +}); diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cb2ad857..5135a73b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,10 +19,13 @@ jobs: include: - os: macos-latest name: macOS + artifact-name: macos-builds - os: windows-latest name: Windows + artifact-name: windows-builds - os: ubuntu-latest name: Linux + artifact-name: linux-builds runs-on: ${{ matrix.os }} @@ -78,3 +81,59 @@ jobs: prerelease: false env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload macOS artifacts for R2 + if: matrix.os == 'macos-latest' + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.artifact-name }} + path: apps/app/dist/*.dmg + retention-days: 1 + + - name: Upload Windows artifacts for R2 + if: matrix.os == 'windows-latest' + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.artifact-name }} + path: apps/app/dist/*.exe + retention-days: 1 + + - name: Upload Linux artifacts for R2 + if: matrix.os == 'ubuntu-latest' + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.artifact-name }} + path: apps/app/dist/*.AppImage + retention-days: 1 + + upload-to-r2: + needs: build-and-release + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Install AWS SDK + run: npm install @aws-sdk/client-s3 + + - name: Upload to R2 and update releases.json + env: + R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }} + R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} + R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} + R2_BUCKET_NAME: ${{ secrets.R2_BUCKET_NAME }} + R2_PUBLIC_URL: ${{ secrets.R2_PUBLIC_URL }} + RELEASE_VERSION: ${{ github.event.inputs.version || github.ref_name }} + GITHUB_REPOSITORY: ${{ github.repository }} + run: node .github/scripts/upload-to-r2.js diff --git a/.gitignore b/.gitignore index 42ad7b8f..da0f3b8e 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ node_modules/ dist/ .next/ node_modules +.automaker/images/ diff --git a/apps/app/src/app/page.tsx b/apps/app/src/app/page.tsx index fdc71ca5..e2260c86 100644 --- a/apps/app/src/app/page.tsx +++ b/apps/app/src/app/page.tsx @@ -17,7 +17,13 @@ import { useSetupStore } from "@/store/setup-store"; import { getElectronAPI, isElectron } from "@/lib/electron"; export default function Home() { - const { currentView, setCurrentView, setIpcConnected, theme, currentProject } = useAppStore(); + const { + currentView, + setCurrentView, + setIpcConnected, + theme, + currentProject, + } = useAppStore(); const { isFirstRun, setupComplete } = useSetupStore(); const [isMounted, setIsMounted] = useState(false); const [streamerPanelOpen, setStreamerPanelOpen] = useState(false); @@ -28,7 +34,11 @@ export default function Home() { const activeElement = document.activeElement; if (activeElement) { const tagName = activeElement.tagName.toLowerCase(); - if (tagName === "input" || tagName === "textarea" || tagName === "select") { + if ( + tagName === "input" || + tagName === "textarea" || + tagName === "select" + ) { return; } if (activeElement.getAttribute("contenteditable") === "true") { @@ -80,7 +90,9 @@ export default function Home() { }); if (isMounted && isFirstRun && !setupComplete) { - console.log("[Setup Flow] Redirecting to setup wizard (first run, not complete)"); + console.log( + "[Setup Flow] Redirecting to setup wizard (first run, not complete)" + ); setCurrentView("setup"); } else if (isMounted && setupComplete) { console.log("[Setup Flow] Setup already complete, showing normal view"); @@ -201,7 +213,10 @@ export default function Home() { return (
-
+
{renderView()}
@@ -215,7 +230,7 @@ export default function Home() { {/* Hidden streamer panel - opens with "\" key, pushes content */}
diff --git a/apps/app/src/lib/electron.ts b/apps/app/src/lib/electron.ts index 759e0784..32f723c3 100644 --- a/apps/app/src/lib/electron.ts +++ b/apps/app/src/lib/electron.ts @@ -415,7 +415,10 @@ export interface ElectronAPI { onAuthProgress?: (callback: (progress: any) => void) => () => void; }; agent?: { - start: (sessionId: string, workingDirectory?: string) => Promise<{ + start: ( + sessionId: string, + workingDirectory?: string + ) => Promise<{ success: boolean; messages?: Message[]; error?: string; @@ -463,9 +466,15 @@ export interface ElectronAPI { name?: string, tags?: string[] ) => Promise<{ success: boolean; error?: string }>; - archive: (sessionId: string) => Promise<{ success: boolean; error?: string }>; - unarchive: (sessionId: string) => Promise<{ success: boolean; error?: string }>; - delete: (sessionId: string) => Promise<{ success: boolean; error?: string }>; + archive: ( + sessionId: string + ) => Promise<{ success: boolean; error?: string }>; + unarchive: ( + sessionId: string + ) => Promise<{ success: boolean; error?: string }>; + delete: ( + sessionId: string + ) => Promise<{ success: boolean; error?: string }>; }; } diff --git a/apps/marketing/public/index.html b/apps/marketing/public/index.html index b15b92f9..3f9a6336 100644 --- a/apps/marketing/public/index.html +++ b/apps/marketing/public/index.html @@ -298,6 +298,65 @@ .feature-card:nth-child(4) { animation-delay: 0.4s; } .feature-card:nth-child(5) { animation-delay: 0.5s; } .feature-card:nth-child(6) { animation-delay: 0.6s; } + + /* Download Buttons */ + .download-section { + margin-top: 2.5rem; + } + + .download-label { + color: var(--text-muted); + font-size: 0.9rem; + margin-bottom: 1rem; + } + + .download-buttons { + display: flex; + gap: 1rem; + justify-content: center; + flex-wrap: wrap; + } + + .btn-download { + padding: 0.75rem 1.5rem; + border-radius: 0.5rem; + text-decoration: none; + font-weight: 600; + transition: all 0.3s; + display: inline-flex; + align-items: center; + gap: 0.5rem; + background: rgba(30, 41, 59, 0.8); + color: var(--text); + border: 1px solid rgba(148, 163, 184, 0.2); + font-size: 0.9rem; + } + + .btn-download:hover { + background: rgba(99, 102, 241, 0.2); + border-color: var(--primary); + transform: translateY(-2px); + } + + .btn-download svg { + width: 20px; + height: 20px; + } + + .download-subtitle { + color: var(--text-muted); + font-size: 0.9rem; + margin-top: 1rem; + } + + .download-subtitle a { + color: var(--primary); + text-decoration: none; + } + + .download-subtitle a:hover { + text-decoration: underline; + } @@ -307,6 +366,7 @@ @@ -321,6 +381,27 @@ View on GitHub Get Started + + @@ -408,5 +489,53 @@

+ + \ No newline at end of file diff --git a/apps/marketing/public/releases.html b/apps/marketing/public/releases.html new file mode 100644 index 00000000..f7aa114b --- /dev/null +++ b/apps/marketing/public/releases.html @@ -0,0 +1,422 @@ + + + + + + Releases - Automaker + + + +
+ +
+ +
+ + +
+
+
+
Loading releases...
+
+
+
+
+ + + + + + diff --git a/apps/server/src/services/auto-mode-service.ts b/apps/server/src/services/auto-mode-service.ts index 77eb42ec..8015de91 100644 --- a/apps/server/src/services/auto-mode-service.ts +++ b/apps/server/src/services/auto-mode-service.ts @@ -193,8 +193,8 @@ export class AutoModeService { throw new Error(`Feature ${featureId} not found`); } - // Update feature status to in-progress - await this.updateFeatureStatus(projectPath, featureId, "in-progress"); + // Update feature status to in_progress + await this.updateFeatureStatus(projectPath, featureId, "in_progress"); // Build the prompt const prompt = this.buildFeaturePrompt(feature); @@ -202,8 +202,8 @@ export class AutoModeService { // Run the agent await this.runAgent(workDir, featureId, prompt, abortController); - // Mark as completed - await this.updateFeatureStatus(projectPath, featureId, "completed"); + // Mark as waiting_approval for user review + await this.updateFeatureStatus(projectPath, featureId, "waiting_approval"); this.emitAutoModeEvent("auto_mode_feature_complete", { featureId, @@ -226,7 +226,7 @@ export class AutoModeService { errorMessage.includes("authentication_failed"); console.error(`[AutoMode] Feature ${featureId} failed:`, error); - await this.updateFeatureStatus(projectPath, featureId, "failed"); + await this.updateFeatureStatus(projectPath, featureId, "backlog"); this.emitAutoModeEvent("auto_mode_error", { featureId, error: errorMessage,