mirror of
https://github.com/0xWheatyz/handler.git
synced 2026-08-30 08:26:25 +00:00
c3e3d1627e
Settings → Manage now opens the entire admin control surface on the phone, mirroring the web dashboard page for page: - Models: full CRUD for model backends incl. write-only API keys (set/clear), harness selection (claude/pi), enable toggles. - Skills: create, toggle, delete, expand to read SKILL.md, and install-from-prompt driven through the command queue. - Connectors: stdio/http/sse MCP servers with args/env/header parsing. - Plugins: marketplace plugins pinned to their repo. - Permissions: default mode + allow/deny/ask rules over the read-only env baseline. - Repositories: register in git-server or manual mode (incl. the mise-init bootstrap), sync, delete. - Git servers: forge hosts with encrypted tokens and generated deploy keys (public half selectable for copying). - Approvals: record operator approve/reject verdicts per branch. - Shared context: browse and set the cross-agent key/value store. - Users: invite (with shareable invite links), promote/disable, mint reset links, delete — the user-accounts feature that just landed. - Claude login: drive the worker's claude /login flow from the phone. - Account (Settings): who you're signed in as, change password, sign out with server-side session revocation. The connect screen gains the matching gate: email sign-in via /auth/login (session token stored like the legacy env token), first-run setup when the server has zero accounts, forgot-password, and the API-token method as fallback (auto-selected for pre-accounts servers). Memory gains note authoring + deletion via a new reloadMemory hook. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01731mKtVzsfeT4Vi3TvkR48
156 lines
4.6 KiB
TypeScript
156 lines
4.6 KiB
TypeScript
import React from "react";
|
|
import { View } from "react-native";
|
|
import { StatusBar } from "expo-status-bar";
|
|
import {
|
|
SafeAreaProvider,
|
|
useSafeAreaInsets,
|
|
} from "react-native-safe-area-context";
|
|
import {
|
|
useFonts,
|
|
Outfit_400Regular,
|
|
Outfit_500Medium,
|
|
Outfit_600SemiBold,
|
|
Outfit_700Bold,
|
|
Outfit_800ExtraBold,
|
|
} from "@expo-google-fonts/outfit";
|
|
import {
|
|
Figtree_400Regular,
|
|
Figtree_500Medium,
|
|
Figtree_600SemiBold,
|
|
Figtree_700Bold,
|
|
Figtree_400Regular_Italic,
|
|
} from "@expo-google-fonts/figtree";
|
|
import {
|
|
SplineSansMono_400Regular,
|
|
SplineSansMono_500Medium,
|
|
SplineSansMono_600SemiBold,
|
|
} from "@expo-google-fonts/spline-sans-mono";
|
|
|
|
import { useTheme } from "./src/theme/useTheme";
|
|
import {
|
|
AppStateProvider,
|
|
useAppState,
|
|
type Screen as ScreenName,
|
|
} from "./src/state/AppState";
|
|
import { ManageScreen } from "./src/screens/manage/ManageScreen";
|
|
import { ModelsScreen } from "./src/screens/manage/ModelsScreen";
|
|
import { SkillsScreen } from "./src/screens/manage/SkillsScreen";
|
|
import { ConnectorsScreen } from "./src/screens/manage/ConnectorsScreen";
|
|
import { PluginsScreen } from "./src/screens/manage/PluginsScreen";
|
|
import { PermissionsScreen } from "./src/screens/manage/PermissionsScreen";
|
|
import { RepositoriesScreen } from "./src/screens/manage/RepositoriesScreen";
|
|
import { GitServersScreen } from "./src/screens/manage/GitServersScreen";
|
|
import { ApprovalsScreen } from "./src/screens/manage/ApprovalsScreen";
|
|
import { SharedContextScreen } from "./src/screens/manage/SharedContextScreen";
|
|
import { UsersScreen } from "./src/screens/manage/UsersScreen";
|
|
import { AccountScreen } from "./src/screens/manage/AccountScreen";
|
|
import { ClaudeLoginScreen } from "./src/screens/manage/ClaudeLoginScreen";
|
|
import { ServerConfigProvider, useServerConfig } from "./src/state/ServerConfig";
|
|
import { ConnectScreen } from "./src/screens/ConnectScreen";
|
|
import { FleetScreen } from "./src/screens/FleetScreen";
|
|
import { AgentDetailScreen } from "./src/screens/AgentDetailScreen";
|
|
import { AnswerScreen } from "./src/screens/AnswerScreen";
|
|
import { SpawnScreen } from "./src/screens/SpawnScreen";
|
|
import { SchedulesScreen } from "./src/screens/SchedulesScreen";
|
|
import { MemoryScreen } from "./src/screens/MemoryScreen";
|
|
import { LogScreen } from "./src/screens/LogScreen";
|
|
import { SettingsScreen } from "./src/screens/SettingsScreen";
|
|
|
|
function Router() {
|
|
const { screen } = useAppState();
|
|
|
|
const screens: Record<ScreenName, () => React.JSX.Element> = {
|
|
connect: ConnectScreen,
|
|
fleet: FleetScreen,
|
|
detail: AgentDetailScreen,
|
|
answer: AnswerScreen,
|
|
spawn: SpawnScreen,
|
|
schedules: SchedulesScreen,
|
|
memory: MemoryScreen,
|
|
log: LogScreen,
|
|
settings: SettingsScreen,
|
|
manage: ManageScreen,
|
|
models: ModelsScreen,
|
|
skills: SkillsScreen,
|
|
connectors: ConnectorsScreen,
|
|
plugins: PluginsScreen,
|
|
permissions: PermissionsScreen,
|
|
repositories: RepositoriesScreen,
|
|
gitServers: GitServersScreen,
|
|
approvals: ApprovalsScreen,
|
|
shared: SharedContextScreen,
|
|
users: UsersScreen,
|
|
account: AccountScreen,
|
|
claudeLogin: ClaudeLoginScreen,
|
|
};
|
|
const Screen = screens[screen];
|
|
|
|
return <Screen />;
|
|
}
|
|
|
|
/** Gate: splash while config loads, ConnectScreen when unconfigured, else the fleet app. */
|
|
function Gate() {
|
|
const { config, loading } = useServerConfig();
|
|
const { scheme, colors } = useTheme();
|
|
|
|
return (
|
|
<View style={{ flex: 1, backgroundColor: colors.surfacePage }}>
|
|
<StatusBar style={scheme === "dark" ? "light" : "dark"} />
|
|
{loading ? (
|
|
<SplashPlaceholder />
|
|
) : config ? (
|
|
<AppStateProvider>
|
|
<Router />
|
|
</AppStateProvider>
|
|
) : (
|
|
<ConnectScreen />
|
|
)}
|
|
</View>
|
|
);
|
|
}
|
|
|
|
export default function App() {
|
|
const [fontsLoaded] = useFonts({
|
|
Outfit_400Regular,
|
|
Outfit_500Medium,
|
|
Outfit_600SemiBold,
|
|
Outfit_700Bold,
|
|
Outfit_800ExtraBold,
|
|
Figtree_400Regular,
|
|
Figtree_500Medium,
|
|
Figtree_600SemiBold,
|
|
Figtree_700Bold,
|
|
Figtree_400Regular_Italic,
|
|
SplineSansMono_400Regular,
|
|
SplineSansMono_500Medium,
|
|
SplineSansMono_600SemiBold,
|
|
});
|
|
|
|
return (
|
|
<SafeAreaProvider>
|
|
{fontsLoaded ? (
|
|
<ServerConfigProvider>
|
|
<Gate />
|
|
</ServerConfigProvider>
|
|
) : (
|
|
<SplashPlaceholder />
|
|
)}
|
|
</SafeAreaProvider>
|
|
);
|
|
}
|
|
|
|
/** Blank page-colored screen while fonts load (avoids a font flash). */
|
|
function SplashPlaceholder() {
|
|
const { colors } = useTheme();
|
|
const insets = useSafeAreaInsets();
|
|
return (
|
|
<View
|
|
style={{
|
|
flex: 1,
|
|
backgroundColor: colors.surfacePage,
|
|
paddingTop: insets.top,
|
|
}}
|
|
/>
|
|
);
|
|
}
|