Maximus upgradus
CodexBar Mobile Build / build-android (push) Failing after 1m38s
CodexBar Mobile Build / release (push) Has been skipped

This commit is contained in:
2026-06-24 20:16:34 +02:00
parent d5b0f9c833
commit ba7d957155
20 changed files with 1192 additions and 642 deletions
+5 -2
View File
@@ -53,11 +53,14 @@ jobs:
- name: 📝 Rename build to APK - name: 📝 Rename build to APK
run: mv app-build codexbar-release.apk run: mv app-build codexbar-release.apk
- name: 🗜 Zip APK
run: zip codexbar-release.zip codexbar-release.apk
- name: 📤 Upload build artifact - name: 📤 Upload build artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: codexbar-android-preview-build name: codexbar-android-preview-build
path: codexbar-release.apk path: codexbar-release.zip
if-no-files-found: error if-no-files-found: error
release: release:
@@ -84,7 +87,7 @@ jobs:
with: with:
tag_name: ${{ env.RELEASE_TAG }} tag_name: ${{ env.RELEASE_TAG }}
name: ${{ env.RELEASE_TAG }} name: ${{ env.RELEASE_TAG }}
files: codexbar-release.apk files: codexbar-release.zip
generate_release_notes: true generate_release_notes: true
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+3 -2
View File
@@ -6,7 +6,7 @@
"version": "1.0.0", "version": "1.0.0",
"orientation": "portrait", "orientation": "portrait",
"icon": "./assets/icon.png", "icon": "./assets/icon.png",
"userInterfaceStyle": "light", "userInterfaceStyle": "dark",
"ios": { "ios": {
"supportsTablet": true "supportsTablet": true
}, },
@@ -27,7 +27,8 @@
"expo-router", "expo-router",
"expo-status-bar", "expo-status-bar",
"expo-secure-store", "expo-secure-store",
"expo-document-picker" "expo-document-picker",
"expo-notifications"
], ],
"extra": { "extra": {
"router": {}, "router": {},
+6 -20
View File
@@ -1,10 +1,12 @@
import { Tabs } from "expo-router"; import { Tabs } from "expo-router";
import { useColorScheme } from "react-native"; import { useColorScheme } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { MaterialIcons } from "@expo/vector-icons"; import { MaterialIcons } from "@expo/vector-icons";
export default function TabLayout() { export default function TabLayout() {
const colorScheme = useColorScheme(); const colorScheme = useColorScheme();
const isDark = colorScheme === "dark"; const isDark = colorScheme === "dark";
const insets = useSafeAreaInsets();
return ( return (
<Tabs <Tabs
@@ -15,8 +17,8 @@ export default function TabLayout() {
tabBarStyle: { tabBarStyle: {
backgroundColor: isDark ? "#09090b" : "#ffffff", backgroundColor: isDark ? "#09090b" : "#ffffff",
borderTopColor: isDark ? "#27272a" : "#f4f4f5", borderTopColor: isDark ? "#27272a" : "#f4f4f5",
height: 56, height: 56 + insets.bottom,
paddingBottom: 8, paddingBottom: 8 + insets.bottom,
paddingTop: 6, paddingTop: 6,
}, },
tabBarLabelStyle: { tabBarLabelStyle: {
@@ -34,24 +36,8 @@ export default function TabLayout() {
), ),
}} }}
/> />
<Tabs.Screen <Tabs.Screen name="codex" options={{ href: null }} />
name="codex" <Tabs.Screen name="claude" options={{ href: null }} />
options={{
title: "Codex",
tabBarIcon: ({ color, size }) => (
<MaterialIcons name="auto-awesome" size={size} color={color} />
),
}}
/>
<Tabs.Screen
name="claude"
options={{
title: "Claude",
tabBarIcon: ({ color, size }) => (
<MaterialIcons name="psychology" size={size} color={color} />
),
}}
/>
<Tabs.Screen <Tabs.Screen
name="settings" name="settings"
options={{ options={{
+20 -9
View File
@@ -17,9 +17,11 @@ import { ErrorMessage } from "@/components/ErrorMessage";
export default function ClaudeTab() { export default function ClaudeTab() {
const { const {
sessionKey, auth,
pendingKey, pendingKey,
setPendingKey, setPendingKey,
pendingOrg,
setPendingOrg,
usage, usage,
status, status,
error, error,
@@ -77,7 +79,7 @@ export default function ClaudeTab() {
<ErrorMessage <ErrorMessage
message={error && (status === "idle" || status === "error") ? error : null} message={error && (status === "idle" || status === "error") ? error : null}
/> />
{sessionKey ? ( {auth ? (
<View className="flex-row items-center justify-between"> <View className="flex-row items-center justify-between">
<View className="flex-row items-center gap-x-2.5"> <View className="flex-row items-center gap-x-2.5">
<View className="w-2 h-2 rounded-full bg-green-500" /> <View className="w-2 h-2 rounded-full bg-green-500" />
@@ -85,7 +87,7 @@ export default function ClaudeTab() {
Connected Connected
</Text> </Text>
<Text className="text-xs text-neutral-400 dark:text-neutral-500 font-mono"> <Text className="text-xs text-neutral-400 dark:text-neutral-500 font-mono">
{sessionKey.slice(0, 10)} {auth.sessionKey.slice(0, 10)}
</Text> </Text>
</View> </View>
<TouchableOpacity <TouchableOpacity
@@ -102,11 +104,20 @@ export default function ClaudeTab() {
<TextInput <TextInput
value={pendingKey} value={pendingKey}
onChangeText={setPendingKey} onChangeText={setPendingKey}
placeholder="sk-ant-..." placeholder="sk-ant-… (required)"
placeholderTextColor="#a3a3a3" placeholderTextColor="#a3a3a3"
autoCapitalize="none" autoCapitalize="none"
autoCorrect={false} autoCorrect={false}
secureTextEntry secureTextEntry
className="border border-neutral-200 dark:border-neutral-700 rounded-xl px-3 py-3 text-sm text-neutral-900 dark:text-white bg-white dark:bg-neutral-800 mb-2"
/>
<TextInput
value={pendingOrg}
onChangeText={setPendingOrg}
placeholder="lastActiveOrg UUID (optional)"
placeholderTextColor="#a3a3a3"
autoCapitalize="none"
autoCorrect={false}
className="border border-neutral-200 dark:border-neutral-700 rounded-xl px-3 py-3 text-sm text-neutral-900 dark:text-white bg-white dark:bg-neutral-800 mb-3" className="border border-neutral-200 dark:border-neutral-700 rounded-xl px-3 py-3 text-sm text-neutral-900 dark:text-white bg-white dark:bg-neutral-800 mb-3"
/> />
<TouchableOpacity <TouchableOpacity
@@ -127,10 +138,10 @@ export default function ClaudeTab() {
Where to find it Where to find it
</Text> </Text>
<Text className="text-xs text-neutral-500 dark:text-neutral-400 leading-relaxed"> <Text className="text-xs text-neutral-500 dark:text-neutral-400 leading-relaxed">
Chrome DevTools Application Cookies claude.ai {" "} Chrome DevTools Application Cookies claude.ai copy{" "}
<Text className="font-mono text-neutral-600 dark:text-neutral-300"> <Text className="font-mono text-neutral-600 dark:text-neutral-300">sessionKey</Text>
sessionKey {" "}and optionally{" "}
</Text> <Text className="font-mono text-neutral-600 dark:text-neutral-300">lastActiveOrg</Text>
</Text> </Text>
</View> </View>
</View> </View>
@@ -143,7 +154,7 @@ export default function ClaudeTab() {
Usage Usage
</Text> </Text>
{status === "idle" && !sessionKey && ( {status === "idle" && !auth && (
<View className="items-center py-8"> <View className="items-center py-8">
<MaterialIcons name="insert-chart-outlined" size={32} color="#d4d4d4" /> <MaterialIcons name="insert-chart-outlined" size={32} color="#d4d4d4" />
<Text className="text-sm text-neutral-400 text-center mt-3"> <Text className="text-sm text-neutral-400 text-center mt-3">
+249 -82
View File
@@ -4,20 +4,81 @@ import {
Text, Text,
TouchableOpacity, TouchableOpacity,
RefreshControl, RefreshControl,
ActivityIndicator,
} from "react-native"; } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context"; import { SafeAreaView } from "react-native-safe-area-context";
import { router, useFocusEffect } from "expo-router"; import { router, useFocusEffect } from "expo-router";
import { useCallback } from "react"; import { useCallback, useEffect } from "react";
import { MaterialIcons } from "@expo/vector-icons"; import { MaterialIcons } from "@expo/vector-icons";
import { useCodexUsage } from "@/hooks/useCodexUsage"; import { useCodexUsage } from "@/hooks/useCodexUsage";
import { useClaudeUsage } from "@/hooks/useClaudeUsage"; import { useClaudeUsage } from "@/hooks/useClaudeUsage";
import { ServiceStatusCard } from "@/components/ServiceStatusCard"; import { onUsageDataLoaded } from "@/lib/notifications";
import { ProgressBar } from "@/components/ProgressBar";
import { ResetCountdown } from "@/components/ResetCountdown";
import { windowLabel } from "@/lib/timeUtils"; import { windowLabel } from "@/lib/timeUtils";
function UsageRow({
label,
percent,
resetAtSeconds,
resetAtISO,
}: {
label: string;
percent: number;
resetAtSeconds?: number;
resetAtISO?: string;
}) {
const color =
percent >= 90 ? "#ef4444" : percent >= 70 ? "#f59e0b" : "#10a37f";
return (
<View className="mb-4">
<View className="flex-row justify-between items-baseline mb-1.5">
<Text className="text-xs font-medium text-neutral-500 dark:text-neutral-400">
{label}
</Text>
<Text className="text-sm font-bold" style={{ color }}>
{Math.round(percent)}% used
</Text>
</View>
<ProgressBar percent={percent} />
<ResetCountdown resetAtSeconds={resetAtSeconds} resetAtISO={resetAtISO} />
</View>
);
}
function SetupPrompt({ label }: { label: string }) {
return (
<View>
<Text className="text-sm text-neutral-400 dark:text-neutral-500 mb-3">
{label}
</Text>
<TouchableOpacity
onPress={() => router.navigate("/(tabs)/settings")}
className="flex-row items-center self-start gap-x-1.5 px-3 py-2 rounded-xl border border-neutral-200 dark:border-neutral-700"
>
<Text className="text-xs font-semibold text-neutral-600 dark:text-neutral-300">
Set up in Settings
</Text>
<MaterialIcons name="arrow-forward" size={12} color="#737373" />
</TouchableOpacity>
</View>
);
}
export default function DashboardTab() { export default function DashboardTab() {
const codex = useCodexUsage(); const codex = useCodexUsage();
const claude = useClaudeUsage(); const claude = useClaudeUsage();
// Fire daily digest reschedule + threshold check whenever fresh data arrives
useEffect(() => {
if (codex.status === "success" || claude.status === "success") {
void onUsageDataLoaded(
claude.status === "success" ? claude.usage : null,
codex.status === "success" ? codex.usage : null
);
}
}, [codex.status, claude.status]);
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {
codex.reload(); codex.reload();
@@ -33,46 +94,16 @@ export default function DashboardTab() {
claude.refresh(); claude.refresh();
}; };
const codexRows = const connectedCount = [codex.auth, claude.auth].filter(Boolean).length;
codex.usage
? [
{
label: `Primary (${windowLabel(codex.usage.rate_limit.primary_window.limit_window_seconds)})`,
percent: codex.usage.rate_limit.primary_window.used_percent,
resetAtSeconds: codex.usage.rate_limit.primary_window.reset_at,
},
{
label: `Secondary (${windowLabel(codex.usage.rate_limit.secondary_window.limit_window_seconds)})`,
percent: codex.usage.rate_limit.secondary_window.used_percent,
resetAtSeconds: codex.usage.rate_limit.secondary_window.reset_at,
},
]
: undefined;
const claudeRows =
claude.usage
? [
{
label: "5-hour window",
percent: claude.usage.five_hour.utilization,
resetAtISO: claude.usage.five_hour.resets_at,
},
{
label: "7-day window",
percent: claude.usage.seven_day.utilization,
resetAtISO: claude.usage.seven_day.resets_at,
},
]
: undefined;
return ( return (
<SafeAreaView <SafeAreaView
className="flex-1 bg-neutral-50 dark:bg-neutral-950" className="flex-1 bg-neutral-50 dark:bg-neutral-950"
edges={["top", "bottom"]} edges={["top"]}
> >
<ScrollView <ScrollView
className="flex-1" className="flex-1"
contentContainerStyle={{ paddingHorizontal: 16, paddingBottom: 32 }} contentContainerStyle={{ paddingHorizontal: 16, paddingBottom: 40 }}
refreshControl={ refreshControl={
<RefreshControl <RefreshControl
refreshing={isRefreshing} refreshing={isRefreshing}
@@ -82,14 +113,23 @@ export default function DashboardTab() {
} }
> >
{/* Header */} {/* Header */}
<View className="flex-row items-center justify-between py-5"> <View className="flex-row items-center justify-between pt-6 pb-5">
<View> <View className="flex-row items-center gap-x-3">
<Text className="text-2xl font-bold text-neutral-900 dark:text-white tracking-tight"> <View className="w-10 h-10 rounded-2xl bg-emerald-500 items-center justify-center">
Codexbar <MaterialIcons name="bar-chart" size={20} color="white" />
</Text> </View>
<Text className="text-xs text-neutral-400 mt-0.5"> <View>
AI usage monitor <Text className="text-xl font-bold text-neutral-900 dark:text-white tracking-tight">
</Text> Codexbar
</Text>
<Text className="text-xs text-neutral-400 mt-0.5">
{connectedCount === 0
? "No services connected"
: connectedCount === 2
? "2 services connected"
: "1 of 2 services connected"}
</Text>
</View>
</View> </View>
<TouchableOpacity <TouchableOpacity
onPress={handleRefresh} onPress={handleRefresh}
@@ -100,48 +140,175 @@ export default function DashboardTab() {
</View> </View>
{/* Codex card */} {/* Codex card */}
<ServiceStatusCard <View className="rounded-2xl bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 mb-4 overflow-hidden">
title="Codex CLI" <View style={{ height: 3, backgroundColor: "#10a37f" }} />
icon="auto-awesome" <View className="p-4">
accentColor="#10a37f" <View className="flex-row items-center justify-between mb-4">
status={!codex.auth ? "idle" : codex.status} <View className="flex-row items-center gap-x-2.5">
badge={codex.usage?.plan_type} <View
rows={codexRows} className="w-8 h-8 rounded-xl items-center justify-center"
unconfiguredLabel="Import auth.json to see your Codex rate limits." style={{ backgroundColor: "#10a37f18" }}
onConfigurePress={() => router.navigate("/(tabs)/codex")} >
footer={ <MaterialIcons name="auto-awesome" size={16} color="#10a37f" />
codex.usage && !codex.usage.credits.unlimited && codex.usage.credits.has_credits
? (
<View className="flex-row items-center gap-x-2">
<MaterialIcons name="toll" size={14} color="#a3a3a3" />
<Text className="text-xs text-neutral-500 dark:text-neutral-400">
${Number(codex.usage.credits.balance).toFixed(2)} credits remaining
</Text>
</View> </View>
) <Text className="text-sm font-semibold text-neutral-900 dark:text-white">
: codex.usage?.credits.unlimited Codex CLI
? ( </Text>
<View className="flex-row items-center gap-x-2"> </View>
<MaterialIcons name="all-inclusive" size={14} color="#10a37f" /> <View className="flex-row items-center gap-x-2">
<Text className="text-xs text-green-600 dark:text-green-400"> {codex.usage?.plan_type && (
Unlimited credits <View className="px-2.5 py-1 rounded-full bg-green-100 dark:bg-green-900/30">
</Text> <Text className="text-xs font-semibold text-green-700 dark:text-green-300 capitalize">
</View> {codex.usage.plan_type}
) </Text>
: null </View>
} )}
/> {codex.auth && codex.status === "error" && (
<View className="w-2 h-2 rounded-full bg-red-500" />
)}
{codex.status === "success" && !codex.usage?.plan_type && (
<View className="w-2 h-2 rounded-full bg-green-500" />
)}
</View>
</View>
{!codex.auth && (
<SetupPrompt label="Import auth.json to see your Codex rate limits." />
)}
{codex.auth && codex.status === "loading" && (
<ActivityIndicator
color="#10a37f"
style={{ marginVertical: 16 }}
/>
)}
{codex.auth && codex.status === "error" && (
<View className="flex-row items-center gap-x-2 py-1">
<MaterialIcons name="error-outline" size={16} color="#ef4444" />
<Text className="text-sm text-red-500 dark:text-red-400">
Failed to fetch usage
</Text>
</View>
)}
{codex.status === "success" && codex.usage && (
<View>
<UsageRow
label={`${windowLabel(codex.usage.rate_limit.primary_window.limit_window_seconds)} window`}
percent={codex.usage.rate_limit.primary_window.used_percent}
resetAtSeconds={codex.usage.rate_limit.primary_window.reset_at}
/>
<UsageRow
label={`${windowLabel(codex.usage.rate_limit.secondary_window.limit_window_seconds)} window`}
percent={
codex.usage.rate_limit.secondary_window.used_percent
}
resetAtSeconds={
codex.usage.rate_limit.secondary_window.reset_at
}
/>
{!codex.usage.credits.unlimited &&
codex.usage.credits.has_credits && (
<View className="pt-3 border-t border-neutral-100 dark:border-neutral-800 flex-row items-center gap-x-2">
<MaterialIcons name="toll" size={14} color="#a3a3a3" />
<Text className="text-xs text-neutral-500 dark:text-neutral-400">
${Number(codex.usage.credits.balance).toFixed(2)}{" "}
credits remaining
</Text>
</View>
)}
{codex.usage.credits.unlimited && (
<View className="pt-3 border-t border-neutral-100 dark:border-neutral-800 flex-row items-center gap-x-2">
<MaterialIcons
name="all-inclusive"
size={14}
color="#10a37f"
/>
<Text className="text-xs text-green-600 dark:text-green-400">
Unlimited credits
</Text>
</View>
)}
</View>
)}
</View>
</View>
{/* Claude card */} {/* Claude card */}
<ServiceStatusCard <View className="rounded-2xl bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 mb-4 overflow-hidden">
title="Claude.ai" <View style={{ height: 3, backgroundColor: "#d97706" }} />
icon="psychology" <View className="p-4">
accentColor="#d97706" <View className="flex-row items-center justify-between mb-4">
status={!claude.sessionKey ? "idle" : claude.status} <View className="flex-row items-center gap-x-2.5">
rows={claudeRows} <View
unconfiguredLabel="Add your session key to see Claude usage windows." className="w-8 h-8 rounded-xl items-center justify-center"
onConfigurePress={() => router.navigate("/(tabs)/claude")} style={{ backgroundColor: "#d9770618" }}
/> >
<MaterialIcons name="psychology" size={16} color="#d97706" />
</View>
<Text className="text-sm font-semibold text-neutral-900 dark:text-white">
Claude.ai
</Text>
</View>
{claude.auth && claude.status === "error" && (
<View className="w-2 h-2 rounded-full bg-red-500" />
)}
{claude.status === "success" && (
<View className="w-2 h-2 rounded-full bg-green-500" />
)}
</View>
{!claude.auth && (
<SetupPrompt label="Add your session key to see Claude usage windows." />
)}
{claude.auth && claude.status === "loading" && (
<ActivityIndicator
color="#d97706"
style={{ marginVertical: 16 }}
/>
)}
{claude.auth && claude.status === "error" && (
<View className="flex-row items-center gap-x-2 py-1">
<MaterialIcons name="error-outline" size={16} color="#ef4444" />
<Text className="text-sm text-red-500 dark:text-red-400">
Failed to fetch usage
</Text>
</View>
)}
{claude.status === "success" && claude.usage && (
<View>
<UsageRow
label="5-hour window"
percent={claude.usage.five_hour.utilization}
resetAtISO={claude.usage.five_hour.resets_at}
/>
<UsageRow
label="7-day window"
percent={claude.usage.seven_day.utilization}
resetAtISO={claude.usage.seven_day.resets_at}
/>
{(claude.usage.seven_day_sonnet ||
claude.usage.seven_day_opus) && (
<View className="pt-3 border-t border-neutral-100 dark:border-neutral-800">
<Text className="text-xs font-semibold uppercase tracking-widest text-neutral-400 mb-3">
By Model (7-day)
</Text>
{claude.usage.seven_day_sonnet && (
<UsageRow
label="Sonnet"
percent={claude.usage.seven_day_sonnet.utilization}
/>
)}
{claude.usage.seven_day_opus && (
<UsageRow
label="Opus"
percent={claude.usage.seven_day_opus.utilization}
/>
)}
</View>
)}
</View>
)}
</View>
</View>
</ScrollView> </ScrollView>
</SafeAreaView> </SafeAreaView>
); );
+372 -82
View File
@@ -1,18 +1,47 @@
import { useEffect, useState, useCallback } from "react"; import { useEffect, useState, useCallback } from "react";
import { View, Text, TouchableOpacity, Alert, ScrollView } from "react-native"; import {
View,
Text,
TouchableOpacity,
Alert,
ScrollView,
TextInput,
Switch,
KeyboardAvoidingView,
Platform,
} from "react-native";
import { SafeAreaView } from "react-native-safe-area-context"; import { SafeAreaView } from "react-native-safe-area-context";
import { router, useFocusEffect } from "expo-router"; import { router, useFocusEffect } from "expo-router";
import { MaterialIcons } from "@expo/vector-icons"; import { MaterialIcons } from "@expo/vector-icons";
import { import {
loadCodexAuth, loadCodexAuth,
clearCodexAuth, clearCodexAuth,
saveCodexAuth,
loadClaudeSessionKey, loadClaudeSessionKey,
clearClaudeSessionKey, clearClaudeSessionKey,
saveClaudeSessionKey,
saveClaudeLastActiveOrg,
clearClaudeLastActiveOrg,
} from "@/lib/storage"; } from "@/lib/storage";
import { pickAndReadCodexAuth } from "@/lib/fileReader";
import { resetOnboarding } from "@/lib/setupState"; import { resetOnboarding } from "@/lib/setupState";
import { useNotificationSettings } from "@/hooks/useNotificationSettings";
import type { CodexAuth } from "@/types/codex"; import type { CodexAuth } from "@/types/codex";
import Constants from "expo-constants"; import Constants from "expo-constants";
function parseTimeInput(s: string): { hour: number; minute: number } | null {
const m = s.trim().match(/^(\d{1,2}):(\d{2})$/);
if (!m) return null;
const h = parseInt(m[1], 10);
const min = parseInt(m[2], 10);
if (h < 0 || h > 23 || min < 0 || min > 59) return null;
return { hour: h, minute: min };
}
function formatTime(hour: number, minute: number): string {
return `${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}`;
}
function SettingRow({ function SettingRow({
icon, icon,
label, label,
@@ -88,14 +117,27 @@ function SectionCard({ children }: { children: React.ReactNode }) {
} }
function Divider() { function Divider() {
return ( return <View className="ml-16 mr-0 h-px bg-neutral-100 dark:bg-neutral-800" />;
<View className="ml-16 mr-0 h-px bg-neutral-100 dark:bg-neutral-800" />
);
} }
export default function SettingsTab() { export default function SettingsTab() {
const [codexAuth, setCodexAuth] = useState<CodexAuth | null>(null); const [codexAuth, setCodexAuth] = useState<CodexAuth | null>(null);
const [claudeKey, setClaudeKey] = useState<string | null>(null); const [claudeKey, setClaudeKey] = useState<string | null>(null);
const [pendingKey, setPendingKey] = useState("");
const [pendingOrg, setPendingOrg] = useState("");
const [claudeError, setClaudeError] = useState<string | null>(null);
const notif = useNotificationSettings();
const [timeInput, setTimeInput] = useState("");
const [thresholdInput, setThresholdInput] = useState("");
// Sync local text inputs when settings load
useEffect(() => {
if (notif.loaded) {
setTimeInput(formatTime(notif.settings.dailyHour, notif.settings.dailyMinute));
setThresholdInput(String(notif.settings.thresholdPct));
}
}, [notif.loaded]);
const reload = useCallback(() => { const reload = useCallback(() => {
Promise.all([loadCodexAuth(), loadClaudeSessionKey()]).then( Promise.all([loadCodexAuth(), loadClaudeSessionKey()]).then(
@@ -110,9 +152,40 @@ export default function SettingsTab() {
reload(); reload();
}, [reload]); }, [reload]);
useFocusEffect(useCallback(() => { useFocusEffect(
reload(); useCallback(() => {
}, [reload])); reload();
}, [reload])
);
const handleImportCodex = async () => {
try {
const auth = await pickAndReadCodexAuth();
await saveCodexAuth(auth);
setCodexAuth(auth);
} catch (e: unknown) {
const msg = e instanceof Error ? e.message : "UNKNOWN_ERROR";
if (msg !== "PICKER_CANCELLED") {
Alert.alert("Import failed", msg);
}
}
};
const handleSaveClaude = async () => {
const trimmed = pendingKey.trim();
if (!trimmed.startsWith("sk-ant-")) {
setClaudeError("Key must start with sk-ant-");
return;
}
const orgTrimmed = pendingOrg.trim() || undefined;
await saveClaudeSessionKey(trimmed);
if (orgTrimmed) await saveClaudeLastActiveOrg(orgTrimmed);
else await clearClaudeLastActiveOrg();
setClaudeKey(trimmed);
setPendingKey("");
setPendingOrg("");
setClaudeError(null);
};
const handleClearCodex = () => { const handleClearCodex = () => {
Alert.alert( Alert.alert(
@@ -172,6 +245,26 @@ export default function SettingsTab() {
); );
}; };
const handleTimeBlur = useCallback(() => {
const parsed = parseTimeInput(timeInput);
if (parsed) {
void notif.update({ dailyHour: parsed.hour, dailyMinute: parsed.minute });
} else {
// Reset to last valid value
setTimeInput(formatTime(notif.settings.dailyHour, notif.settings.dailyMinute));
}
}, [timeInput, notif]);
const handleThresholdBlur = useCallback(() => {
const n = parseInt(thresholdInput, 10);
if (!isNaN(n) && n >= 1 && n <= 99) {
void notif.update({ thresholdPct: n });
} else {
setThresholdInput(String(notif.settings.thresholdPct));
}
}, [thresholdInput, notif]);
const canSaveClaude = pendingKey.trim().startsWith("sk-ant-");
const appVersion = Constants.expoConfig?.version ?? "1.0.0"; const appVersion = Constants.expoConfig?.version ?? "1.0.0";
return ( return (
@@ -179,88 +272,285 @@ export default function SettingsTab() {
className="flex-1 bg-neutral-50 dark:bg-neutral-950" className="flex-1 bg-neutral-50 dark:bg-neutral-950"
edges={["top", "bottom"]} edges={["top", "bottom"]}
> >
<ScrollView className="flex-1" contentContainerStyle={{ paddingBottom: 48 }}> <KeyboardAvoidingView
{/* Header */} className="flex-1"
<View className="px-4 py-5"> behavior={Platform.OS === "ios" ? "padding" : "height"}
<Text className="text-2xl font-bold text-neutral-900 dark:text-white tracking-tight"> >
Settings <ScrollView
</Text> className="flex-1"
</View> contentContainerStyle={{ paddingBottom: 48 }}
keyboardShouldPersistTaps="handled"
>
{/* Header */}
<View className="px-4 py-5">
<Text className="text-2xl font-bold text-neutral-900 dark:text-white tracking-tight">
Settings
</Text>
</View>
{/* Codex CLI section */} {/* Codex CLI section */}
<SectionHeader title="Codex CLI" /> <SectionHeader title="Codex CLI" />
<SectionCard> <SectionCard>
{codexAuth ? ( {codexAuth ? (
<> <>
<SettingRow
icon="check-circle"
label="Connected"
value={
codexAuth.accountId
? `${codexAuth.accountId.slice(0, 16)}`
: undefined
}
disabled
/>
<Divider />
<SettingRow
icon="delete-outline"
label="Clear credentials"
onPress={handleClearCodex}
destructive
/>
</>
) : (
<SettingRow <SettingRow
icon="check-circle" icon="upload-file"
label="Connected" label="Import auth.json"
value={codexAuth.accountId ? `${codexAuth.accountId.slice(0, 16)}` : undefined} onPress={handleImportCodex}
disabled
/> />
<Divider /> )}
<SettingRow </SectionCard>
icon="delete-outline"
label="Clear credentials" {/* Claude.ai section */}
onPress={handleClearCodex} <SectionHeader title="Claude.ai" />
destructive <SectionCard>
/> {claudeKey ? (
</> <>
) : ( <SettingRow
icon="check-circle"
label="Connected"
value={`${claudeKey.slice(0, 16)}`}
disabled
/>
<Divider />
<SettingRow
icon="delete-outline"
label="Clear session key"
onPress={handleClearClaude}
destructive
/>
</>
) : (
<View className="p-4">
{claudeError && (
<Text className="text-xs text-red-500 mb-2">
{claudeError}
</Text>
)}
<TextInput
value={pendingKey}
onChangeText={(t) => {
setPendingKey(t);
setClaudeError(null);
}}
placeholder="sk-ant-… (required)"
placeholderTextColor="#a3a3a3"
autoCapitalize="none"
autoCorrect={false}
secureTextEntry
className="border border-neutral-200 dark:border-neutral-700 rounded-xl px-3 py-3 text-sm text-neutral-900 dark:text-white bg-neutral-50 dark:bg-neutral-800 mb-2"
/>
<TextInput
value={pendingOrg}
onChangeText={setPendingOrg}
placeholder="lastActiveOrg UUID (optional)"
placeholderTextColor="#a3a3a3"
autoCapitalize="none"
autoCorrect={false}
className="border border-neutral-200 dark:border-neutral-700 rounded-xl px-3 py-3 text-sm text-neutral-900 dark:text-white bg-neutral-50 dark:bg-neutral-800 mb-3"
/>
<TouchableOpacity
onPress={handleSaveClaude}
disabled={!canSaveClaude}
className="py-3 px-4 rounded-xl items-center"
style={{
backgroundColor: canSaveClaude ? "#d97706" : "#e5e5e5",
}}
>
<Text
className="font-semibold text-sm"
style={{ color: canSaveClaude ? "white" : "#a3a3a3" }}
>
Save Session Key
</Text>
</TouchableOpacity>
<Text className="text-xs text-neutral-400 text-center mt-3 leading-relaxed">
Chrome DevTools Application Cookies claude.ai {" "}
<Text className="font-mono text-neutral-500">sessionKey</Text>
</Text>
</View>
)}
</SectionCard>
{/* Notifications section */}
<SectionHeader title="Notifications" />
<SectionCard>
{notif.permissionStatus !== "granted" ? (
<TouchableOpacity
onPress={async () => {
const granted = await notif.askPermissions();
if (!granted && notif.permissionStatus === "denied") {
Alert.alert(
"Notifications blocked",
"Go to Settings → CodexBar → Notifications to enable them."
);
}
}}
className="flex-row items-center gap-x-3 py-3.5 px-4"
>
<View className="w-8 items-center">
<MaterialIcons name="notifications-off" size={20} color="#737373" />
</View>
<View className="flex-1">
<Text className="text-sm font-medium text-neutral-800 dark:text-neutral-100">
Enable notifications
</Text>
<Text className="text-xs text-neutral-400 mt-0.5">
{notif.permissionStatus === "denied"
? "Blocked — open system Settings to allow"
: "Required to receive alerts"}
</Text>
</View>
{notif.permissionStatus !== "denied" && (
<View className="px-2.5 py-1 rounded-full bg-amber-100 dark:bg-amber-900/30">
<Text className="text-xs font-semibold text-amber-700 dark:text-amber-300">
Allow
</Text>
</View>
)}
</TouchableOpacity>
) : (
<>
{/* Daily digest */}
<View className="flex-row items-center gap-x-3 py-3.5 px-4">
<View className="w-8 items-center">
<MaterialIcons name="alarm" size={20} color="#737373" />
</View>
<View className="flex-1">
<Text className="text-sm font-medium text-neutral-800 dark:text-neutral-100">
Daily digest
</Text>
<Text className="text-xs text-neutral-400 mt-0.5">
Daily reminder with last-known usage
</Text>
</View>
<Switch
value={notif.settings.dailyEnabled}
onValueChange={(v) => void notif.update({ dailyEnabled: v })}
trackColor={{ false: "#e5e5e5", true: "#10a37f" }}
thumbColor="white"
/>
</View>
{notif.settings.dailyEnabled && (
<>
<Divider />
<View className="flex-row items-center gap-x-3 py-3 px-4">
<View className="w-8 items-center">
<MaterialIcons name="schedule" size={20} color="#737373" />
</View>
<Text className="flex-1 text-sm font-medium text-neutral-800 dark:text-neutral-100">
Time
</Text>
<TextInput
value={timeInput}
onChangeText={setTimeInput}
onBlur={handleTimeBlur}
placeholder="09:00"
placeholderTextColor="#a3a3a3"
keyboardType="numbers-and-punctuation"
returnKeyType="done"
maxLength={5}
className="text-sm font-mono text-neutral-600 dark:text-neutral-300 text-right"
style={{ minWidth: 52 }}
/>
</View>
</>
)}
<Divider />
{/* Threshold alert */}
<View className="flex-row items-center gap-x-3 py-3.5 px-4">
<View className="w-8 items-center">
<MaterialIcons name="warning" size={20} color="#737373" />
</View>
<View className="flex-1">
<Text className="text-sm font-medium text-neutral-800 dark:text-neutral-100">
Low quota alert
</Text>
<Text className="text-xs text-neutral-400 mt-0.5">
Alert when weekly quota runs low
</Text>
</View>
<Switch
value={notif.settings.thresholdEnabled}
onValueChange={(v) => void notif.update({ thresholdEnabled: v })}
trackColor={{ false: "#e5e5e5", true: "#d97706" }}
thumbColor="white"
/>
</View>
{notif.settings.thresholdEnabled && (
<>
<Divider />
<View className="flex-row items-center gap-x-3 py-3 px-4">
<View className="w-8 items-center">
<MaterialIcons name="battery-alert" size={20} color="#737373" />
</View>
<Text className="flex-1 text-sm font-medium text-neutral-800 dark:text-neutral-100">
Alert below
</Text>
<View className="flex-row items-center gap-x-1">
<TextInput
value={thresholdInput}
onChangeText={setThresholdInput}
onBlur={handleThresholdBlur}
keyboardType="numeric"
returnKeyType="done"
maxLength={2}
className="text-sm font-mono text-neutral-600 dark:text-neutral-300 text-right"
style={{ minWidth: 28 }}
/>
<Text className="text-sm text-neutral-400 dark:text-neutral-500">
% remaining
</Text>
</View>
</View>
</>
)}
</>
)}
</SectionCard>
{/* App section */}
<SectionHeader title="App" />
<SectionCard>
<SettingRow <SettingRow
icon="upload-file" icon="info-outline"
label="Import auth.json" label="Version"
onPress={() => router.navigate("/(tabs)/codex")} value={appVersion}
disabled
/> />
)} <Divider />
</SectionCard>
{/* Claude.ai section */}
<SectionHeader title="Claude.ai" />
<SectionCard>
{claudeKey ? (
<>
<SettingRow
icon="check-circle"
label="Connected"
value={`${claudeKey.slice(0, 16)}`}
disabled
/>
<Divider />
<SettingRow
icon="delete-outline"
label="Clear session key"
onPress={handleClearClaude}
destructive
/>
</>
) : (
<SettingRow <SettingRow
icon="vpn-key" icon="restart-alt"
label="Enter session key" label="Re-run Setup Wizard"
onPress={() => router.navigate("/(tabs)/claude")} onPress={handleResetSetup}
destructive
/> />
)} </SectionCard>
</SectionCard> </ScrollView>
</KeyboardAvoidingView>
{/* App section */}
<SectionHeader title="App" />
<SectionCard>
<SettingRow
icon="info-outline"
label="Version"
value={appVersion}
disabled
/>
<Divider />
<SettingRow
icon="restart-alt"
label="Re-run Setup Wizard"
onPress={handleResetSetup}
destructive
/>
</SectionCard>
</ScrollView>
</SafeAreaView> </SafeAreaView>
); );
} }
+2 -1
View File
@@ -2,11 +2,12 @@ import "../global.css";
import { Stack } from "expo-router"; import { Stack } from "expo-router";
import { SafeAreaProvider } from "react-native-safe-area-context"; import { SafeAreaProvider } from "react-native-safe-area-context";
import { StatusBar } from "expo-status-bar"; import { StatusBar } from "expo-status-bar";
import "@/lib/notifications"; // registers setNotificationHandler on app start
export default function RootLayout() { export default function RootLayout() {
return ( return (
<SafeAreaProvider> <SafeAreaProvider>
<StatusBar style="auto" /> <StatusBar style="light" />
<Stack screenOptions={{ headerShown: false }} /> <Stack screenOptions={{ headerShown: false }} />
</SafeAreaProvider> </SafeAreaProvider>
); );
+65 -29
View File
@@ -11,17 +11,28 @@ import {
import { SafeAreaView } from "react-native-safe-area-context"; import { SafeAreaView } from "react-native-safe-area-context";
import { router } from "expo-router"; import { router } from "expo-router";
import { MaterialIcons } from "@expo/vector-icons"; import { MaterialIcons } from "@expo/vector-icons";
import { saveClaudeSessionKey, loadClaudeSessionKey } from "@/lib/storage"; import {
saveClaudeSessionKey,
saveClaudeLastActiveOrg,
clearClaudeLastActiveOrg,
loadClaudeSessionKey,
loadClaudeLastActiveOrg,
} from "@/lib/storage";
export default function OnboardingClaudeScreen() { export default function OnboardingClaudeScreen() {
const [savedKey, setSavedKey] = useState<string | null>(null); const [savedKey, setSavedKey] = useState<string | null>(null);
const [savedOrg, setSavedOrg] = useState<string | null>(null);
const [pendingKey, setPendingKey] = useState(""); const [pendingKey, setPendingKey] = useState("");
const [pendingOrg, setPendingOrg] = useState("");
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
useEffect(() => { useEffect(() => {
loadClaudeSessionKey().then((k) => { Promise.all([loadClaudeSessionKey(), loadClaudeLastActiveOrg()]).then(
if (k) setSavedKey(k); ([k, o]) => {
}); if (k) setSavedKey(k);
if (o) setSavedOrg(o);
}
);
}, []); }, []);
const canSave = pendingKey.trim().startsWith("sk-ant-"); const canSave = pendingKey.trim().startsWith("sk-ant-");
@@ -32,11 +43,16 @@ export default function OnboardingClaudeScreen() {
setError("Session key must start with sk-ant-"); setError("Session key must start with sk-ant-");
return; return;
} }
const orgTrimmed = pendingOrg.trim() || undefined;
await saveClaudeSessionKey(trimmed); await saveClaudeSessionKey(trimmed);
if (orgTrimmed) await saveClaudeLastActiveOrg(orgTrimmed);
else await clearClaudeLastActiveOrg();
setSavedKey(trimmed); setSavedKey(trimmed);
setSavedOrg(orgTrimmed ?? null);
setPendingKey(""); setPendingKey("");
setPendingOrg("");
setError(null); setError(null);
}, [pendingKey]); }, [pendingKey, pendingOrg]);
return ( return (
<SafeAreaView className="flex-1 bg-neutral-950"> <SafeAreaView className="flex-1 bg-neutral-950">
@@ -92,27 +108,38 @@ export default function OnboardingClaudeScreen() {
</Text> </Text>
{savedKey ? ( {savedKey ? (
<View className="flex-row items-center gap-x-3 bg-neutral-900 rounded-2xl p-4 border border-neutral-800 mb-4"> <View className="bg-neutral-900 rounded-2xl border border-neutral-800 mb-4 overflow-hidden">
<View <View className="flex-row items-center gap-x-3 p-4">
className="w-10 h-10 rounded-xl items-center justify-center" <View
style={{ backgroundColor: "#d9770622" }} className="w-10 h-10 rounded-xl items-center justify-center"
> style={{ backgroundColor: "#d9770622" }}
<MaterialIcons name="check-circle" size={22} color="#d97706" /> >
<MaterialIcons name="check-circle" size={22} color="#d97706" />
</View>
<View className="flex-1">
<Text className="text-white font-semibold">Connected</Text>
<Text className="text-neutral-500 text-sm font-mono">
{savedKey.slice(0, 16)}
</Text>
</View>
<TouchableOpacity
onPress={() => {
setSavedKey(null);
setSavedOrg(null);
setPendingKey("");
setPendingOrg("");
}}
>
<Text className="text-neutral-400 text-xs">Change</Text>
</TouchableOpacity>
</View> </View>
<View className="flex-1"> {savedOrg && (
<Text className="text-white font-semibold">Connected</Text> <View className="border-t border-neutral-800 px-4 py-3">
<Text className="text-neutral-500 text-sm font-mono"> <Text className="text-neutral-500 text-xs font-mono">
{savedKey.slice(0, 16)} org: {savedOrg.slice(0, 8)}
</Text> </Text>
</View> </View>
<TouchableOpacity )}
onPress={() => {
setSavedKey(null);
setPendingKey("");
}}
>
<Text className="text-neutral-400 text-xs">Change</Text>
</TouchableOpacity>
</View> </View>
) : ( ) : (
<View> <View>
@@ -127,13 +154,22 @@ export default function OnboardingClaudeScreen() {
setPendingKey(t); setPendingKey(t);
setError(null); setError(null);
}} }}
placeholder="sk-ant-..." placeholder="sk-ant-… (required)"
placeholderTextColor="#525252" placeholderTextColor="#525252"
autoCapitalize="none" autoCapitalize="none"
autoCorrect={false} autoCorrect={false}
secureTextEntry secureTextEntry
className="border border-neutral-800 rounded-xl px-4 py-3.5 text-sm text-white bg-neutral-900 mb-3" className="border border-neutral-800 rounded-xl px-4 py-3.5 text-sm text-white bg-neutral-900 mb-3"
/> />
<TextInput
value={pendingOrg}
onChangeText={setPendingOrg}
placeholder="lastActiveOrg UUID (optional)"
placeholderTextColor="#525252"
autoCapitalize="none"
autoCorrect={false}
className="border border-neutral-800 rounded-xl px-4 py-3.5 text-sm text-white bg-neutral-900 mb-3"
/>
<TouchableOpacity <TouchableOpacity
onPress={handleSave} onPress={handleSave}
disabled={!canSave} disabled={!canSave}
@@ -149,11 +185,11 @@ export default function OnboardingClaudeScreen() {
</TouchableOpacity> </TouchableOpacity>
<View className="bg-neutral-900 rounded-xl p-4 border border-neutral-800"> <View className="bg-neutral-900 rounded-xl p-4 border border-neutral-800">
<Text className="text-neutral-400 text-xs font-semibold uppercase tracking-widest mb-2"> <Text className="text-neutral-400 text-xs font-semibold uppercase tracking-widest mb-2">
How to find your session key How to find your credentials
</Text> </Text>
<Text className="text-neutral-500 text-sm leading-relaxed"> <Text className="text-neutral-500 text-sm leading-relaxed">
Open Claude.ai in Chrome DevTools (F12) Application Cookies claude.ai find{" "} Open Claude.ai in Chrome DevTools (F12) Application Cookies claude.ai{"\n"}
<Text className="text-neutral-300 font-mono">sessionKey</Text> Copy <Text className="text-neutral-300 font-mono">sessionKey</Text> and optionally <Text className="text-neutral-300 font-mono">lastActiveOrg</Text>
</Text> </Text>
</View> </View>
</View> </View>
+1
View File
@@ -5,5 +5,6 @@ module.exports = function (api) {
["babel-preset-expo", { jsxImportSource: "nativewind" }], ["babel-preset-expo", { jsxImportSource: "nativewind" }],
"nativewind/babel", "nativewind/babel",
], ],
plugins: ["react-native-reanimated/plugin"],
}; };
}; };
+42 -33
View File
@@ -4,15 +4,18 @@ import {
loadClaudeSessionKey, loadClaudeSessionKey,
saveClaudeSessionKey, saveClaudeSessionKey,
clearClaudeSessionKey, clearClaudeSessionKey,
loadClaudeLastActiveOrg,
saveClaudeLastActiveOrg,
clearClaudeLastActiveOrg,
} from "@/lib/storage"; } from "@/lib/storage";
import type { ClaudeUsageResponse } from "@/types/claude"; import type { ClaudeAuth, ClaudeUsageResponse } from "@/types/claude";
type Status = "idle" | "loading" | "success" | "error"; type Status = "idle" | "loading" | "success" | "error";
async function doFetch( async function doFetch(
sessionKey: string, auth: ClaudeAuth,
set: { set: {
sessionKey: (v: string | null) => void; auth: (v: ClaudeAuth | null) => void;
usage: (v: ClaudeUsageResponse | null) => void; usage: (v: ClaudeUsageResponse | null) => void;
status: (v: Status) => void; status: (v: Status) => void;
error: (v: string | null) => void; error: (v: string | null) => void;
@@ -21,10 +24,13 @@ async function doFetch(
set.status("loading"); set.status("loading");
set.error(null); set.error(null);
try { try {
const orgs = await fetchClaudeOrgs(sessionKey); let orgUuid = auth.lastActiveOrg;
if (!orgs.length) throw new Error("NO_ORGS_FOUND"); if (!orgUuid) {
const orgUuid = orgs[0].uuid; const orgs = await fetchClaudeOrgs(auth);
const data = await fetchClaudeUsage(sessionKey, orgUuid); if (!orgs.length) throw new Error("NO_ORGS_FOUND");
orgUuid = orgs[0].uuid;
}
const data = await fetchClaudeUsage(auth, orgUuid);
set.usage(data); set.usage(data);
set.status("success"); set.status("success");
} catch (e: unknown) { } catch (e: unknown) {
@@ -33,29 +39,32 @@ async function doFetch(
set.status("error"); set.status("error");
if (msg === "TOKEN_EXPIRED") { if (msg === "TOKEN_EXPIRED") {
await clearClaudeSessionKey(); await clearClaudeSessionKey();
set.sessionKey(null); set.auth(null);
} }
} }
} }
export function useClaudeUsage() { export function useClaudeUsage() {
const [sessionKey, setSessionKey] = useState<string | null>(null); const [auth, setAuth] = useState<ClaudeAuth | null>(null);
const [pendingKey, setPendingKey] = useState(""); const [pendingKey, setPendingKey] = useState("");
const [pendingOrg, setPendingOrg] = useState("");
const [usage, setUsage] = useState<ClaudeUsageResponse | null>(null); const [usage, setUsage] = useState<ClaudeUsageResponse | null>(null);
const [status, setStatus] = useState<Status>("idle"); const [status, setStatus] = useState<Status>("idle");
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const setters = { sessionKey: setSessionKey, usage: setUsage, status: setStatus, error: setError }; const setters = { auth: setAuth, usage: setUsage, status: setStatus, error: setError };
useEffect(() => { useEffect(() => {
loadClaudeSessionKey().then((stored) => { Promise.all([loadClaudeSessionKey(), loadClaudeLastActiveOrg()]).then(
if (stored) setSessionKey(stored); ([key, org]) => {
}); if (key) setAuth({ sessionKey: key, lastActiveOrg: org ?? undefined });
}
);
}, []); }, []);
useEffect(() => { useEffect(() => {
if (sessionKey) void doFetch(sessionKey, setters); if (auth) void doFetch(auth, setters);
}, [sessionKey]); }, [auth]);
const saveKey = useCallback(async () => { const saveKey = useCallback(async () => {
const trimmed = pendingKey.trim(); const trimmed = pendingKey.trim();
@@ -63,47 +72,47 @@ export function useClaudeUsage() {
setError("Session key must start with sk-ant-"); setError("Session key must start with sk-ant-");
return; return;
} }
const orgTrimmed = pendingOrg.trim() || undefined;
await saveClaudeSessionKey(trimmed); await saveClaudeSessionKey(trimmed);
setSessionKey(trimmed); if (orgTrimmed) await saveClaudeLastActiveOrg(orgTrimmed);
else await clearClaudeLastActiveOrg();
setAuth({ sessionKey: trimmed, lastActiveOrg: orgTrimmed });
setPendingKey(""); setPendingKey("");
setPendingOrg("");
setError(null); setError(null);
}, [pendingKey]); }, [pendingKey, pendingOrg]);
const refresh = useCallback(async () => { const refresh = useCallback(async () => {
if (!sessionKey) return; if (!auth) return;
await doFetch(sessionKey, setters); await doFetch(auth, setters);
}, [sessionKey]); }, [auth]);
// Re-reads the session key from storage and fetches fresh data. Call from
// useFocusEffect so the dashboard stays current when the key is saved from
// another tab. If the string value is unchanged, React bails out on setState
// so we trigger the fetch directly.
const reload = useCallback(async () => { const reload = useCallback(async () => {
const stored = await loadClaudeSessionKey(); const [key, org] = await Promise.all([loadClaudeSessionKey(), loadClaudeLastActiveOrg()]);
if (!stored) { if (!key) {
setSessionKey(null); setAuth(null);
setUsage(null); setUsage(null);
setStatus("idle"); setStatus("idle");
return; return;
} }
// If already set to the same string, setState bails out and [sessionKey] // Creating a new object always triggers the [auth] useEffect even if values are identical
// effect won't fire, so call the fetch directly. setAuth({ sessionKey: key, lastActiveOrg: org ?? undefined });
setSessionKey(stored);
void doFetch(stored, setters);
}, []); }, []);
const clearKey = useCallback(async () => { const clearKey = useCallback(async () => {
await clearClaudeSessionKey(); await clearClaudeSessionKey();
setSessionKey(null); setAuth(null);
setUsage(null); setUsage(null);
setStatus("idle"); setStatus("idle");
setError(null); setError(null);
}, []); }, []);
return { return {
sessionKey, auth,
pendingKey, pendingKey,
setPendingKey, setPendingKey,
pendingOrg,
setPendingOrg,
usage, usage,
status, status,
error, error,
+64
View File
@@ -0,0 +1,64 @@
import { useState, useEffect, useCallback } from "react";
import {
loadNotifSettings,
saveNotifSettings,
type NotifSettings,
} from "@/lib/storage";
import {
requestPermissions,
getPermissionStatus,
scheduleDailyDigest,
cancelDailyDigest,
} from "@/lib/notifications";
export function useNotificationSettings() {
const [settings, setSettings] = useState<NotifSettings>({
dailyEnabled: false,
dailyHour: 9,
dailyMinute: 0,
thresholdEnabled: false,
thresholdPct: 20,
});
const [permissionStatus, setPermissionStatus] = useState("undetermined");
const [loaded, setLoaded] = useState(false);
useEffect(() => {
Promise.all([loadNotifSettings(), getPermissionStatus()]).then(
([stored, status]) => {
setSettings(stored);
setPermissionStatus(status);
setLoaded(true);
}
);
}, []);
const askPermissions = useCallback(async (): Promise<boolean> => {
const granted = await requestPermissions();
setPermissionStatus(granted ? "granted" : "denied");
return granted;
}, []);
const update = useCallback(
async (patch: Partial<NotifSettings>) => {
const next = { ...settings, ...patch };
setSettings(next);
await saveNotifSettings(next);
// Keep scheduled notification in sync when toggling or changing time
const dailyChanged =
"dailyEnabled" in patch ||
"dailyHour" in patch ||
"dailyMinute" in patch;
if (dailyChanged) {
if (next.dailyEnabled) {
await scheduleDailyDigest(next.dailyHour, next.dailyMinute, null, null);
} else {
await cancelDailyDigest();
}
}
},
[settings]
);
return { settings, permissionStatus, loaded, askPermissions, update };
}
+24 -15
View File
@@ -1,24 +1,33 @@
import type { ClaudeOrg, ClaudeUsageResponse } from "@/types/claude"; import type { ClaudeAuth, ClaudeOrg, ClaudeUsageResponse } from "@/types/claude";
const BASE_URL = "https://claude.ai/api"; const BASE_URL = "https://claude.ai/api";
function cookieHeader(sessionKey: string): Record<string, string> { const BROWSER_UA =
"Mozilla/5.0 (Linux; Android 10; Mobile) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36";
function buildHeaders(auth: ClaudeAuth): Record<string, string> {
const cookies = [`sessionKey=${auth.sessionKey}`];
if (auth.lastActiveOrg) cookies.push(`lastActiveOrg=${auth.lastActiveOrg}`);
return { return {
Cookie: `sessionKey=${sessionKey}`, Cookie: cookies.join("; "),
"Accept": "application/json", Accept: "application/json, text/plain, */*",
"Origin": "https://claude.ai", "Accept-Language": "en-US,en;q=0.9",
"User-Agent": BROWSER_UA,
Referer: "https://claude.ai/",
Origin: "https://claude.ai",
"anthropic-client-platform": "web_claude_to_cc_migration",
}; };
} }
export async function fetchClaudeOrgs(sessionKey: string): Promise<ClaudeOrg[]> { export async function fetchClaudeOrgs(auth: ClaudeAuth): Promise<ClaudeOrg[]> {
const response = await fetch(`${BASE_URL}/organizations`, { const response = await fetch(`${BASE_URL}/organizations`, {
headers: cookieHeader(sessionKey), headers: buildHeaders(auth),
credentials: "omit",
}); });
if (response.status === 401 || response.status === 403) {
throw new Error("TOKEN_EXPIRED");
}
if (!response.ok) { if (!response.ok) {
await response.text().catch(() => null);
if (response.status === 401 || response.status === 403) throw new Error("TOKEN_EXPIRED");
throw new Error(`HTTP_ERROR_${response.status}`); throw new Error(`HTTP_ERROR_${response.status}`);
} }
@@ -26,17 +35,17 @@ export async function fetchClaudeOrgs(sessionKey: string): Promise<ClaudeOrg[]>
} }
export async function fetchClaudeUsage( export async function fetchClaudeUsage(
sessionKey: string, auth: ClaudeAuth,
orgUuid: string orgUuid: string
): Promise<ClaudeUsageResponse> { ): Promise<ClaudeUsageResponse> {
const response = await fetch(`${BASE_URL}/organizations/${orgUuid}/usage`, { const response = await fetch(`${BASE_URL}/organizations/${orgUuid}/usage`, {
headers: cookieHeader(sessionKey), headers: buildHeaders(auth),
credentials: "omit",
}); });
if (response.status === 401 || response.status === 403) {
throw new Error("TOKEN_EXPIRED");
}
if (!response.ok) { if (!response.ok) {
await response.text().catch(() => null);
if (response.status === 401 || response.status === 403) throw new Error("TOKEN_EXPIRED");
throw new Error(`HTTP_ERROR_${response.status}`); throw new Error(`HTTP_ERROR_${response.status}`);
} }
+1
View File
@@ -18,6 +18,7 @@ export async function fetchCodexUsage(auth: CodexAuth): Promise<CodexUsageRespon
throw new Error("TOKEN_EXPIRED"); throw new Error("TOKEN_EXPIRED");
} }
if (!response.ok) { if (!response.ok) {
await response.text().catch(() => null);
throw new Error(`HTTP_ERROR_${response.status}`); throw new Error(`HTTP_ERROR_${response.status}`);
} }
+6 -8
View File
@@ -1,5 +1,4 @@
import * as DocumentPicker from "expo-document-picker"; import * as DocumentPicker from "expo-document-picker";
import { File } from "expo-file-system/next";
import type { CodexAuth } from "@/types/codex"; import type { CodexAuth } from "@/types/codex";
export async function pickAndReadCodexAuth(): Promise<CodexAuth> { export async function pickAndReadCodexAuth(): Promise<CodexAuth> {
@@ -8,13 +7,11 @@ export async function pickAndReadCodexAuth(): Promise<CodexAuth> {
copyToCacheDirectory: true, copyToCacheDirectory: true,
}); });
if (result.canceled) { if (result.canceled) throw new Error("PICKER_CANCELLED");
throw new Error("PICKER_CANCELLED");
}
const { uri } = result.assets[0]; const { uri } = result.assets[0];
const file = new File(uri); const response = await fetch(uri);
const text = file.text(); const text = await response.text();
let parsed: Record<string, unknown>; let parsed: Record<string, unknown>;
try { try {
@@ -23,8 +20,9 @@ export async function pickAndReadCodexAuth(): Promise<CodexAuth> {
throw new Error("INVALID_JSON"); throw new Error("INVALID_JSON");
} }
const accessToken = parsed["accessToken"] as string | undefined; const tokens = parsed["tokens"] as Record<string, unknown> | undefined;
const accountId = parsed["accountId"] as string | undefined; const accessToken = (tokens?.["access_token"] ?? parsed["accessToken"]) as string | undefined;
const accountId = (tokens?.["account_id"] ?? parsed["accountId"]) as string | undefined;
if (!accessToken || typeof accessToken !== "string") { if (!accessToken || typeof accessToken !== "string") {
throw new Error("MISSING_ACCESS_TOKEN"); throw new Error("MISSING_ACCESS_TOKEN");
+148
View File
@@ -0,0 +1,148 @@
import * as Notifications from "expo-notifications";
import {
loadNotifSettings,
loadNotifThresholdLastFired,
saveNotifThresholdLastFired,
} from "@/lib/storage";
import type { ClaudeUsageResponse } from "@/types/claude";
import type { CodexUsageResponse } from "@/types/codex";
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldShowBanner: true,
shouldShowList: true,
shouldPlaySound: false,
shouldSetBadge: false,
}),
});
export async function requestPermissions(): Promise<boolean> {
const { status } = await Notifications.requestPermissionsAsync();
return status === "granted";
}
export async function getPermissionStatus(): Promise<string> {
const { status } = await Notifications.getPermissionsAsync();
return status;
}
const DAILY_ID = "codexbar-daily-digest";
export async function scheduleDailyDigest(
hour: number,
minute: number,
claudeUsage: ClaudeUsageResponse | null,
codexUsage: CodexUsageResponse | null
): Promise<void> {
try {
await Notifications.cancelScheduledNotificationAsync(DAILY_ID);
} catch {}
const parts: string[] = [];
if (claudeUsage) {
parts.push(`Claude 7d: ${Math.round(claudeUsage.seven_day.utilization)}% used`);
}
if (codexUsage) {
parts.push(
`Codex: ${Math.round(codexUsage.rate_limit.secondary_window.used_percent)}% used`
);
}
await Notifications.scheduleNotificationAsync({
identifier: DAILY_ID,
content: {
title: "Usage Digest",
body:
parts.length > 0
? parts.join(" · ")
: "Open to check your usage limits",
},
trigger: {
type: Notifications.SchedulableTriggerInputTypes.CALENDAR,
hour,
minute,
repeats: true,
},
});
}
export async function cancelDailyDigest(): Promise<void> {
try {
await Notifications.cancelScheduledNotificationAsync(DAILY_ID);
} catch {}
}
interface ThresholdAlert {
service: string;
window: string;
remaining: number;
}
function buildThresholdAlerts(
thresholdPct: number,
claudeUsage: ClaudeUsageResponse | null,
codexUsage: CodexUsageResponse | null
): ThresholdAlert[] {
const alerts: ThresholdAlert[] = [];
if (claudeUsage) {
const remaining = 100 - claudeUsage.seven_day.utilization;
if (remaining <= thresholdPct) {
alerts.push({ service: "Claude", window: "7-day", remaining });
}
}
if (codexUsage) {
const remaining = 100 - codexUsage.rate_limit.secondary_window.used_percent;
if (remaining <= thresholdPct) {
alerts.push({ service: "Codex", window: "weekly", remaining });
}
}
return alerts;
}
/** Call this after every successful usage fetch. Reschedules daily digest and fires threshold alerts. */
export async function onUsageDataLoaded(
claudeUsage: ClaudeUsageResponse | null,
codexUsage: CodexUsageResponse | null
): Promise<void> {
const settings = await loadNotifSettings();
if (settings.dailyEnabled) {
await scheduleDailyDigest(
settings.dailyHour,
settings.dailyMinute,
claudeUsage,
codexUsage
);
}
if (settings.thresholdEnabled) {
const today = new Date().toISOString().slice(0, 10);
const lastFired = await loadNotifThresholdLastFired();
if (lastFired !== today) {
const alerts = buildThresholdAlerts(
settings.thresholdPct,
claudeUsage,
codexUsage
);
if (alerts.length > 0) {
await Notifications.scheduleNotificationAsync({
content: {
title: "Low quota warning",
body: alerts
.map(
(a) =>
`${a.service} ${a.window}: ${Math.round(a.remaining)}% remaining`
)
.join("\n"),
},
trigger: null,
});
await saveNotifThresholdLastFired(today);
}
}
}
}
+64
View File
@@ -3,8 +3,59 @@ import * as SecureStore from "expo-secure-store";
const KEYS = { const KEYS = {
CODEX_AUTH: "codexbar_codex_auth", CODEX_AUTH: "codexbar_codex_auth",
CLAUDE_SESSION_KEY: "codexbar_claude_session_key", CLAUDE_SESSION_KEY: "codexbar_claude_session_key",
CLAUDE_LAST_ACTIVE_ORG: "codexbar_claude_last_active_org",
NOTIF_DAILY_ENABLED: "notifDailyEnabled",
NOTIF_DAILY_HOUR: "notifDailyHour",
NOTIF_DAILY_MINUTE: "notifDailyMinute",
NOTIF_THRESHOLD_ENABLED: "notifThresholdEnabled",
NOTIF_THRESHOLD_PCT: "notifThresholdPct",
NOTIF_THRESHOLD_LAST_FIRED: "notifThresholdLastFired",
} as const; } as const;
export interface NotifSettings {
dailyEnabled: boolean;
dailyHour: number;
dailyMinute: number;
thresholdEnabled: boolean;
/** Alert when remaining quota falls below this % (e.g. 20 = fire when < 20% left) */
thresholdPct: number;
}
export async function loadNotifSettings(): Promise<NotifSettings> {
const [de, dh, dm, te, tp] = await Promise.all([
SecureStore.getItemAsync(KEYS.NOTIF_DAILY_ENABLED),
SecureStore.getItemAsync(KEYS.NOTIF_DAILY_HOUR),
SecureStore.getItemAsync(KEYS.NOTIF_DAILY_MINUTE),
SecureStore.getItemAsync(KEYS.NOTIF_THRESHOLD_ENABLED),
SecureStore.getItemAsync(KEYS.NOTIF_THRESHOLD_PCT),
]);
return {
dailyEnabled: de === "true",
dailyHour: dh !== null ? parseInt(dh, 10) : 9,
dailyMinute: dm !== null ? parseInt(dm, 10) : 0,
thresholdEnabled: te === "true",
thresholdPct: tp !== null ? parseInt(tp, 10) : 20,
};
}
export async function saveNotifSettings(s: NotifSettings): Promise<void> {
await Promise.all([
SecureStore.setItemAsync(KEYS.NOTIF_DAILY_ENABLED, String(s.dailyEnabled)),
SecureStore.setItemAsync(KEYS.NOTIF_DAILY_HOUR, String(s.dailyHour)),
SecureStore.setItemAsync(KEYS.NOTIF_DAILY_MINUTE, String(s.dailyMinute)),
SecureStore.setItemAsync(KEYS.NOTIF_THRESHOLD_ENABLED, String(s.thresholdEnabled)),
SecureStore.setItemAsync(KEYS.NOTIF_THRESHOLD_PCT, String(s.thresholdPct)),
]);
}
export async function loadNotifThresholdLastFired(): Promise<string | null> {
return SecureStore.getItemAsync(KEYS.NOTIF_THRESHOLD_LAST_FIRED);
}
export async function saveNotifThresholdLastFired(date: string): Promise<void> {
await SecureStore.setItemAsync(KEYS.NOTIF_THRESHOLD_LAST_FIRED, date);
}
export async function saveCodexAuth(auth: { export async function saveCodexAuth(auth: {
accessToken: string; accessToken: string;
accountId?: string; accountId?: string;
@@ -29,10 +80,23 @@ export async function loadClaudeSessionKey(): Promise<string | null> {
return SecureStore.getItemAsync(KEYS.CLAUDE_SESSION_KEY); return SecureStore.getItemAsync(KEYS.CLAUDE_SESSION_KEY);
} }
export async function saveClaudeLastActiveOrg(orgId: string): Promise<void> {
await SecureStore.setItemAsync(KEYS.CLAUDE_LAST_ACTIVE_ORG, orgId);
}
export async function loadClaudeLastActiveOrg(): Promise<string | null> {
return SecureStore.getItemAsync(KEYS.CLAUDE_LAST_ACTIVE_ORG);
}
export async function clearClaudeLastActiveOrg(): Promise<void> {
await SecureStore.deleteItemAsync(KEYS.CLAUDE_LAST_ACTIVE_ORG);
}
export async function clearCodexAuth(): Promise<void> { export async function clearCodexAuth(): Promise<void> {
await SecureStore.deleteItemAsync(KEYS.CODEX_AUTH); await SecureStore.deleteItemAsync(KEYS.CODEX_AUTH);
} }
export async function clearClaudeSessionKey(): Promise<void> { export async function clearClaudeSessionKey(): Promise<void> {
await SecureStore.deleteItemAsync(KEYS.CLAUDE_SESSION_KEY); await SecureStore.deleteItemAsync(KEYS.CLAUDE_SESSION_KEY);
await SecureStore.deleteItemAsync(KEYS.CLAUDE_LAST_ACTIVE_ORG);
} }
+16 -6
View File
@@ -5,11 +5,17 @@ export function formatResetCountdown(resetAtSeconds: number): string {
if (diffSeconds <= 0) return "resetting now"; if (diffSeconds <= 0) return "resetting now";
if (diffSeconds < 60) return "resets in <1m"; if (diffSeconds < 60) return "resets in <1m";
const hours = Math.floor(diffSeconds / 3600); const totalHours = Math.floor(diffSeconds / 3600);
const minutes = Math.floor((diffSeconds % 3600) / 60); const minutes = Math.floor((diffSeconds % 3600) / 60);
if (hours > 0 && minutes > 0) return `resets in ${hours}h ${minutes}m`; if (totalHours >= 24) {
if (hours > 0) return `resets in ${hours}h`; const days = Math.floor(totalHours / 24);
const hours = totalHours % 24;
if (hours > 0) return `resets in ${days}d ${hours}h`;
return `resets in ${days}d`;
}
if (totalHours > 0 && minutes > 0) return `resets in ${totalHours}h ${minutes}m`;
if (totalHours > 0) return `resets in ${totalHours}h`;
return `resets in ${minutes}m`; return `resets in ${minutes}m`;
} }
@@ -19,7 +25,11 @@ export function formatResetCountdownISO(isoString: string): string {
} }
export function windowLabel(limitWindowSeconds: number): string { export function windowLabel(limitWindowSeconds: number): string {
if (limitWindowSeconds <= 3600) return "1h window"; const hours = limitWindowSeconds / 3600;
if (limitWindowSeconds <= 86400) return "24h window"; if (hours < 24) return `${Math.round(hours)}h`;
return `${Math.round(limitWindowSeconds / 3600)}h window`; const days = hours / 24;
const wholeDays = Math.floor(days);
const remainderHours = Math.round(hours - wholeDays * 24);
if (remainderHours === 0) return `${wholeDays}d`;
return `${wholeDays}d ${remainderHours}h`;
} }
+96 -352
View File
@@ -15,12 +15,14 @@
"expo-document-picker": "~56.0.4", "expo-document-picker": "~56.0.4",
"expo-file-system": "~56.0.8", "expo-file-system": "~56.0.8",
"expo-linking": "~56.0.14", "expo-linking": "~56.0.14",
"expo-notifications": "~56.0.18",
"expo-router": "~56.2.11", "expo-router": "~56.2.11",
"expo-secure-store": "~56.0.4", "expo-secure-store": "~56.0.4",
"expo-status-bar": "~56.0.4", "expo-status-bar": "~56.0.4",
"nativewind": "^4.2.6", "nativewind": "^4.2.6",
"react": "^19.2.7", "react": "19.2.3",
"react-native": "0.85.3", "react-native": "0.85.3",
"react-native-reanimated": "4.3.1",
"react-native-safe-area-context": "~5.7.0", "react-native-safe-area-context": "~5.7.0",
"react-native-screens": "4.25.2" "react-native-screens": "4.25.2"
}, },
@@ -40,6 +42,7 @@
"version": "5.2.0", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=10" "node": ">=10"
@@ -567,22 +570,6 @@
"@babel/core": "^7.0.0-0" "@babel/core": "^7.0.0-0"
} }
}, },
"node_modules/@babel/plugin-transform-arrow-functions": {
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.29.7.tgz",
"integrity": "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-async-generator-functions": { "node_modules/@babel/plugin-transform-async-generator-functions": {
"version": "7.29.7", "version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.7.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.7.tgz",
@@ -956,38 +943,6 @@
"@babel/core": "^7.0.0-0" "@babel/core": "^7.0.0-0"
} }
}, },
"node_modules/@babel/plugin-transform-react-jsx-self": {
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz",
"integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-react-jsx-source": {
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz",
"integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-react-pure-annotations": { "node_modules/@babel/plugin-transform-react-pure-annotations": {
"version": "7.29.7", "version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.29.7.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.29.7.tgz",
@@ -1004,22 +959,6 @@
"@babel/core": "^7.0.0-0" "@babel/core": "^7.0.0-0"
} }
}, },
"node_modules/@babel/plugin-transform-regenerator": {
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.7.tgz",
"integrity": "sha512-rNNFV0DBAJp988xW2DOntfDoYn1eR8GGF5AT5vYc+rjyfaQkM242c9tZUHHPe7KYaiJizXPWhQTzzdbXySyhBw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-runtime": { "node_modules/@babel/plugin-transform-runtime": {
"version": "7.29.7", "version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.7.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.7.tgz",
@@ -1049,38 +988,6 @@
"semver": "bin/semver.js" "semver": "bin/semver.js"
} }
}, },
"node_modules/@babel/plugin-transform-shorthand-properties": {
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.29.7.tgz",
"integrity": "sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-template-literals": {
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.29.7.tgz",
"integrity": "sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-typescript": { "node_modules/@babel/plugin-transform-typescript": {
"version": "7.29.7", "version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz",
@@ -1665,6 +1572,7 @@
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@nodelib/fs.stat": "2.0.5", "@nodelib/fs.stat": "2.0.5",
@@ -1678,6 +1586,7 @@
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 8" "node": ">= 8"
@@ -1687,6 +1596,7 @@
"version": "1.2.8", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@nodelib/fs.scandir": "2.1.5", "@nodelib/fs.scandir": "2.1.5",
@@ -2130,54 +2040,6 @@
"node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0"
} }
}, },
"node_modules/@react-native/babel-preset": {
"version": "0.85.3",
"resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.85.3.tgz",
"integrity": "sha512-fD7fxEhkJB/aF57tWoXjaAWpklfrExYZS3k6aXPP3BQ77DZY7gvf/b7dbirwjID6NVnP1JDRJyTuPBGr0K/vlw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/core": "^7.25.2",
"@babel/plugin-proposal-export-default-from": "^7.24.7",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-syntax-export-default-from": "^7.24.7",
"@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
"@babel/plugin-syntax-optional-chaining": "^7.8.3",
"@babel/plugin-transform-async-generator-functions": "^7.25.4",
"@babel/plugin-transform-async-to-generator": "^7.24.7",
"@babel/plugin-transform-block-scoping": "^7.25.0",
"@babel/plugin-transform-class-properties": "^7.25.4",
"@babel/plugin-transform-classes": "^7.25.4",
"@babel/plugin-transform-destructuring": "^7.24.8",
"@babel/plugin-transform-flow-strip-types": "^7.25.2",
"@babel/plugin-transform-for-of": "^7.24.7",
"@babel/plugin-transform-modules-commonjs": "^7.24.8",
"@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7",
"@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7",
"@babel/plugin-transform-optional-catch-binding": "^7.24.7",
"@babel/plugin-transform-optional-chaining": "^7.24.8",
"@babel/plugin-transform-private-methods": "^7.24.7",
"@babel/plugin-transform-private-property-in-object": "^7.24.7",
"@babel/plugin-transform-react-display-name": "^7.24.7",
"@babel/plugin-transform-react-jsx": "^7.25.2",
"@babel/plugin-transform-react-jsx-self": "^7.24.7",
"@babel/plugin-transform-react-jsx-source": "^7.24.7",
"@babel/plugin-transform-regenerator": "^7.24.7",
"@babel/plugin-transform-runtime": "^7.24.7",
"@babel/plugin-transform-typescript": "^7.25.2",
"@babel/plugin-transform-unicode-regex": "^7.24.7",
"@react-native/babel-plugin-codegen": "0.85.3",
"babel-plugin-syntax-hermes-parser": "0.33.3",
"babel-plugin-transform-flow-enums": "^0.0.2",
"react-refresh": "^0.14.0"
},
"engines": {
"node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0"
},
"peerDependencies": {
"@babel/core": "*"
}
},
"node_modules/@react-native/codegen": { "node_modules/@react-native/codegen": {
"version": "0.85.3", "version": "0.85.3",
"resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.85.3.tgz", "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.85.3.tgz",
@@ -2308,58 +2170,6 @@
"node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0"
} }
}, },
"node_modules/@react-native/metro-babel-transformer": {
"version": "0.85.3",
"resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.85.3.tgz",
"integrity": "sha512-omuKq+r7jM4XvCMIlNMPP7Up3SyB8o5EAdZtF7YXniKyq7UOMBqhYHFqgsdOXr0lT+3ADf7VCJG3sb82jlBrrQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/core": "^7.25.2",
"@react-native/babel-preset": "0.85.3",
"hermes-parser": "0.33.3",
"nullthrows": "^1.1.1"
},
"engines": {
"node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0"
},
"peerDependencies": {
"@babel/core": "*"
}
},
"node_modules/@react-native/metro-babel-transformer/node_modules/hermes-estree": {
"version": "0.33.3",
"resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.33.3.tgz",
"integrity": "sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg==",
"license": "MIT",
"peer": true
},
"node_modules/@react-native/metro-babel-transformer/node_modules/hermes-parser": {
"version": "0.33.3",
"resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.33.3.tgz",
"integrity": "sha512-Yg3HgaG4CqgyowtYjX/FsnPAuZdHOqSMtnbpylbptsQ9nwwSKsy6uRWcGO5RK0EqiX12q8HvDWKgeAVajRO5DA==",
"license": "MIT",
"peer": true,
"dependencies": {
"hermes-estree": "0.33.3"
}
},
"node_modules/@react-native/metro-config": {
"version": "0.85.3",
"resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.85.3.tgz",
"integrity": "sha512-sVo6HepUmCcpdfozEf91lA0FjpLNNZYu/Zi9FiYiAQTK8pzATXDVTqhvdxpFrQn435p5eUTSbllvbH/KN+bnyA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@react-native/js-polyfills": "0.85.3",
"@react-native/metro-babel-transformer": "0.85.3",
"metro-config": "^0.84.3",
"metro-runtime": "^0.84.3"
},
"engines": {
"node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0"
}
},
"node_modules/@react-native/normalize-colors": { "node_modules/@react-native/normalize-colors": {
"version": "0.85.3", "version": "0.85.3",
"resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.85.3.tgz", "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.85.3.tgz",
@@ -2395,61 +2205,6 @@
"integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@testing-library/dom": {
"version": "10.4.1",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.10.4",
"@babel/runtime": "^7.12.5",
"@types/aria-query": "^5.0.1",
"aria-query": "5.3.0",
"dom-accessibility-api": "^0.5.9",
"lz-string": "^1.5.0",
"picocolors": "1.1.1",
"pretty-format": "^27.0.2"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@testing-library/dom/node_modules/ansi-styles": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/@testing-library/dom/node_modules/pretty-format": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"ansi-regex": "^5.0.1",
"ansi-styles": "^5.0.0",
"react-is": "^17.0.1"
},
"engines": {
"node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
}
},
"node_modules/@testing-library/dom/node_modules/react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
"license": "MIT",
"peer": true
},
"node_modules/@testing-library/jest-dom": { "node_modules/@testing-library/jest-dom": {
"version": "6.9.1", "version": "6.9.1",
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz",
@@ -2488,13 +2243,6 @@
"@testing-library/dom": ">=7.21.4" "@testing-library/dom": ">=7.21.4"
} }
}, },
"node_modules/@types/aria-query": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
"license": "MIT",
"peer": true
},
"node_modules/@types/istanbul-lib-coverage": { "node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.6", "version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
@@ -2532,21 +2280,12 @@
"version": "19.2.17", "version": "19.2.17",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz",
"integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"csstype": "^3.2.2" "csstype": "^3.2.2"
} }
}, },
"node_modules/@types/react-test-renderer": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-19.1.0.tgz",
"integrity": "sha512-XD0WZrHqjNrxA/MaR9O22w/RNidWR9YZmBdRGI7wcnWGrv/3dA8wKCJ8m63Sn+tLJhcjmuhOi629N66W6kgWzQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/yargs": { "node_modules/@types/yargs": {
"version": "17.0.35", "version": "17.0.35",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz",
@@ -2684,12 +2423,14 @@
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/anymatch": { "node_modules/anymatch": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"normalize-path": "^3.0.0", "normalize-path": "^3.0.0",
@@ -2907,6 +2648,12 @@
} }
} }
}, },
"node_modules/badgin": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/badgin/-/badgin-1.2.3.tgz",
"integrity": "sha512-NQGA7LcfCpSzIbGRbkgjgdWkjy7HI+Th5VLxTJfW5EeaAf3fnS+xWQaQOCYiny+q6QSvxqoSO04vCx+4u++EJw==",
"license": "MIT"
},
"node_modules/balanced-match": { "node_modules/balanced-match": {
"version": "4.0.4", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
@@ -2961,6 +2708,7 @@
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@@ -3087,6 +2835,7 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 6" "node": ">= 6"
@@ -3132,6 +2881,7 @@
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"anymatch": "~3.1.2", "anymatch": "~3.1.2",
@@ -3156,6 +2906,7 @@
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"is-glob": "^4.0.1" "is-glob": "^4.0.1"
@@ -3444,6 +3195,7 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"cssesc": "bin/cssesc" "cssesc": "bin/cssesc"
@@ -3456,6 +3208,7 @@
"version": "3.2.3", "version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/debug": { "node_modules/debug": {
@@ -3552,12 +3305,14 @@
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
"dev": true,
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/dlv": { "node_modules/dlv": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/dnssd-advertise": { "node_modules/dnssd-advertise": {
@@ -3566,13 +3321,6 @@
"integrity": "sha512-Ndrrf6BMPalkQPd/zubL+4YghH2J9NspapQ09uDXwYbvOPkP0oaqf5CkcwJ0b50kS2O3ul6yVu+jz+RY62Cejg==", "integrity": "sha512-Ndrrf6BMPalkQPd/zubL+4YghH2J9NspapQ09uDXwYbvOPkP0oaqf5CkcwJ0b50kS2O3ul6yVu+jz+RY62Cejg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/dom-accessibility-api": {
"version": "0.5.16",
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
"license": "MIT",
"peer": true
},
"node_modules/ee-first": { "node_modules/ee-first": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -3738,6 +3486,15 @@
} }
} }
}, },
"node_modules/expo-application": {
"version": "56.0.3",
"resolved": "https://registry.npmjs.org/expo-application/-/expo-application-56.0.3.tgz",
"integrity": "sha512-DdGGPlMuM6cSTeKhbvh6OeLr2O/+EI5BHKYrD+Do8sJPYgLwzGrgESELfyjJCpEhFzT+TgKIdmLmWXhNUQnHiw==",
"license": "MIT",
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-constants": { "node_modules/expo-constants": {
"version": "56.0.18", "version": "56.0.18",
"resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-56.0.18.tgz", "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-56.0.18.tgz",
@@ -3854,6 +3611,24 @@
"react-native": "*" "react-native": "*"
} }
}, },
"node_modules/expo-notifications": {
"version": "56.0.18",
"resolved": "https://registry.npmjs.org/expo-notifications/-/expo-notifications-56.0.18.tgz",
"integrity": "sha512-HHnrwyCLC5srFojcHYS2KskbNroy9o2fwPKdyhjrdjjrBu4sNRKm4LepcuZjDy98cZKEm89WIPW8O45vut8Rgw==",
"license": "MIT",
"dependencies": {
"@expo/image-utils": "^0.10.1",
"abort-controller": "^3.0.0",
"badgin": "^1.1.5",
"expo-application": "~56.0.3",
"expo-constants": "~56.0.18"
},
"peerDependencies": {
"expo": "*",
"react": "*",
"react-native": "*"
}
},
"node_modules/expo-router": { "node_modules/expo-router": {
"version": "56.2.11", "version": "56.2.11",
"resolved": "https://registry.npmjs.org/expo-router/-/expo-router-56.2.11.tgz", "resolved": "https://registry.npmjs.org/expo-router/-/expo-router-56.2.11.tgz",
@@ -4377,6 +4152,7 @@
"version": "3.3.3", "version": "3.3.3",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.stat": "^2.0.2",
@@ -4393,6 +4169,7 @@
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"is-glob": "^4.0.1" "is-glob": "^4.0.1"
@@ -4405,6 +4182,7 @@
"version": "1.20.1", "version": "1.20.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
"integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
"dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"reusify": "^1.0.4" "reusify": "^1.0.4"
@@ -4516,6 +4294,7 @@
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
@@ -4592,6 +4371,7 @@
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
"dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"is-glob": "^4.0.3" "is-glob": "^4.0.3"
@@ -4766,6 +4546,7 @@
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"binary-extensions": "^2.0.0" "binary-extensions": "^2.0.0"
@@ -4808,6 +4589,7 @@
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@@ -4826,6 +4608,7 @@
"version": "4.0.3", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"is-extglob": "^2.1.1" "is-extglob": "^2.1.1"
@@ -4959,6 +4742,7 @@
"version": "1.21.7", "version": "1.21.7",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
"dev": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"jiti": "bin/jiti.js" "jiti": "bin/jiti.js"
@@ -5327,6 +5111,7 @@
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=14" "node": ">=14"
@@ -5339,6 +5124,7 @@
"version": "1.2.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash.debounce": { "node_modules/lodash.debounce": {
@@ -5457,16 +5243,6 @@
"yallist": "^3.0.2" "yallist": "^3.0.2"
} }
}, },
"node_modules/lz-string": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
"license": "MIT",
"peer": true,
"bin": {
"lz-string": "bin/bin.js"
}
},
"node_modules/makeerror": { "node_modules/makeerror": {
"version": "1.0.12", "version": "1.0.12",
"resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
@@ -5498,6 +5274,7 @@
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 8" "node": ">= 8"
@@ -5891,6 +5668,7 @@
"version": "2.7.0", "version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"any-promise": "^1.0.0", "any-promise": "^1.0.0",
@@ -5970,6 +5748,7 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@@ -6012,6 +5791,7 @@
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@@ -6021,6 +5801,7 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 6" "node": ">= 6"
@@ -6267,6 +6048,7 @@
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@@ -6276,6 +6058,7 @@
"version": "4.0.7", "version": "4.0.7",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
"integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 6" "node": ">= 6"
@@ -6345,6 +6128,7 @@
"version": "15.1.0", "version": "15.1.0",
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"postcss-value-parser": "^4.0.0", "postcss-value-parser": "^4.0.0",
@@ -6362,6 +6146,7 @@
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
"integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@@ -6387,6 +6172,7 @@
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
"integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@@ -6429,6 +6215,7 @@
"version": "6.2.0", "version": "6.2.0",
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@@ -6454,6 +6241,7 @@
"version": "6.1.4", "version": "6.1.4",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz",
"integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"cssesc": "^3.0.0", "cssesc": "^3.0.0",
@@ -6467,6 +6255,7 @@
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/pretty-format": { "node_modules/pretty-format": {
@@ -6566,6 +6355,7 @@
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@@ -6592,9 +6382,9 @@
} }
}, },
"node_modules/react": { "node_modules/react": {
"version": "19.2.7", "version": "19.2.3",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
"integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@@ -6610,19 +6400,6 @@
"ws": "^7" "ws": "^7"
} }
}, },
"node_modules/react-dom": {
"version": "19.2.7",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz",
"integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
"peerDependencies": {
"react": "^19.2.7"
}
},
"node_modules/react-fast-compare": { "node_modules/react-fast-compare": {
"version": "3.2.2", "version": "3.2.2",
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
@@ -6993,46 +6770,29 @@
"react-native-reanimated": ">= 2.0.0" "react-native-reanimated": ">= 2.0.0"
} }
}, },
"node_modules/react-native-gesture-handler": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-3.0.2.tgz",
"integrity": "sha512-XBTgmvr2jh/xrMwlHTV2Z1x6IFUl3zfLxyaCAnvj3GSXK5YlY3ZmiIs57hniPmh3I7RtjPFxtGdRr0i+PzyBrg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/react-test-renderer": "^19.1.0",
"invariant": "^2.2.4"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native-is-edge-to-edge": { "node_modules/react-native-is-edge-to-edge": {
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.3.1.tgz", "resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.3.1.tgz",
"integrity": "sha512-NIXU/iT5+ORyCc7p0z2nnlkouYKX425vuU1OEm6bMMtWWR9yvb+Xg5AZmImTKoF9abxCPqrKC3rOZsKzUYgYZA==", "integrity": "sha512-NIXU/iT5+ORyCc7p0z2nnlkouYKX425vuU1OEm6bMMtWWR9yvb+Xg5AZmImTKoF9abxCPqrKC3rOZsKzUYgYZA==",
"license": "MIT", "license": "MIT",
"peer": true,
"peerDependencies": { "peerDependencies": {
"react": "*", "react": "*",
"react-native": "*" "react-native": "*"
} }
}, },
"node_modules/react-native-reanimated": { "node_modules/react-native-reanimated": {
"version": "4.5.0", "version": "4.3.1",
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.5.0.tgz", "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.3.1.tgz",
"integrity": "sha512-+iPfvK34PKKYP/p/4TaBliFkbfvjGDIvXuiiaxvISP5ip7sWegvlacwU/uAV6zNDSSmX0tDyER7PurPMKGDipA==", "integrity": "sha512-KhGsS0YkCA+gusgyzlf9hnqzVPIR398KTpqXyqq/+yYJJPAvyEEPKcxlB0xtOOXSMrR2A9uRKVARVQhZwrOh+Q==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"react-native-is-edge-to-edge": "^1.3.1", "react-native-is-edge-to-edge": "^1.3.1",
"semver": "^7.7.3" "semver": "^7.7.3"
}, },
"peerDependencies": { "peerDependencies": {
"react": "*", "react": "*",
"react-native": "0.83 - 0.86", "react-native": "0.81 - 0.85",
"react-native-worklets": "0.10.x" "react-native-worklets": "0.8.x"
} }
}, },
"node_modules/react-native-safe-area-context": { "node_modules/react-native-safe-area-context": {
@@ -7059,33 +6819,6 @@
"react-native": ">=0.82.0" "react-native": ">=0.82.0"
} }
}, },
"node_modules/react-native-worklets": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.10.0.tgz",
"integrity": "sha512-JhE6IxDf6iabC0qu3+TAKA4v9RlluXmoIngPQX7/QUByf75lfrsHZ6/dQhyjEWnp1EEQiwzz8Cpew140ZcewDw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/plugin-transform-arrow-functions": "^7.27.1",
"@babel/plugin-transform-class-properties": "^7.28.6",
"@babel/plugin-transform-classes": "^7.28.6",
"@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6",
"@babel/plugin-transform-optional-chaining": "^7.28.6",
"@babel/plugin-transform-shorthand-properties": "^7.27.1",
"@babel/plugin-transform-template-literals": "^7.27.1",
"@babel/plugin-transform-unicode-regex": "^7.27.1",
"@babel/preset-typescript": "^7.28.5",
"@babel/types": "^7.27.1",
"convert-source-map": "^2.0.0",
"semver": "^7.7.4"
},
"peerDependencies": {
"@babel/core": "*",
"@react-native/metro-config": "*",
"react": "*",
"react-native": "0.83 - 0.86"
}
},
"node_modules/react-native/node_modules/commander": { "node_modules/react-native/node_modules/commander": {
"version": "12.1.0", "version": "12.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
@@ -7177,6 +6910,7 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"pify": "^2.3.0" "pify": "^2.3.0"
@@ -7186,6 +6920,7 @@
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"picomatch": "^2.2.1" "picomatch": "^2.2.1"
@@ -7328,6 +7063,7 @@
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"iojs": ">=1.0.0", "iojs": ">=1.0.0",
@@ -7338,6 +7074,7 @@
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@@ -7752,6 +7489,7 @@
"version": "3.35.1", "version": "3.35.1",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
"integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/gen-mapping": "^0.3.2",
@@ -7774,6 +7512,7 @@
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 6" "node": ">= 6"
@@ -7820,6 +7559,7 @@
"version": "3.4.19", "version": "3.4.19",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
"integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@alloc/quick-lru": "^5.2.0", "@alloc/quick-lru": "^5.2.0",
@@ -7897,6 +7637,7 @@
"version": "3.3.1", "version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"any-promise": "^1.0.0" "any-promise": "^1.0.0"
@@ -7906,6 +7647,7 @@
"version": "1.6.0", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"thenify": ">= 3.1.0 < 4" "thenify": ">= 3.1.0 < 4"
@@ -8002,6 +7744,7 @@
"version": "0.1.13", "version": "0.1.13",
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
"dev": true,
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/tslib": { "node_modules/tslib": {
@@ -8023,7 +7766,7 @@
"version": "6.0.3", "version": "6.0.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
"devOptional": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
@@ -8174,6 +7917,7 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/utils-merge": { "node_modules/utils-merge": {
+3 -1
View File
@@ -10,12 +10,14 @@
"expo-document-picker": "~56.0.4", "expo-document-picker": "~56.0.4",
"expo-file-system": "~56.0.8", "expo-file-system": "~56.0.8",
"expo-linking": "~56.0.14", "expo-linking": "~56.0.14",
"expo-notifications": "~56.0.18",
"expo-router": "~56.2.11", "expo-router": "~56.2.11",
"expo-secure-store": "~56.0.4", "expo-secure-store": "~56.0.4",
"expo-status-bar": "~56.0.4", "expo-status-bar": "~56.0.4",
"nativewind": "^4.2.6", "nativewind": "^4.2.6",
"react": "^19.2.7", "react": "19.2.3",
"react-native": "0.85.3", "react-native": "0.85.3",
"react-native-reanimated": "4.3.1",
"react-native-safe-area-context": "~5.7.0", "react-native-safe-area-context": "~5.7.0",
"react-native-screens": "4.25.2" "react-native-screens": "4.25.2"
}, },
+5
View File
@@ -1,3 +1,8 @@
export interface ClaudeAuth {
sessionKey: string;
lastActiveOrg?: string;
}
export interface ClaudeOrg { export interface ClaudeOrg {
uuid: string; uuid: string;
name: string; name: string;