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 @@
+
+