diff --git a/packages/go/.env.example b/packages/go/.env.example new file mode 100644 index 000000000..cf4f0e23d --- /dev/null +++ b/packages/go/.env.example @@ -0,0 +1,2 @@ +NEXT_PUBLIC_DEFAULT_WORKSPACE_ID= +NEXT_PUBLIC_DEFAULT_WORKSPACE_TOKEN= diff --git a/packages/go/.gitignore b/packages/go/.gitignore new file mode 100644 index 000000000..6421a7222 --- /dev/null +++ b/packages/go/.gitignore @@ -0,0 +1,15 @@ +# Override root .gitignore exclusions needed for this package +!lib/ +!hooks/ + +node_modules/ +.next/ +.env.local +dist/ +out/ +*.dmg +*.AppImage +*.deb +*.exe +*.snap +package-lock.json diff --git a/packages/go/README.md b/packages/go/README.md new file mode 100644 index 000000000..c0102d027 --- /dev/null +++ b/packages/go/README.md @@ -0,0 +1,32 @@ +# OpenAgents Go + +Electron desktop app for OpenAgents workspaces. + + + + + +## Development + +```bash +cd packages/go +npm install +npm run electron:dev +``` + +## Build & Install + +```bash +npm version patch +npm run electron:build +``` + +Output in `dist/` — `.dmg` (macOS), `.exe` (Windows), `.AppImage` (Linux). + +macOS: open the `.dmg`, drag "OpenAgents Go" to Applications. + +## TODO + +- [ ] Restore last active thread on relaunch +- [ ] Persist light/dark mode preference across sessions +- [ ] Default sidebar to collapsed state diff --git a/packages/go/app/layout.tsx b/packages/go/app/layout.tsx new file mode 100644 index 000000000..696edbecd --- /dev/null +++ b/packages/go/app/layout.tsx @@ -0,0 +1,53 @@ +import type { Metadata, Viewport } from 'next'; +import { Inter } from 'next/font/google'; +import { ThemeProvider } from 'next-themes'; +import { Toaster } from '@/components/ui/sonner'; +import { AuthProvider } from '@/lib/auth-context'; +import { OpenAgentsAuthProvider } from '@/lib/openagents-auth-context'; +import { ElectronInit } from '@/components/layout/electron-init'; +import '@/styles/globals.css'; + +const inter = Inter({ subsets: ['latin'] }); + +export const metadata: Metadata = { + title: 'OpenAgents Go', + description: 'OpenAgents desktop app', + icons: { + icon: [ + { url: '/favicon.ico', sizes: 'any' }, + { url: '/favicon-16x16.png', sizes: '16x16', type: 'image/png' }, + { url: '/favicon-32x32.png', sizes: '32x32', type: 'image/png' }, + ], + apple: '/apple-touch-icon.png', + }, + manifest: '/site.webmanifest', +}; + +export const viewport: Viewport = { + width: 'device-width', + initialScale: 1, + maximumScale: 1, + viewportFit: 'cover', +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + +
+Workspace not found.
+
+ Add ?token=your_workspace_token to the URL.
+
+ {isSwitching ? 'Select a workspace or paste a new URL.' : 'Paste your workspace URL to get started.'} +
+Recent Workspaces
+{fullUrl}
+
+ Get a workspace URL by running{' '}
+ openagents workspace create
+
{agent.agentName}
++ {agent.agentType && {agent.agentType} · } + {isOnline + ? 'Online' + : agent.lastHeartbeatAt + ? `Last seen ${timeAgo(agent.lastHeartbeatAt)}` + : 'Offline'} +
+No browser tabs
+Open a tab or ask an agent to browse
++ {ctx?.name || tab.title || truncateUrl(tab.url)} +
++ {truncateUrl(tab.url)} + {tab.lastActiveAt && ` · ${timeAgo(tab.lastActiveAt)}`} +
+{ctx.name}
++ {ctx.domain || 'no domain'} + {ctx.lastUsedAt && ` · ${timeAgo(ctx.lastUsedAt)}`} + {' · idle'} +
++ {tab.title || truncateUrl(tab.url)} +
++ {truncateUrl(tab.url)} + {' · '} + {(tab.createdBy || 'unknown').replace(/^(openagents:|human:)/, '')} + {tab.lastActiveAt && ` · ${timeAgo(tab.lastActiveAt)}`} +
+Select a browser tab
+Choose a tab from the list or open a new one
+{tab.title || 'Untitled'}
+{tab.url}
+No screenshot available
+
+ Select a thread
+Choose a thread from the list or create a new one.
+No agents online
+ )} + > + ); + })()} ++ Send a message to start chatting with your agent. + Messages will appear here in real time. +
+
+ {parsed.args}
+
+ )}
+
+ {renderMentions(children, agentNames)}
+ ), + blockquote: ({ children }) => ( ++ {children} ++ ), + hr: () =>
+ {children}
+
+ );
+ }
+ return (
+
+ {children}
+
+ );
+ },
+ pre: ({ children }) => (
+
+ {children}
+
+ ),
+
+ // Links
+ a: ({ href, children }) => (
+
+ {children}
+
+ ),
+
+ // Inline
+ strong: ({ children }) => {children},
+ del: ({ children }) => + Use this token to connect agents to your workspace. Keep it private — anyone with this token can join agents. +
++ Open a terminal and run the graphical setup. It will guide you through connecting an agent to this workspace. +
++ $ + openagents ++ +
+ Don't have the CLI?{' '}
+ Install with{' '}
+ pip install openagents
+
+ The CLI supports these agent types. You'll select one during the guided setup. +
+{entry.description}
++ {files.length === 0 ? 'No files yet' : search ? 'No matches' : 'Empty folder'} +
++ {files.length === 0 + ? 'Upload a file or ask an agent to create one' + : search + ? 'Try a different search term' + : 'Upload files here or go back'} +
+{entry.name}
++ {entry.fileCount} {entry.fileCount === 1 ? 'file' : 'files'} +
+{displayName}
++ {formatSize(file.size)} · {(file.uploadedBy || 'unknown').replace(/^(openagents:|human:)/, '')} + {file.createdAt && ` · ${timeAgo(file.createdAt)}`} +
+Select a file
+Choose a file from the list to preview.
+{file.filename.split('/').pop() || file.filename}
++ {file.filename.includes('/') && ( + {file.filename.split('/').slice(0, -1).join('/')}/ · + )} + {formatSize(file.size)} · {file.contentType || 'unknown'} · {(file.uploadedBy || 'unknown').replace(/^(openagents:|human:)/, '')} +
+
+ {content}
+
+ ) : (
+ Preview not available for this file type
+ ++ Agents ({onlineCount}/{recentAgents.length}) +
++ Collaboration +
++ Actions +
+
+
+ {workspace?.name || 'Workspace'} +
+ )} +{workspace?.slug || ''}
+No threads found
+ ) : ( + filteredSessions.map((session) => { + const isInGrid = topSessions.some((t) => t.sessionId === session.sessionId); + const preview = lastMessageBySession[session.sessionId]; + const activityMs = session.lastEventAt; + const displayTime = activityMs + ? timeAgo(new Date(activityMs).toISOString()) + : session.createdAt ? timeAgo(session.createdAt) : ''; + return ( + + ); + }) + )} +New Session
+No sessions yet
+ ) : ( + sessions.map((session) => ( ++ {agent.workingDir} +
+ )} +{preview}
++ {preview} +
+No results found
+Try a different search term
+ > + ) : ( + <> +No threads yet
+Create a thread to start chatting
+ > + )} ++ {preview} +
+