From b3a4fd2be1d69fbf3949572f6ef4db27e719eb8e Mon Sep 17 00:00:00 2001 From: Cody Seibert Date: Fri, 12 Dec 2025 22:42:43 -0500 Subject: [PATCH] feat: introduce marketing mode and update sidebar display - Added a new configuration flag `IS_MARKETING` to toggle marketing mode. - Updated the sidebar component to conditionally display the marketing URL when in marketing mode. - Refactored event type naming for consistency in the sidebar logic. - Cleaned up formatting in the HttpApiClient for improved readability. --- apps/app/src/components/layout/sidebar.tsx | 13 +- apps/app/src/config/app-config.ts | 6 + apps/app/src/lib/http-api-client.ts | 132 ++++++++++++++++----- 3 files changed, 120 insertions(+), 31 deletions(-) create mode 100644 apps/app/src/config/app-config.ts diff --git a/apps/app/src/components/layout/sidebar.tsx b/apps/app/src/components/layout/sidebar.tsx index 82e46044..8476483c 100644 --- a/apps/app/src/components/layout/sidebar.tsx +++ b/apps/app/src/components/layout/sidebar.tsx @@ -4,6 +4,7 @@ import { useState, useMemo, useEffect, useCallback, useRef } from "react"; import { cn } from "@/lib/utils"; import { useAppStore, formatShortcut } from "@/store/app-store"; import { CoursePromoBadge } from "@/components/ui/course-promo-badge"; +import { IS_MARKETING } from "@/config/app-config"; import { FolderOpen, Plus, @@ -366,7 +367,7 @@ export function Sidebar() { if ( event.type === "auto_mode_feature_complete" || event.type === "auto_mode_error" || - event.type === "auto_mode_feature_started" + event.type === "auto_mode_feature_start" ) { const fetchRunningAgentsCount = async () => { try { @@ -853,7 +854,15 @@ export function Sidebar() { sidebarOpen ? "hidden lg:block" : "hidden" )} > - Automaker + {IS_MARKETING ? ( + <> + https://automaker.app + + ) : ( + <> + Automaker + + )} {/* Bug Report Button */} diff --git a/apps/app/src/config/app-config.ts b/apps/app/src/config/app-config.ts new file mode 100644 index 00000000..6755a303 --- /dev/null +++ b/apps/app/src/config/app-config.ts @@ -0,0 +1,6 @@ +/** + * Marketing mode flag + * When set to true, displays "https://automaker.app" with "maker" in theme color + */ + +export const IS_MARKETING = process.env.NEXT_PUBLIC_IS_MARKETING === "true"; diff --git a/apps/app/src/lib/http-api-client.ts b/apps/app/src/lib/http-api-client.ts index ba0f4c6b..ed5377bb 100644 --- a/apps/app/src/lib/http-api-client.ts +++ b/apps/app/src/lib/http-api-client.ts @@ -33,7 +33,6 @@ import type { } from "@/types/electron"; import { getGlobalFileBrowser } from "@/contexts/file-browser-context"; - // Server URL - configurable via environment variable const getServerUrl = (): string => { if (typeof window !== "undefined") { @@ -43,7 +42,6 @@ const getServerUrl = (): string => { return "http://localhost:3008"; }; - // Get API key from environment variable const getApiKey = (): string | null => { if (typeof window !== "undefined") { @@ -76,7 +74,10 @@ export class HttpApiClient implements ElectronAPI { } private connectWebSocket(): void { - if (this.isConnecting || (this.ws && this.ws.readyState === WebSocket.OPEN)) { + if ( + this.isConnecting || + (this.ws && this.ws.readyState === WebSocket.OPEN) + ) { return; } @@ -103,7 +104,10 @@ export class HttpApiClient implements ElectronAPI { callbacks.forEach((cb) => cb(data.payload)); } } catch (error) { - console.error("[HttpApiClient] Failed to parse WebSocket message:", error); + console.error( + "[HttpApiClient] Failed to parse WebSocket message:", + error + ); } }; @@ -130,7 +134,10 @@ export class HttpApiClient implements ElectronAPI { } } - private subscribeToEvent(type: EventType, callback: EventCallback): () => void { + private subscribeToEvent( + type: EventType, + callback: EventCallback + ): () => void { if (!this.eventCallbacks.has(type)) { this.eventCallbacks.set(type, new Set()); } @@ -196,7 +203,9 @@ export class HttpApiClient implements ElectronAPI { return result.status === "ok" ? "pong" : "error"; } - async openExternalLink(url: string): Promise<{ success: boolean; error?: string }> { + async openExternalLink( + url: string + ): Promise<{ success: boolean; error?: string }> { // Open in new tab window.open(url, "_blank", "noopener,noreferrer"); return { success: true }; @@ -301,7 +310,9 @@ export class HttpApiClient implements ElectronAPI { async getPath(name: string): Promise { // Server provides data directory if (name === "userData") { - const result = await this.get<{ dataDir: string }>("/api/health/detailed"); + const result = await this.get<{ dataDir: string }>( + "/api/health/detailed" + ); return result.dataDir || "/data"; } return `/data/${name}`; @@ -313,7 +324,12 @@ export class HttpApiClient implements ElectronAPI { mimeType: string, projectPath?: string ): Promise { - return this.post("/api/fs/save-image", { data, filename, mimeType, projectPath }); + return this.post("/api/fs/save-image", { + data, + filename, + mimeType, + projectPath, + }); } async saveBoardBackground( @@ -464,14 +480,19 @@ export class HttpApiClient implements ElectronAPI { output?: string; }> => this.post("/api/setup/auth-claude"), - authCodex: (apiKey?: string): Promise<{ + authCodex: ( + apiKey?: string + ): Promise<{ success: boolean; requiresManualAuth?: boolean; command?: string; error?: string; }> => this.post("/api/setup/auth-codex", { apiKey }), - storeApiKey: (provider: string, apiKey: string): Promise<{ + storeApiKey: ( + provider: string, + apiKey: string + ): Promise<{ success: boolean; error?: string; }> => this.post("/api/setup/store-api-key", { provider, apiKey }), @@ -483,7 +504,9 @@ export class HttpApiClient implements ElectronAPI { hasGoogleKey: boolean; }> => this.get("/api/setup/api-keys"), - configureCodexMcp: (projectPath: string): Promise<{ + configureCodexMcp: ( + projectPath: string + ): Promise<{ success: boolean; configPath?: string; error?: string; @@ -516,8 +539,11 @@ export class HttpApiClient implements ElectronAPI { this.post("/api/features/get", { projectPath, featureId }), create: (projectPath: string, feature: Feature) => this.post("/api/features/create", { projectPath, feature }), - update: (projectPath: string, featureId: string, updates: Partial) => - this.post("/api/features/update", { projectPath, featureId, updates }), + update: ( + projectPath: string, + featureId: string, + updates: Partial + ) => this.post("/api/features/update", { projectPath, featureId, updates }), delete: (projectPath: string, featureId: string) => this.post("/api/features/delete", { projectPath, featureId }), getAgentOutput: (projectPath: string, featureId: string) => @@ -534,8 +560,16 @@ export class HttpApiClient implements ElectronAPI { this.post("/api/auto-mode/stop-feature", { featureId }), status: (projectPath?: string) => this.post("/api/auto-mode/status", { projectPath }), - runFeature: (projectPath: string, featureId: string, useWorktrees?: boolean) => - this.post("/api/auto-mode/run-feature", { projectPath, featureId, useWorktrees }), + runFeature: ( + projectPath: string, + featureId: string, + useWorktrees?: boolean + ) => + this.post("/api/auto-mode/run-feature", { + projectPath, + featureId, + useWorktrees, + }), verifyFeature: (projectPath: string, featureId: string) => this.post("/api/auto-mode/verify-feature", { projectPath, featureId }), resumeFeature: (projectPath: string, featureId: string) => @@ -559,7 +593,10 @@ export class HttpApiClient implements ElectronAPI { commitFeature: (projectPath: string, featureId: string) => this.post("/api/auto-mode/commit-feature", { projectPath, featureId }), onEvent: (callback: (event: AutoModeEvent) => void) => { - return this.subscribeToEvent("auto-mode:event", callback as EventCallback); + return this.subscribeToEvent( + "auto-mode:event", + callback as EventCallback + ); }, }; @@ -578,7 +615,11 @@ export class HttpApiClient implements ElectronAPI { getDiffs: (projectPath: string, featureId: string) => this.post("/api/worktree/diffs", { projectPath, featureId }), getFileDiff: (projectPath: string, featureId: string, filePath: string) => - this.post("/api/worktree/file-diff", { projectPath, featureId, filePath }), + this.post("/api/worktree/file-diff", { + projectPath, + featureId, + filePath, + }), }; // Git API @@ -596,20 +637,30 @@ export class HttpApiClient implements ElectronAPI { stop: () => this.post("/api/suggestions/stop"), status: () => this.get("/api/suggestions/status"), onEvent: (callback: (event: SuggestionsEvent) => void) => { - return this.subscribeToEvent("suggestions:event", callback as EventCallback); + return this.subscribeToEvent( + "suggestions:event", + callback as EventCallback + ); }, }; // Spec Regeneration API specRegeneration: SpecRegenerationAPI = { - create: (projectPath: string, projectOverview: string, generateFeatures?: boolean) => + create: ( + projectPath: string, + projectOverview: string, + generateFeatures?: boolean + ) => this.post("/api/spec-regeneration/create", { projectPath, projectOverview, generateFeatures, }), generate: (projectPath: string, projectDefinition: string) => - this.post("/api/spec-regeneration/generate", { projectPath, projectDefinition }), + this.post("/api/spec-regeneration/generate", { + projectPath, + projectDefinition, + }), generateFeatures: (projectPath: string) => this.post("/api/spec-regeneration/generate-features", { projectPath }), stop: () => this.post("/api/spec-regeneration/stop"), @@ -656,7 +707,10 @@ export class HttpApiClient implements ElectronAPI { // Agent API agent = { - start: (sessionId: string, workingDirectory?: string): Promise<{ + start: ( + sessionId: string, + workingDirectory?: string + ): Promise<{ success: boolean; messages?: Message[]; error?: string; @@ -668,9 +722,16 @@ export class HttpApiClient implements ElectronAPI { workingDirectory?: string, imagePaths?: string[] ): Promise<{ success: boolean; error?: string }> => - this.post("/api/agent/send", { sessionId, message, workingDirectory, imagePaths }), + this.post("/api/agent/send", { + sessionId, + message, + workingDirectory, + imagePaths, + }), - getHistory: (sessionId: string): Promise<{ + getHistory: ( + sessionId: string + ): Promise<{ success: boolean; messages?: Message[]; isRunning?: boolean; @@ -690,17 +751,24 @@ export class HttpApiClient implements ElectronAPI { // Templates API templates = { - clone: (repoUrl: string, projectName: string, parentDir: string): Promise<{ + clone: ( + repoUrl: string, + projectName: string, + parentDir: string + ): Promise<{ success: boolean; projectPath?: string; projectName?: string; error?: string; - }> => this.post("/api/templates/clone", { repoUrl, projectName, parentDir }), + }> => + this.post("/api/templates/clone", { repoUrl, projectName, parentDir }), }; // Sessions API sessions = { - list: (includeArchived?: boolean): Promise<{ + list: ( + includeArchived?: boolean + ): Promise<{ success: boolean; sessions?: SessionListItem[]; error?: string; @@ -730,13 +798,19 @@ export class HttpApiClient implements ElectronAPI { ): Promise<{ success: boolean; error?: string }> => this.put(`/api/sessions/${sessionId}`, { name, tags }), - archive: (sessionId: string): Promise<{ success: boolean; error?: string }> => + archive: ( + sessionId: string + ): Promise<{ success: boolean; error?: string }> => this.post(`/api/sessions/${sessionId}/archive`, {}), - unarchive: (sessionId: string): Promise<{ success: boolean; error?: string }> => + unarchive: ( + sessionId: string + ): Promise<{ success: boolean; error?: string }> => this.post(`/api/sessions/${sessionId}/unarchive`, {}), - delete: (sessionId: string): Promise<{ success: boolean; error?: string }> => + delete: ( + sessionId: string + ): Promise<{ success: boolean; error?: string }> => this.httpDelete(`/api/sessions/${sessionId}`), }; }